Skip to content

Commit 956d453

Browse files
authored
Merge pull request #14 from jonas1307/feat/list-output
feat(list): add --output json/csv to list, mine, and get
2 parents bac927b + e8afaef commit 956d453

8 files changed

Lines changed: 77 additions & 0 deletions

File tree

‎DevOps/Actions/ActionHelpers.cs‎

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,66 @@
11
using DevOps.Responses;
22
using DevOps.Services;
3+
using DevOps.Utils;
4+
using Newtonsoft.Json;
35
using Spectre.Console;
46

57
namespace DevOps.Actions;
68

79
internal static class ActionHelpers
810
{
11+
/// <summary>
12+
/// Writes work items as machine-readable JSON or CSV to stdout. Returns a non-zero
13+
/// exit code (with an error) for an unknown format.
14+
/// </summary>
15+
internal static int WriteWorkItemsOutput(List<WorkItemResponse> items, string format)
16+
{
17+
switch (format?.ToLowerInvariant())
18+
{
19+
case "json":
20+
var projected = items.Select(i => new
21+
{
22+
id = i.Id,
23+
type = i.Fields.WorkItemType,
24+
title = i.Fields.Title,
25+
state = i.Fields.State,
26+
assignedTo = i.Fields.AssignedTo?.DisplayName,
27+
project = i.Fields.TeamProject,
28+
parentId = i.Fields.ParentId,
29+
priority = i.Fields.Priority,
30+
createdDate = i.Fields.CreatedDate,
31+
changedDate = i.Fields.ChangedDate
32+
});
33+
Console.WriteLine(JsonConvert.SerializeObject(projected, Formatting.Indented));
34+
return 0;
35+
36+
case "csv":
37+
Console.WriteLine("id,type,title,state,assigned_to,project,parent_id,priority,created,changed");
38+
foreach (var i in items)
39+
{
40+
var f = i.Fields;
41+
Console.WriteLine(string.Join(",",
42+
i.Id,
43+
Csv(f.WorkItemType), Csv(f.Title), Csv(f.State),
44+
Csv(f.AssignedTo?.DisplayName), Csv(f.TeamProject),
45+
f.ParentId?.ToString() ?? "", f.Priority?.ToString() ?? "",
46+
f.CreatedDate.ToString("yyyy-MM-dd"), f.ChangedDate.ToString("yyyy-MM-dd")));
47+
}
48+
return 0;
49+
50+
default:
51+
ConsoleHelper.WriteError($"Unknown output format '{format}'. Use 'json' or 'csv'.");
52+
return 1;
53+
}
54+
}
55+
56+
private static string Csv(string value)
57+
{
58+
if (string.IsNullOrEmpty(value)) return "";
59+
return value.Contains(',') || value.Contains('"') || value.Contains('\n')
60+
? "\"" + value.Replace("\"", "\"\"") + "\""
61+
: value;
62+
}
63+
964
/// <summary>Creates a table with bold headers, sized to the terminal, using the configured border.</summary>
1065
internal static Table NewTable(params string[] columns)
1166
{

‎DevOps/Actions/GetAction.cs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ internal static async Task<int> Execute(GetOptions opts, CancellationToken ct)
1313
var project = ConfigService.ResolveProject(opts.Project);
1414
var item = await HttpService.GetWorkItem(opts.Id, project, ct);
1515

16+
if (!string.IsNullOrEmpty(opts.Output))
17+
return ActionHelpers.WriteWorkItemsOutput([item], opts.Output);
18+
1619
Console.WriteLine($"ID : {item.Id}");
1720
Console.WriteLine($"Type : {item.Fields.WorkItemType}");
1821
Console.WriteLine($"Title : {item.Fields.Title}");

‎DevOps/Actions/ListAction.cs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ internal static async Task<int> Execute(ListOptions opts, CancellationToken ct)
1414
var project = ConfigService.ResolveProject(opts.Project);
1515
var (items, totalMatched) = await HttpService.ListWorkItems(project, opts.State, opts.Type, opts.AssignedTo, opts.Query, opts.ParentId, opts.Top, ct);
1616

17+
if (!string.IsNullOrEmpty(opts.Output))
18+
return ActionHelpers.WriteWorkItemsOutput(items, opts.Output);
19+
1720
if (items.Count == 0)
1821
{
1922
Console.WriteLine("No work items found.");

‎DevOps/Actions/MineAction.cs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ internal static async Task<int> Execute(MineOptions opts, CancellationToken ct)
1414
var project = ConfigService.ResolveProject(opts.Project);
1515
var (items, totalMatched) = await HttpService.ListWorkItems(project, opts.State, opts.Type, "me", opts.Query, opts.ParentId, opts.Top, ct);
1616

17+
if (!string.IsNullOrEmpty(opts.Output))
18+
return ActionHelpers.WriteWorkItemsOutput(items, opts.Output);
19+
1720
if (items.Count == 0)
1821
{
1922
Console.WriteLine("No work items assigned to you.");

‎DevOps/Options/GetOptions.cs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,7 @@ public class GetOptions
1010

1111
[Option('p', "project", Required = false, HelpText = "Project name. Uses default if configured.")]
1212
public string Project { get; set; }
13+
14+
[Option('o', "output", Required = false, HelpText = "Output format: 'json' or 'csv'. Defaults to a detailed view.")]
15+
public string Output { get; set; }
1316
}

‎DevOps/Options/ListOptions.cs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,7 @@ public class ListOptions
2828

2929
[Option('n', "top", Required = false, Default = 50, HelpText = "Maximum number of work items to fetch (default: 50).")]
3030
public int Top { get; set; }
31+
32+
[Option('o', "output", Required = false, HelpText = "Output format: 'json' or 'csv'. Defaults to a table.")]
33+
public string Output { get; set; }
3134
}

‎DevOps/Options/MineOptions.cs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,7 @@ public class MineOptions
2222

2323
[Option('n', "top", Required = false, Default = 50, HelpText = "Maximum number of work items to fetch (default: 50).")]
2424
public int Top { get; set; }
25+
26+
[Option('o', "output", Required = false, HelpText = "Output format: 'json' or 'csv'. Defaults to a table.")]
27+
public string Output { get; set; }
2528
}

‎README.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ devops get -i 1234 -p AnotherProject
9191
|---|---|---|
9292
| `--id` | `-i` | Work item ID (required) |
9393
| `--project` | `-p` | Project name (uses default if configured) |
94+
| `--output` | `-o` | Output format: `json` or `csv`. Defaults to a detailed view |
9495

9596
---
9697

@@ -114,6 +115,7 @@ devops mine -p 1234 # only children of work item 1234
114115
| `--query` | `-q` | Additional WIQL WHERE clause |
115116
| `--parent` | `-p` | Filter by parent work item ID |
116117
| `--top` | `-n` | Maximum number of work items to fetch (default: 50) |
118+
| `--output` | `-o` | Output format: `json` or `csv`. Defaults to a table |
117119

118120
---
119121

@@ -126,6 +128,7 @@ devops list -t Bug -a me
126128
devops list -P MyProject -s "In Progress" -t Task
127129
devops list -p 1234 # only children of work item 1234
128130
devops list -n 200 # fetch up to 200 items instead of the default 50
131+
devops list -s Active -o json # machine-readable output for scripting
129132
devops list -q "[System.IterationPath] UNDER 'MyProject\\Sprint 1'"
130133
```
131134

@@ -140,6 +143,7 @@ Queries fetch up to `--top` items (default 50). When more match than were fetche
140143
| `--query` | `-q` | WIQL WHERE clause for advanced filtering |
141144
| `--parent` | `-p` | Filter by parent work item ID |
142145
| `--top` | `-n` | Maximum number of work items to fetch (default: 50) |
146+
| `--output` | `-o` | Output format: `json` or `csv`. Defaults to a table |
143147

144148
---
145149

0 commit comments

Comments
 (0)