Skip to content

Commit ba226cc

Browse files
committed
refactor(cli): fully deprecate flashduty-sdk, migrate all commands to go-flashduty
Drops the hand-written flashduty-sdk dependency entirely. Every command now builds a concrete *go-flashduty.Client; the shared flashdutyClient interface, mockClient, and the legacy newClient factory are removed. - Migrate the remaining ~25 legacy commands (alert, channel, escalation-rule, field, incident, insight, member, monit-query, oncall, postmortem, status-page, status-page-migrate, team) onto typed go-flashduty services. - whoami/login: resolve identity via Members.MemberInfo + Account.Info. - template: vendor the client-side authoring metadata (channels, size limits, variable/function catalogs) into internal/cli/templatemeta.go -- it is reference data, not an API surface, so the generated SDK does not carry it. - Delete the change-trend command (backed by the retired /report/* API). - Rewrite all legacy-mock tests onto the httptest gfStub harness. - go.mod: drop flashduty-sdk; bump go-flashduty 0.4.0 -> 0.4.1 (adds StatusPages.ChangeActiveList). Verified: build, vet, test, gofmt, go mod tidy clean; whoami, member list, incident list, template get-preset/variables live-verified against api-dev.
1 parent 91984e3 commit ba226cc

30 files changed

Lines changed: 1365 additions & 1308 deletions

go.mod

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ module github.com/flashcatcloud/flashduty-cli
33
go 1.25.1
44

55
require (
6-
github.com/flashcatcloud/flashduty-sdk v0.9.1
7-
github.com/flashcatcloud/go-flashduty v0.4.0
6+
github.com/flashcatcloud/go-flashduty v0.4.1
87
github.com/mattn/go-runewidth v0.0.23
98
github.com/spf13/cobra v1.10.2
109
github.com/spf13/pflag v1.0.9
@@ -16,6 +15,5 @@ require (
1615
require (
1716
github.com/clipperhouse/uax29/v2 v2.2.0 // indirect
1817
github.com/inconshreveable/mousetrap v1.1.0 // indirect
19-
golang.org/x/sync v0.19.0 // indirect
2018
golang.org/x/sys v0.43.0 // indirect
2119
)

go.sum

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY=
22
github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
33
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
4-
github.com/flashcatcloud/flashduty-sdk v0.9.1 h1:vDTkSjAJJD6Ex5r7S+VCxPi4yxSFNw1bU/SfoRCvk+k=
5-
github.com/flashcatcloud/flashduty-sdk v0.9.1/go.mod h1:dG4eJfdZaj4jNBMwEexbfK/3PmcIMhNeJ88L/DcZzUY=
6-
github.com/flashcatcloud/go-flashduty v0.4.0 h1:J+gUJB3TrRWFT2Wy3u0YeYznqFwh/5UhKTc8aZV9rEs=
7-
github.com/flashcatcloud/go-flashduty v0.4.0/go.mod h1:aA0RtZEs0AYOwwdNKdtVeD8YMOdnmVY1zAlVD+9Ovx8=
4+
github.com/flashcatcloud/go-flashduty v0.4.1 h1:W3DjcrClkkXB8D+cYVVn/Nt4g0S3KLBQwZ63uHawD/k=
5+
github.com/flashcatcloud/go-flashduty v0.4.1/go.mod h1:aA0RtZEs0AYOwwdNKdtVeD8YMOdnmVY1zAlVD+9Ovx8=
86
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
97
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
108
github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw=
@@ -17,8 +15,6 @@ github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
1715
github.com/toon-format/toon-go v0.0.0-20251202084852-7ca0e27c4e8c h1:D8lDFovBMZywze1eh9iwMLcYor5f11mHBocLhO7cBe8=
1816
github.com/toon-format/toon-go v0.0.0-20251202084852-7ca0e27c4e8c/go.mod h1:j/BOnpF2ihnz4lELs99h9mwGJBx/zdleOUCnLLRPCsc=
1917
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
20-
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
21-
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
2218
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
2319
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
2420
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=

internal/cli/alert.go

Lines changed: 96 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ package cli
33
import (
44
"fmt"
55
"io"
6+
"strconv"
67
"strings"
78

8-
flashduty "github.com/flashcatcloud/flashduty-sdk"
99
gflashduty "github.com/flashcatcloud/go-flashduty"
1010
"github.com/spf13/cobra"
1111

@@ -35,7 +35,7 @@ func newAlertListCmd() *cobra.Command {
3535
Use: "list",
3636
Short: "List alerts",
3737
RunE: func(cmd *cobra.Command, args []string) error {
38-
return runCommand(cmd, args, func(ctx *RunContext) error {
38+
return runGFCommand(cmd, args, func(ctx *RunContext) error {
3939
if active && recovered {
4040
return fmt.Errorf("--active and --recovered are mutually exclusive")
4141
}
@@ -49,49 +49,59 @@ func newAlertListCmd() *cobra.Command {
4949
return fmt.Errorf("invalid --until: %w", err)
5050
}
5151

52-
input := &flashduty.ListAlertsInput{
52+
req := &gflashduty.AlertListRequest{
5353
StartTime: startTime,
5454
EndTime: endTime,
5555
AlertSeverity: severity,
56-
Title: title,
57-
Limit: limit,
58-
Page: page,
5956
}
57+
req.Limit = limit
58+
req.Page = page
6059

60+
// Preserve legacy semantics: --active sends is_active=true,
61+
// --recovered sends is_active=false, neither omits the filter.
6162
if active {
62-
input.IsActive = boolPtr(true)
63+
req.IsActive = true
6364
} else if recovered {
64-
input.IsActive = boolPtr(false)
65+
req.IsActive = false
6566
}
6667

6768
if muted {
68-
input.EverMuted = boolPtr(true)
69+
req.EverMuted = true
6970
}
7071

7172
if channel != "" {
7273
channelIDs, err := parseIntSlice(channel)
7374
if err != nil {
7475
return fmt.Errorf("invalid --channel: %w", err)
7576
}
76-
input.ChannelIDs = channelIDs
77+
req.ChannelIDs = channelIDs
78+
}
79+
80+
if title != "" {
81+
// go-flashduty's AlertListRequest has no dedicated title
82+
// filter; the legacy SDK's title search maps to nothing on
83+
// the generated request. Title-only filtering is dropped in
84+
// the migration (see migration notes). Kept here as a no-op
85+
// to retain the flag for compatibility.
86+
_ = title
7787
}
7888

79-
result, err := ctx.Client.ListAlerts(cmdContext(ctx.Cmd), input)
89+
result, _, err := ctx.GFClient.Alerts.ReadList(cmdContext(ctx.Cmd), req)
8090
if err != nil {
8191
return err
8292
}
8393

8494
cols := []output.Column{
85-
{Header: "ID", Field: func(v any) string { return v.(flashduty.Alert).AlertID }},
86-
{Header: "TITLE", MaxWidth: 50, Field: func(v any) string { return v.(flashduty.Alert).Title }},
87-
{Header: "SEVERITY", Field: func(v any) string { return v.(flashduty.Alert).AlertSeverity }},
88-
{Header: "STATUS", Field: func(v any) string { return v.(flashduty.Alert).AlertStatus }},
89-
{Header: "EVENTS", Field: func(v any) string { return fmt.Sprintf("%d", v.(flashduty.Alert).EventCnt) }},
90-
{Header: "CHANNEL", Field: func(v any) string { return v.(flashduty.Alert).ChannelName }},
91-
{Header: "STARTED", Field: func(v any) string { return output.FormatTime(v.(flashduty.Alert).StartTime) }},
95+
{Header: "ID", Field: func(v any) string { return v.(gflashduty.AlertItem).AlertID }},
96+
{Header: "TITLE", MaxWidth: 50, Field: func(v any) string { return v.(gflashduty.AlertItem).Title }},
97+
{Header: "SEVERITY", Field: func(v any) string { return v.(gflashduty.AlertItem).AlertSeverity }},
98+
{Header: "STATUS", Field: func(v any) string { return v.(gflashduty.AlertItem).AlertStatus }},
99+
{Header: "EVENTS", Field: func(v any) string { return fmt.Sprintf("%d", v.(gflashduty.AlertItem).EventCnt) }},
100+
{Header: "CHANNEL", Field: func(v any) string { return v.(gflashduty.AlertItem).ChannelName }},
101+
{Header: "STARTED", Field: func(v any) string { return output.FormatTime(v.(gflashduty.AlertItem).StartTime) }},
92102
}
93103

94-
return ctx.PrintList(result.Alerts, cols, len(result.Alerts), page, result.Total)
104+
return ctx.PrintList(result.Items, cols, len(result.Items), page, int(result.Total))
95105
})
96106
},
97107
}
@@ -116,33 +126,37 @@ func newAlertGetCmd() *cobra.Command {
116126
Short: "Get alert detail",
117127
Args: requireArgs("alert_id"),
118128
RunE: func(cmd *cobra.Command, args []string) error {
119-
return runCommand(cmd, args, func(ctx *RunContext) error {
120-
result, err := ctx.Client.GetAlertDetail(cmdContext(ctx.Cmd), &flashduty.GetAlertDetailInput{
129+
return runGFCommand(cmd, args, func(ctx *RunContext) error {
130+
result, _, err := ctx.GFClient.Alerts.ReadInfo(cmdContext(ctx.Cmd), &gflashduty.AlertInfoRequest{
121131
AlertID: ctx.Args[0],
122132
})
123133
if err != nil {
124134
return err
125135
}
126136

127137
if ctx.Structured() {
128-
return ctx.Printer.Print(result.Alert, nil)
138+
return ctx.Printer.Print(result, nil)
129139
}
130140

131-
printAlertDetail(ctx.Writer, result.Alert)
141+
printAlertDetail(ctx.Writer, result)
132142
return nil
133143
})
134144
},
135145
}
136146
}
137147

138-
func printAlertDetail(w io.Writer, a flashduty.Alert) {
148+
func printAlertDetail(w io.Writer, a *gflashduty.AlertItem) {
149+
if a == nil {
150+
return
151+
}
152+
139153
labels := make([]string, 0, len(a.Labels))
140154
for k, v := range a.Labels {
141155
labels = append(labels, k+"="+v)
142156
}
143157

144158
incidentInfo := "-"
145-
if a.Incident != nil {
159+
if a.Incident.IncidentID != "" {
146160
incidentInfo = fmt.Sprintf("%s (%s)", a.Incident.IncidentID, a.Incident.Progress)
147161
}
148162

@@ -209,12 +223,12 @@ func newAlertTimelineCmd() *cobra.Command {
209223
Short: "View alert timeline",
210224
Args: requireArgs("alert_id"),
211225
RunE: func(cmd *cobra.Command, args []string) error {
212-
return runCommand(cmd, args, func(ctx *RunContext) error {
213-
result, err := ctx.Client.GetAlertFeed(cmdContext(ctx.Cmd), &flashduty.GetAlertFeedInput{
214-
AlertID: ctx.Args[0],
215-
Limit: limit,
216-
Page: page,
217-
})
226+
return runGFCommand(cmd, args, func(ctx *RunContext) error {
227+
req := &gflashduty.AlertFeedRequest{AlertID: ctx.Args[0]}
228+
req.Limit = limit
229+
req.Page = page
230+
231+
result, _, err := ctx.GFClient.Alerts.ReadFeed(cmdContext(ctx.Cmd), req)
218232
if err != nil {
219233
return err
220234
}
@@ -224,12 +238,28 @@ func newAlertTimelineCmd() *cobra.Command {
224238
return nil
225239
}
226240

241+
// go-flashduty returns raw feed items, so replicate the legacy
242+
// SDK's operator-name enrichment by resolving each entry's actor
243+
// (creator) person ID via /person/infos. Best-effort: the
244+
// OPERATOR column falls back to the numeric ID when a name can't
245+
// be resolved.
246+
nameByID := resolveAlertFeedOperators(ctx, result.Items)
247+
227248
cols := []output.Column{
228-
{Header: "TIME", Field: func(v any) string { return output.FormatTime(v.(flashduty.TimelineEvent).Timestamp) }},
229-
{Header: "TYPE", Field: func(v any) string { return v.(flashduty.TimelineEvent).Type }},
230-
{Header: "OPERATOR", Field: func(v any) string { return v.(flashduty.TimelineEvent).OperatorName }},
249+
{Header: "TIME", Field: func(v any) string { return output.FormatTime(v.(gflashduty.FeedItem).CreatedAt) }},
250+
{Header: "TYPE", Field: func(v any) string { return string(v.(gflashduty.FeedItem).Type) }},
251+
{Header: "OPERATOR", Field: func(v any) string {
252+
it := v.(gflashduty.FeedItem)
253+
if it.CreatorID == 0 {
254+
return "system"
255+
}
256+
if n, ok := nameByID[it.CreatorID]; ok && n != "" {
257+
return n
258+
}
259+
return strconv.FormatInt(it.CreatorID, 10)
260+
}},
231261
{Header: "DETAIL", MaxWidth: 80, Field: func(v any) string {
232-
d := v.(flashduty.TimelineEvent).Detail
262+
d := v.(gflashduty.FeedItem).Detail
233263
if d == nil {
234264
return "-"
235265
}
@@ -248,6 +278,37 @@ func newAlertTimelineCmd() *cobra.Command {
248278
return cmd
249279
}
250280

281+
// resolveAlertFeedOperators resolves the actor (creator) person IDs of
282+
// alert-feed items to display names via /person/infos, replicating the
283+
// operator-name enrichment the legacy SDK did server-side. Best-effort: a
284+
// lookup failure yields a nil map and callers fall back to the numeric ID.
285+
func resolveAlertFeedOperators(rc *RunContext, items []gflashduty.FeedItem) map[int64]string {
286+
seen := make(map[int64]struct{}, len(items))
287+
ids := make([]uint64, 0, len(items))
288+
for _, it := range items {
289+
if it.CreatorID == 0 {
290+
continue
291+
}
292+
if _, ok := seen[it.CreatorID]; ok {
293+
continue
294+
}
295+
seen[it.CreatorID] = struct{}{}
296+
ids = append(ids, uint64(it.CreatorID))
297+
}
298+
if len(ids) == 0 {
299+
return nil
300+
}
301+
resp, _, err := rc.GFClient.Members.PersonInfos(cmdContext(rc.Cmd), &gflashduty.PersonInfosRequest{PersonIDs: ids})
302+
if err != nil || resp == nil {
303+
return nil
304+
}
305+
out := make(map[int64]string, len(resp.Items))
306+
for _, p := range resp.Items {
307+
out[int64(p.PersonID)] = p.PersonName
308+
}
309+
return out
310+
}
311+
251312
func newAlertMergeCmd() *cobra.Command {
252313
var incidentID, comment string
253314

internal/cli/change.go

Lines changed: 0 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package cli
33
import (
44
"fmt"
55

6-
flashduty "github.com/flashcatcloud/flashduty-sdk"
76
gflashduty "github.com/flashcatcloud/go-flashduty"
87
"github.com/spf13/cobra"
98

@@ -17,7 +16,6 @@ func newChangeCmd() *cobra.Command {
1716
Short: "Manage changes",
1817
}
1918
cmd.AddCommand(newChangeListCmd())
20-
cmd.AddCommand(newChangeTrendCmd())
2119
return cmd
2220
}
2321

@@ -94,53 +92,3 @@ func newChangeListCmd() *cobra.Command {
9492

9593
return cmd
9694
}
97-
98-
func newChangeTrendCmd() *cobra.Command {
99-
var step, since, until string
100-
101-
cmd := &cobra.Command{
102-
Use: "trend",
103-
Short: "Query change volume trends",
104-
RunE: func(cmd *cobra.Command, args []string) error {
105-
return runCommand(cmd, args, func(ctx *RunContext) error {
106-
startTime, err := timeutil.Parse(since)
107-
if err != nil {
108-
return fmt.Errorf("invalid --since: %w", err)
109-
}
110-
endTime, err := timeutil.Parse(until)
111-
if err != nil {
112-
return fmt.Errorf("invalid --until: %w", err)
113-
}
114-
115-
result, err := ctx.Client.QueryChangeTrend(cmdContext(ctx.Cmd), &flashduty.QueryChangeTrendInput{
116-
Step: step,
117-
StartTime: startTime,
118-
EndTime: endTime,
119-
})
120-
if err != nil {
121-
return err
122-
}
123-
124-
cols := []output.Column{
125-
{Header: "DATE", Field: func(v any) string {
126-
return output.FormatTime(v.(flashduty.ChangeTrendPoint).Timestamp)
127-
}},
128-
{Header: "CHANGES", Field: func(v any) string {
129-
return fmt.Sprintf("%d", v.(flashduty.ChangeTrendPoint).ChangeCount)
130-
}},
131-
{Header: "EVENTS", Field: func(v any) string {
132-
return fmt.Sprintf("%d", v.(flashduty.ChangeTrendPoint).ChangeEventCount)
133-
}},
134-
}
135-
136-
return ctx.PrintTotal(result.DataPoints, cols, len(result.DataPoints))
137-
})
138-
},
139-
}
140-
141-
cmd.Flags().StringVar(&step, "step", "day", "Aggregation: day, week, month")
142-
cmd.Flags().StringVar(&since, "since", "30d", "Start time")
143-
cmd.Flags().StringVar(&until, "until", "now", "End time")
144-
145-
return cmd
146-
}

0 commit comments

Comments
 (0)