Skip to content

Commit e6a8fa2

Browse files
authored
Merge pull request #51 from flashcatcloud/feat/cli-drop-idverb-shadows
feat(cli): drop transparent curated ID-verb shadows (parity via #50)
2 parents 6848243 + 0e23f9c commit e6a8fa2

7 files changed

Lines changed: 25 additions & 167 deletions

File tree

e2e/edge_case_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,10 +134,11 @@ func TestJSONOnAckCommand(t *testing.T) {
134134
id := extractIncidentID(t, r.Stdout)
135135
t.Cleanup(func() { runCLI(t, "incident", "close", id) })
136136

137+
// ack is served by the generated twin; --json wraps the OK line as {"message":"..."}.
137138
r = runCLI(t, "incident", "ack", id, "--json")
138139
requireSuccess(t, r)
139140
requireValidJSON(t, r.Stdout)
140-
requireContains(t, r.Stdout, "Acknowledged")
141+
requireContains(t, r.Stdout, "OK: POST /incident/ack")
141142
}
142143

143144
// Test 307: --json on close command

e2e/incident_extended_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,9 +192,10 @@ func TestIncidentAckSingleID(t *testing.T) {
192192
id := extractIncidentID(t, r.Stdout)
193193
t.Cleanup(func() { runCLI(t, "incident", "close", id) })
194194

195+
// Served by the generated twin (positional id → incident_ids).
195196
r = runCLI(t, "incident", "ack", id)
196197
requireSuccess(t, r)
197-
requireContains(t, r.Stdout, "Acknowledged 1 incident(s).")
198+
requireContains(t, r.Stdout, "OK: POST /incident/ack")
198199
}
199200

200201
// Test 204: close single ID

e2e/incident_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,10 @@ func TestIncidentLifecycle(t *testing.T) {
101101
requireContains(t, r.Stdout, "Triggered")
102102
requireContains(t, r.Stdout, name)
103103

104-
// Step 3: Ack
104+
// Step 3: Ack (served by the generated twin; positional id → incident_ids).
105105
r = runCLI(t, "incident", "ack", id)
106106
requireSuccess(t, r)
107-
requireContains(t, r.Stdout, "Acknowledged 1 incident(s).")
107+
requireContains(t, r.Stdout, "OK: POST /incident/ack")
108108

109109
// Step 4: Get - should be Processing
110110
r = runCLI(t, "incident", "get", id)

internal/cli/alert.go

Lines changed: 2 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ func newAlertCmd() *cobra.Command {
2222
cmd.AddCommand(newAlertGetCmd())
2323
cmd.AddCommand(newAlertEventsCmd())
2424
cmd.AddCommand(newAlertTimelineCmd())
25-
cmd.AddCommand(newAlertMergeCmd())
25+
// merge is registered via the generated layer (positional alert-ids fold to
26+
// alert_ids). Flag-name change: --incident (curated) → --incident-id (generated).
2627
return cmd
2728
}
2829

@@ -303,33 +304,3 @@ func resolveAlertFeedOperators(rc *RunContext, items []flashduty.FeedItem) map[i
303304
}
304305
return out
305306
}
306-
307-
func newAlertMergeCmd() *cobra.Command {
308-
var incidentID, comment string
309-
310-
cmd := &cobra.Command{
311-
Use: "merge <alert_id> [<alert_id2> ...]",
312-
Short: "Merge alerts into an incident",
313-
Args: requireArgs("alert_id"),
314-
RunE: func(cmd *cobra.Command, args []string) error {
315-
return runCommand(cmd, args, func(ctx *RunContext) error {
316-
if _, err := ctx.Client.Alerts.WriteMerge(cmdContext(ctx.Cmd), &flashduty.AlertMergeRequest{
317-
AlertIDs: ctx.Args,
318-
IncidentID: incidentID,
319-
Comment: comment,
320-
}); err != nil {
321-
return err
322-
}
323-
324-
ctx.WriteResult(fmt.Sprintf("Merged %d alert(s) into incident %s.", len(ctx.Args), incidentID))
325-
return nil
326-
})
327-
},
328-
}
329-
330-
cmd.Flags().StringVar(&incidentID, "incident", "", "Target incident ID")
331-
cmd.Flags().StringVar(&comment, "comment", "", "Merge comment")
332-
_ = cmd.MarkFlagRequired("incident")
333-
334-
return cmd
335-
}

internal/cli/command_test.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,7 @@ func TestCommandIncidentUnack(t *testing.T) {
430430
saveAndResetGlobals(t)
431431
stub := newGFStub(t)
432432

433+
// unack is served by the generated twin (positional ids → incident_ids).
433434
out, err := execCommand("incident", "unack", "inc-1", "inc-2")
434435
if err != nil {
435436
t.Fatalf("[incident-unack] unexpected error: %v", err)
@@ -440,7 +441,7 @@ func TestCommandIncidentUnack(t *testing.T) {
440441
if got, want := strings.Join(stub.bodyStrings("incident_ids"), ","), "inc-1,inc-2"; got != want {
441442
t.Fatalf("[incident-unack] expected ids %q, got %q", want, got)
442443
}
443-
if !strings.Contains(out, "Unacknowledged 2 incident(s).") {
444+
if !strings.Contains(out, "OK: POST /incident/unack") {
444445
t.Fatalf("[incident-unack] unexpected output:\n%s", out)
445446
}
446447
}
@@ -449,6 +450,7 @@ func TestCommandIncidentWake(t *testing.T) {
449450
saveAndResetGlobals(t)
450451
stub := newGFStub(t)
451452

453+
// wake is served by the generated twin (positional id → incident_ids).
452454
out, err := execCommand("incident", "wake", "inc-1")
453455
if err != nil {
454456
t.Fatalf("[incident-wake] unexpected error: %v", err)
@@ -459,7 +461,7 @@ func TestCommandIncidentWake(t *testing.T) {
459461
if got, want := strings.Join(stub.bodyStrings("incident_ids"), ","), "inc-1"; got != want {
460462
t.Fatalf("[incident-wake] expected ids %q, got %q", want, got)
461463
}
462-
if !strings.Contains(out, "Restored notifications for 1 incident(s).") {
464+
if !strings.Contains(out, "OK: POST /incident/wake") {
463465
t.Fatalf("[incident-wake] unexpected output:\n%s", out)
464466
}
465467
}
@@ -500,13 +502,15 @@ func TestCommandIncidentCommentAllows1024UnicodeRunes(t *testing.T) {
500502
}
501503
}
502504

505+
// TestCommandIncidentLifecycleRejectsMoreThan100IDs covers the curated
506+
// commands that still enforce the 100-id batch cap client-side. unack and wake
507+
// were dropped in favor of their generated twins, which carry no client-side
508+
// cap (the backend enforces the limit), so they are intentionally absent here.
503509
func TestCommandIncidentLifecycleRejectsMoreThan100IDs(t *testing.T) {
504510
commands := []struct {
505511
name string
506512
args []string
507513
}{
508-
{name: "unack", args: []string{"incident", "unack"}},
509-
{name: "wake", args: []string{"incident", "wake"}},
510514
{name: "comment", args: []string{"incident", "comment", "--comment", "too many"}},
511515
{name: "remove", args: []string{"incident", "remove"}},
512516
}
@@ -612,6 +616,7 @@ func TestCommandIncidentDisableMerge(t *testing.T) {
612616
saveAndResetGlobals(t)
613617
stub := newGFStub(t)
614618

619+
// disable-merge is served by the generated twin (positional ids → incident_ids).
615620
out, err := execCommand("incident", "disable-merge", "inc-1", "inc-2")
616621
if err != nil {
617622
t.Fatalf("[incident-disable-merge] unexpected error: %v", err)
@@ -622,7 +627,7 @@ func TestCommandIncidentDisableMerge(t *testing.T) {
622627
if got, want := strings.Join(stub.bodyStrings("incident_ids"), ","), "inc-1,inc-2"; got != want {
623628
t.Fatalf("[incident-disable-merge] expected ids %q, got %q", want, got)
624629
}
625-
if !strings.Contains(out, "Disabled auto-merge for 2 incident(s).") {
630+
if !strings.Contains(out, "OK: POST /incident/disable-merge") {
626631
t.Fatalf("[incident-disable-merge] unexpected output:\n%s", out)
627632
}
628633
}

internal/cli/gen_positional_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ import (
1111
// the variadic marker; an *_id scalar op renders the single id; the override and
1212
// int cases render their pinned field.
1313
func TestGenPositionalUseLine(t *testing.T) {
14-
// (a) and (b) read the generated constructors directly (the curated commands
15-
// own the live `incident ack`/`incident info` path-names, so the generated
16-
// twins are dropped at registration but still constructible for assertion).
14+
// (a) and (b) call the generated constructors directly so the Use-line
15+
// assertions stay independent of registration order. `incident ack` now
16+
// surfaces the generated twin (curated shadow dropped); `incident info` was
17+
// always generated-only (the curated leaf is named `detail`).
1718
ack := genIncidentsAckCmd()
1819
if got := ack.Use; got != "ack <incident-id> [<id2>...]" {
1920
t.Errorf("ack twin Use = %q, want %q", got, "ack <incident-id> [<id2>...]")

internal/cli/incident.go

Lines changed: 3 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,18 @@ func newIncidentCmd() *cobra.Command {
2828
cmd.AddCommand(newIncidentGetCmd())
2929
cmd.AddCommand(newIncidentCreateCmd())
3030
cmd.AddCommand(newIncidentUpdateCmd())
31-
cmd.AddCommand(newIncidentAckCmd())
32-
cmd.AddCommand(newIncidentUnackCmd())
31+
// ack, unack, wake, reopen, and disable-merge are registered via the
32+
// generated layer (positional ids fold to incident_ids; flags are a superset
33+
// of the dropped curated shadows).
3334
cmd.AddCommand(newIncidentCloseCmd())
34-
cmd.AddCommand(newIncidentWakeCmd())
3535
cmd.AddCommand(newIncidentTimelineCmd())
3636
cmd.AddCommand(newIncidentAlertsCmd())
3737
cmd.AddCommand(newIncidentSimilarCmd())
3838
cmd.AddCommand(newIncidentMergeCmd())
3939
cmd.AddCommand(newIncidentSnoozeCmd())
40-
cmd.AddCommand(newIncidentReopenCmd())
4140
cmd.AddCommand(newIncidentReassignCmd())
4241
cmd.AddCommand(newIncidentAddResponderCmd())
4342
cmd.AddCommand(newIncidentCommentCmd())
44-
cmd.AddCommand(newIncidentDisableMergeCmd())
4543
cmd.AddCommand(newIncidentRemoveCmd())
4644
cmd.AddCommand(newIncidentWarRoomCmd())
4745
cmd.AddCommand(newIncidentFeedCmd())
@@ -441,53 +439,6 @@ func newIncidentUpdateCmd() *cobra.Command {
441439
return cmd
442440
}
443441

444-
func newIncidentAckCmd() *cobra.Command {
445-
return &cobra.Command{
446-
Use: "ack <id> [<id2> ...]",
447-
Short: "Acknowledge incidents",
448-
Args: requireArgs("incident_id"),
449-
RunE: func(cmd *cobra.Command, args []string) error {
450-
return runCommand(cmd, args, func(ctx *RunContext) error {
451-
if _, err := ctx.Client.Incidents.Ack(cmdContext(ctx.Cmd), &flashduty.AckIncidentRequest{
452-
IncidentIDs: ctx.Args,
453-
}); err != nil {
454-
return err
455-
}
456-
ctx.WriteResult(fmt.Sprintf("Acknowledged %d incident(s).", len(ctx.Args)))
457-
return nil
458-
})
459-
},
460-
}
461-
}
462-
463-
func newIncidentUnackCmd() *cobra.Command {
464-
return &cobra.Command{
465-
Use: "unack <id> [<id2> ...]",
466-
Short: "Cancel incident acknowledgement",
467-
Long: `Cancel acknowledgement for one or more incidents.
468-
469-
Use this when an incident was acknowledged by mistake and should return to the
470-
unacknowledged state. The command accepts up to 100 incident IDs.`,
471-
Example: ` flashduty incident unack inc_123
472-
flashduty incident unack inc_123 inc_456`,
473-
Args: requireArgs("incident_id"),
474-
RunE: func(cmd *cobra.Command, args []string) error {
475-
if err := validateIncidentIDBatch(args); err != nil {
476-
return err
477-
}
478-
return runCommand(cmd, args, func(ctx *RunContext) error {
479-
if _, err := ctx.Client.Incidents.Unack(cmdContext(ctx.Cmd), &flashduty.UnackIncidentRequest{
480-
IncidentIDs: ctx.Args,
481-
}); err != nil {
482-
return err
483-
}
484-
ctx.WriteResult(fmt.Sprintf("Unacknowledged %d incident(s).", len(ctx.Args)))
485-
return nil
486-
})
487-
},
488-
}
489-
}
490-
491442
func newIncidentCloseCmd() *cobra.Command {
492443
return &cobra.Command{
493444
Use: "close <id> [<id2> ...]",
@@ -507,34 +458,6 @@ func newIncidentCloseCmd() *cobra.Command {
507458
}
508459
}
509460

510-
func newIncidentWakeCmd() *cobra.Command {
511-
return &cobra.Command{
512-
Use: "wake <id> [<id2> ...]",
513-
Short: "Restore notifications for snoozed incidents",
514-
Long: `Wake one or more snoozed incidents.
515-
516-
This cancels snooze and restores normal incident notifications. The command
517-
accepts up to 100 incident IDs.`,
518-
Example: ` flashduty incident wake inc_123
519-
flashduty incident wake inc_123 inc_456`,
520-
Args: requireArgs("incident_id"),
521-
RunE: func(cmd *cobra.Command, args []string) error {
522-
if err := validateIncidentIDBatch(args); err != nil {
523-
return err
524-
}
525-
return runCommand(cmd, args, func(ctx *RunContext) error {
526-
if _, err := ctx.Client.Incidents.Wake(cmdContext(ctx.Cmd), &flashduty.WakeIncidentRequest{
527-
IncidentIDs: ctx.Args,
528-
}); err != nil {
529-
return err
530-
}
531-
ctx.WriteResult(fmt.Sprintf("Restored notifications for %d incident(s).", len(ctx.Args)))
532-
return nil
533-
})
534-
},
535-
}
536-
}
537-
538461
func newIncidentTimelineCmd() *cobra.Command {
539462
return &cobra.Command{
540463
Use: "timeline <id>",
@@ -784,25 +707,6 @@ func newIncidentSnoozeCmd() *cobra.Command {
784707
return cmd
785708
}
786709

787-
func newIncidentReopenCmd() *cobra.Command {
788-
return &cobra.Command{
789-
Use: "reopen <id> [<id2> ...]",
790-
Short: "Reopen closed incidents",
791-
Args: requireArgs("incident_id"),
792-
RunE: func(cmd *cobra.Command, args []string) error {
793-
return runCommand(cmd, args, func(ctx *RunContext) error {
794-
if _, err := ctx.Client.Incidents.Reopen(cmdContext(ctx.Cmd), &flashduty.ReopenIncidentRequest{
795-
IncidentIDs: ctx.Args,
796-
}); err != nil {
797-
return err
798-
}
799-
ctx.WriteResult(fmt.Sprintf("Reopened %d incident(s).", len(ctx.Args)))
800-
return nil
801-
})
802-
},
803-
}
804-
}
805-
806710
func newIncidentReassignCmd() *cobra.Command {
807711
var person string
808712

@@ -952,31 +856,6 @@ webhook reply behavior.`,
952856
return cmd
953857
}
954858

955-
func newIncidentDisableMergeCmd() *cobra.Command {
956-
return &cobra.Command{
957-
Use: "disable-merge <id> [<id2> ...]",
958-
Short: "Disable automatic merging for incidents",
959-
Long: `Disable automatic alert merging for one or more incidents.
960-
961-
Use this when an incident should stay isolated and must not absorb additional
962-
matching alerts automatically. The command accepts up to 100 incident IDs.`,
963-
Example: ` flashduty incident disable-merge inc_123
964-
flashduty incident disable-merge inc_123 inc_456`,
965-
Args: requireArgs("incident_id"),
966-
RunE: func(cmd *cobra.Command, args []string) error {
967-
return runCommand(cmd, args, func(ctx *RunContext) error {
968-
if _, err := ctx.Client.Incidents.DisableMerge(cmdContext(ctx.Cmd), &flashduty.DisableIncidentMergeRequest{
969-
IncidentIDs: ctx.Args,
970-
}); err != nil {
971-
return err
972-
}
973-
ctx.WriteResult(fmt.Sprintf("Disabled auto-merge for %d incident(s).", len(ctx.Args)))
974-
return nil
975-
})
976-
},
977-
}
978-
}
979-
980859
func newIncidentRemoveCmd() *cobra.Command {
981860
var force bool
982861

0 commit comments

Comments
 (0)