Skip to content

Commit 8b69361

Browse files
committed
Merge remote-tracking branch 'origin/feat/ai-sre' into feat/generated-relative-time
2 parents ad87b26 + b534516 commit 8b69361

5 files changed

Lines changed: 617 additions & 10 deletions

File tree

internal/cli/display_columns.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package cli
2+
3+
// colSpec is a display-only column for the generic table renderer: which row
4+
// field to show, its header, and an optional width cap. It NEVER affects flags or
5+
// json/toon output — a wrong entry degrades a single table column at worst, it
6+
// can't cause a functional error. Field is the Go struct field name on the row
7+
// type; timestamp fields are detected and formatted automatically.
8+
type colSpec struct {
9+
Header string
10+
Field string
11+
MaxWidth int
12+
}
13+
14+
// displayColumns maps a go-flashduty response row type (by Go type name) to its
15+
// human table columns, seeded from the hand-written column sets the curated
16+
// commands used before the CLI converged on generated commands. Row types with
17+
// no entry fall back to the reflective heuristic in generic_table.go.
18+
//
19+
// Names are intentionally not resolved here (e.g. ChannelItem shows TEAM_ID /
20+
// CREATOR_ID, not team/creator names): id→name enrichment belongs in the API
21+
// response, not the client. Until the API carries those names, the table shows
22+
// the ids; json/toon is unaffected either way.
23+
var displayColumns = map[string][]colSpec{
24+
"IncidentInfo": {
25+
{Header: "ID", Field: "IncidentID"},
26+
{Header: "TITLE", Field: "Title", MaxWidth: 50},
27+
{Header: "SEVERITY", Field: "IncidentSeverity"},
28+
{Header: "PROGRESS", Field: "Progress"},
29+
{Header: "CHANNEL", Field: "ChannelName"},
30+
{Header: "CREATED", Field: "StartTime"},
31+
},
32+
"PastIncidentItem": {
33+
{Header: "ID", Field: "IncidentID"},
34+
{Header: "TITLE", Field: "Title", MaxWidth: 50},
35+
{Header: "SEVERITY", Field: "IncidentSeverity"},
36+
{Header: "PROGRESS", Field: "Progress"},
37+
{Header: "CHANNEL", Field: "ChannelName"},
38+
{Header: "CREATED", Field: "StartTime"},
39+
},
40+
"AlertInfo": {
41+
{Header: "ALERT_ID", Field: "AlertID"},
42+
{Header: "TITLE", Field: "Title", MaxWidth: 50},
43+
{Header: "SEVERITY", Field: "AlertSeverity"},
44+
{Header: "STATUS", Field: "AlertStatus"},
45+
{Header: "STARTED", Field: "StartTime"},
46+
},
47+
"AlertItem": {
48+
{Header: "ID", Field: "AlertID"},
49+
{Header: "TITLE", Field: "Title", MaxWidth: 50},
50+
{Header: "SEVERITY", Field: "AlertSeverity"},
51+
{Header: "STATUS", Field: "AlertStatus"},
52+
{Header: "EVENTS", Field: "EventCnt"},
53+
{Header: "CHANNEL", Field: "ChannelName"},
54+
{Header: "STARTED", Field: "StartTime"},
55+
},
56+
"AlertEventItem": {
57+
{Header: "EVENT_ID", Field: "EventID"},
58+
{Header: "ALERT_ID", Field: "AlertID"},
59+
{Header: "SEVERITY", Field: "EventSeverity"},
60+
{Header: "STATUS", Field: "EventStatus"},
61+
{Header: "TIME", Field: "EventTime"},
62+
{Header: "TITLE", Field: "Title", MaxWidth: 50},
63+
},
64+
"ChangeItem": {
65+
{Header: "ID", Field: "ChangeID"},
66+
{Header: "TITLE", Field: "Title", MaxWidth: 50},
67+
{Header: "STATUS", Field: "ChangeStatus"},
68+
{Header: "CHANNEL", Field: "ChannelName"},
69+
{Header: "TIME", Field: "StartTime"},
70+
},
71+
"ChannelItem": {
72+
{Header: "ID", Field: "ChannelID"},
73+
{Header: "NAME", Field: "ChannelName", MaxWidth: 40},
74+
{Header: "TEAM_ID", Field: "TeamID"},
75+
{Header: "CREATOR_ID", Field: "CreatorID"},
76+
{Header: "STATUS", Field: "Status"},
77+
},
78+
"TeamItem": {
79+
{Header: "ID", Field: "TeamID"},
80+
{Header: "NAME", Field: "TeamName", MaxWidth: 40},
81+
},
82+
"MemberItem": {
83+
{Header: "ID", Field: "MemberID"},
84+
{Header: "NAME", Field: "MemberName", MaxWidth: 30},
85+
{Header: "EMAIL", Field: "Email"},
86+
{Header: "STATUS", Field: "Status"},
87+
{Header: "TIMEZONE", Field: "TimeZone"},
88+
},
89+
"FieldItem": {
90+
{Header: "ID", Field: "FieldID"},
91+
{Header: "NAME", Field: "FieldName"},
92+
{Header: "DISPLAY_NAME", Field: "DisplayName"},
93+
{Header: "TYPE", Field: "FieldType"},
94+
},
95+
"WarRoomItem": {
96+
{Header: "INTEGRATION", Field: "IntegrationID"},
97+
{Header: "CHAT_ID", Field: "ChatID"},
98+
{Header: "INCIDENT_ID", Field: "IncidentID"},
99+
{Header: "STATUS", Field: "Status"},
100+
{Header: "PLUGIN", Field: "PluginType"},
101+
{Header: "CREATED", Field: "CreatedAt"},
102+
},
103+
"WarRoomPersonItem": {
104+
{Header: "PERSON_ID", Field: "PersonID"},
105+
{Header: "NAME", Field: "PersonName"},
106+
{Header: "EMAIL", Field: "Email"},
107+
{Header: "STATUS", Field: "Status"},
108+
},
109+
}

internal/cli/gen_support.go

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -164,20 +164,16 @@ func bindURLTagged(body map[string]any, rv reflect.Value) {
164164
}
165165
}
166166

167-
// printGenericResult renders a generated command's typed response. Generated
168-
// commands have no curated column set, so in machine-readable mode (TOON/JSON)
169-
// it marshals the whole value — which is what the agent reads — and in human
170-
// table mode it falls back to pretty JSON rather than a blank table.
167+
// printGenericResult renders a generated command's typed response. In
168+
// machine-readable mode (TOON/JSON) it marshals the whole value — which is what
169+
// the agent reads. In human (table) mode it derives an aligned table by
170+
// reflection (renderGenericTable), since generated commands carry no hand-written
171+
// column set; anything that isn't a list or object falls back to indented JSON.
171172
func printGenericResult(ctx *RunContext, data any) error {
172173
if ctx.Structured() {
173174
return ctx.Printer.Print(data, nil)
174175
}
175-
out, err := json.MarshalIndent(data, "", " ")
176-
if err != nil {
177-
return fmt.Errorf("failed to marshal output: %w", err)
178-
}
179-
_, err = fmt.Fprintln(ctx.Writer, string(out))
180-
return err
176+
return renderGenericTable(ctx, data)
181177
}
182178

183179
// genParseTimeFlag parses a relative-or-absolute time flag into unix seconds,

0 commit comments

Comments
 (0)