Skip to content

Commit b42fcd0

Browse files
authored
fix(cli): insight discovery + team get positional ID (audit fix/cli-insight-team-ids)
fix(cli): insight discovery + team get positional ID (audit fix/cli-insight-team-ids)
2 parents e93ef96 + 18098ae commit b42fcd0

4 files changed

Lines changed: 59 additions & 4 deletions

File tree

internal/cli/incident.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ func newIncidentListCmd() *cobra.Command {
8080
cmd := &cobra.Command{
8181
Use: "list",
8282
Short: "List incidents",
83-
Long: curatedLong("List incidents matching the given filters. The --since/--until window must be < 31 days; --limit max is 100.", "Incidents", "List"),
83+
Long: curatedLong("List incidents matching the given filters. The --since/--until window must be < 31 days; --limit max is 100.\n\nSee also: fduty insight <team|responder|channel> for aggregated metrics (MTTA, MTTR, noise reduction) instead of paginating raw incidents.", "Incidents", "List"),
8484
RunE: func(cmd *cobra.Command, args []string) error {
8585
return runCommand(cmd, args, func(ctx *RunContext) error {
8686
startTime, err := timeutil.Parse(since)

internal/cli/insight.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
func newInsightCmd() *cobra.Command {
1414
cmd := &cobra.Command{
1515
Use: "insight",
16-
Short: "Query insight metrics",
16+
Short: "Query aggregated incident metrics by team, responder, or channel",
1717
}
1818
// insight team/channel/responder are now served by the generated commands
1919
// (richer flag set: severities, *_ids, fields, aggregate-unit, …; relative

internal/cli/team.go

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,23 +92,40 @@ func newTeamGetCmd() *cobra.Command {
9292
)
9393

9494
cmd := &cobra.Command{
95-
Use: "get",
95+
Use: "get [<id>]",
9696
Short: "Get team detail",
9797
Long: curatedLong(`Get detailed information about a specific team.
9898
99-
Specify the team by exactly one of: --id, --name, or --ref-id.
99+
Specify the team by positional ID, or by exactly one of: --id, --name, or --ref-id.
100100
The output includes team metadata, member list, and audit information.
101101
102102
Examples:
103+
flashduty team get 123
103104
flashduty team get --id 123
104105
flashduty team get --name "SRE Team"
105106
flashduty team get --ref-id "hr-dept-42"
106107
flashduty team get --id 123 --json`, "Teams", "ReadInfo"),
108+
Args: optionalArg("id"),
107109
PreRunE: func(cmd *cobra.Command, args []string) error {
110+
if len(args) == 1 {
111+
for _, f := range []string{"id", "name", "ref-id"} {
112+
if cmd.Flags().Changed(f) {
113+
return fmt.Errorf("positional <id> cannot be combined with --%s", f)
114+
}
115+
}
116+
return nil
117+
}
108118
return requireExactlyOneFlag(cmd, "id", "name", "ref-id")
109119
},
110120
RunE: func(cmd *cobra.Command, args []string) error {
111121
return runCommand(cmd, args, func(ctx *RunContext) error {
122+
if len(ctx.Args) == 1 {
123+
id, err := strconv.ParseInt(ctx.Args[0], 10, 64)
124+
if err != nil {
125+
return fmt.Errorf("invalid team id %q: must be a number", ctx.Args[0])
126+
}
127+
teamID = id
128+
}
112129
team, _, err := ctx.Client.Teams.ReadInfo(cmdContext(ctx.Cmd), &flashduty.TeamInfoRequest{
113130
TeamID: uint64(teamID),
114131
TeamName: teamName,

internal/cli/team_get_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package cli
2+
3+
import "testing"
4+
5+
func TestTeamGetAcceptsPositionalID(t *testing.T) {
6+
cmd := newTeamGetCmd()
7+
// MaximumNArgs(1): two positional args should be rejected
8+
if cmd.Args != nil {
9+
if err := cmd.Args(cmd, []string{"123456", "789"}); err == nil {
10+
t.Fatal("team get should reject two positional args")
11+
}
12+
}
13+
}
14+
15+
func TestTeamGetNoArgNoFlagFails(t *testing.T) {
16+
cmd := newTeamGetCmd()
17+
err := cmd.PreRunE(cmd, []string{})
18+
if err == nil {
19+
t.Fatal("expected error when no positional arg and no flag provided")
20+
}
21+
}
22+
23+
func TestTeamGetPositionalArgBypassesPreRunE(t *testing.T) {
24+
cmd := newTeamGetCmd()
25+
err := cmd.PreRunE(cmd, []string{"123456"})
26+
if err != nil {
27+
t.Fatalf("PreRunE should succeed with positional arg, got: %v", err)
28+
}
29+
}
30+
31+
func TestTeamGetPositionalAndFlagConflictFails(t *testing.T) {
32+
cmd := newTeamGetCmd()
33+
_ = cmd.Flags().Set("id", "456")
34+
err := cmd.PreRunE(cmd, []string{"123"})
35+
if err == nil {
36+
t.Fatal("expected error when positional arg and --id flag are both provided")
37+
}
38+
}

0 commit comments

Comments
 (0)