From e6a9f80647dabe5647d052d3baf34f010dd942cc Mon Sep 17 00:00:00 2001 From: Florian Dehau Date: Wed, 11 Feb 2026 09:42:08 +0100 Subject: [PATCH] Implement error-tracking issues search and get commands ## What ? Add `error-tracking issues search` and `error-tracking issues get` commands using `datadogV2.ErrorTrackingApi`. The `search` command supports `--query`, `--from`, `--to`, `--order-by`, and `--limit` flags. The `get` command fetches a single issue with related data (assignee, case, team owners) via include parameters. ## Why ? Add support for Error Tracking in pup. --- README.md | 2 +- cmd/error_tracking.go | 172 ++++++++++++++++++++++++++++++------- cmd/error_tracking_test.go | 47 ++++++++-- docs/COMMANDS.md | 4 +- 4 files changed, 182 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 6e32118c..4dcefa48 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,7 @@ See [docs/COMMANDS.md](docs/COMMANDS.md) for detailed command reference. | Incidents | ✅ | `incidents list`, `incidents get`, `incidents attachments` | Incident management with attachment support | | On-Call (Teams) | ✅ | `on-call teams` (CRUD, memberships with roles) | Full team management system with admin/member roles | | Case Management | ✅ | `cases` (create, search, assign, archive, projects) | Complete case management with priorities P1-P5 | -| Error Tracking | ✅ | `error-tracking issues list`, `error-tracking issues get` | Error issue management | +| Error Tracking | ✅ | `error-tracking issues search`, `error-tracking issues get` | Error issue search and details | | Service Catalog | ✅ | `service-catalog list`, `service-catalog get` | Service registry management | | Scorecards | ✅ | `scorecards list`, `scorecards get` | Service quality scores | | Incident Services/Teams | ❌ | - | Not yet implemented | diff --git a/cmd/error_tracking.go b/cmd/error_tracking.go index ed68fe79..430aa049 100644 --- a/cmd/error_tracking.go +++ b/cmd/error_tracking.go @@ -6,9 +6,9 @@ package cmd import ( - "fmt" - + "github.com/DataDog/datadog-api-client-go/v2/api/datadogV2" "github.com/DataDog/pup/pkg/formatter" + "github.com/DataDog/pup/pkg/util" "github.com/spf13/cobra" ) @@ -21,14 +21,12 @@ Error tracking automatically groups and prioritizes errors from your applications to help you identify and fix critical issues. CAPABILITIES: - • List error issues - • Get error details - • View error trends - • Manage error status + • Search error issues with filtering and sorting + • Get detailed information about a specific issue EXAMPLES: - # List error issues - pup error-tracking issues list + # Search error issues + pup error-tracking issues search # Get issue details pup error-tracking issues get issue-id @@ -42,56 +40,168 @@ var errorTrackingIssuesCmd = &cobra.Command{ Short: "Manage error issues", } -var errorTrackingIssuesListCmd = &cobra.Command{ - Use: "list", - Short: "List error issues", - RunE: runErrorTrackingIssuesList, +var errorTrackingIssuesSearchCmd = &cobra.Command{ + Use: "search", + Short: "Search error issues", + Long: `Search error tracking issues with optional filtering. + +Search for error tracking issues across your applications. Results are sorted +by the specified order (default: total count). + +FLAGS: + --query Search query to filter issues (default: "*") + --from Start time: relative (e.g., "1h", "1d", "7d") or absolute + (ISO 8601, Unix timestamp) (default: "1d") + --to End time: "now", relative, or absolute (default: "now") + --order-by Sort order: TOTAL_COUNT, FIRST_SEEN, IMPACTED_SESSIONS, + PRIORITY (default: "TOTAL_COUNT") + --limit Maximum number of issues to return (default: 10) + +EXAMPLES: + # Search all issues from the last 24 hours + pup error-tracking issues search + + # Search for specific errors + pup error-tracking issues search --query="NullPointerException" + + # Search issues from the last 7 days + pup error-tracking issues search --from=7d + + # Sort by most recently seen + pup error-tracking issues search --order-by=FIRST_SEEN + + # Combine filters + pup error-tracking issues search --query="timeout" --from=3d --order-by=PRIORITY`, + RunE: runErrorTrackingIssuesSearch, } var errorTrackingIssuesGetCmd = &cobra.Command{ Use: "get [issue-id]", Short: "Get issue details", - Args: cobra.ExactArgs(1), - RunE: runErrorTrackingIssuesGet, + Long: `Get detailed information about a specific error tracking issue. + +Retrieves full details for an issue including assignee, case, and team owners. + +ARGUMENTS: + issue-id The ID of the error tracking issue + +EXAMPLES: + # Get issue details + pup error-tracking issues get abc123 + + # Get issue details as YAML + pup error-tracking issues get abc123 --output=yaml`, + Args: cobra.ExactArgs(1), + RunE: runErrorTrackingIssuesGet, } +var ( + etQuery string + etFrom string + etTo string + etOrderBy string + etLimit int +) + func init() { - errorTrackingIssuesCmd.AddCommand(errorTrackingIssuesListCmd, errorTrackingIssuesGetCmd) + errorTrackingIssuesSearchCmd.Flags().StringVar(&etQuery, "query", "*", "Search query to filter issues") + errorTrackingIssuesSearchCmd.Flags().StringVar(&etFrom, "from", "1d", "Start time (relative or absolute)") + errorTrackingIssuesSearchCmd.Flags().StringVar(&etTo, "to", "now", "End time (relative or absolute)") + errorTrackingIssuesSearchCmd.Flags().StringVar(&etOrderBy, "order-by", "TOTAL_COUNT", "Sort order: TOTAL_COUNT, FIRST_SEEN, IMPACTED_SESSIONS, PRIORITY") + errorTrackingIssuesSearchCmd.Flags().IntVar(&etLimit, "limit", 10, "Maximum number of issues to return") + + errorTrackingIssuesCmd.AddCommand(errorTrackingIssuesSearchCmd, errorTrackingIssuesGetCmd) errorTrackingCmd.AddCommand(errorTrackingIssuesCmd) } -func runErrorTrackingIssuesList(cmd *cobra.Command, args []string) error { - result := map[string]interface{}{ - "data": []map[string]interface{}{}, - "meta": map[string]interface{}{ - "message": "Error tracking list - API endpoint implementation pending", +func runErrorTrackingIssuesSearch(cmd *cobra.Command, args []string) error { + client, err := getClient() + if err != nil { + return err + } + + fromMs, err := util.ParseTimeToUnixMilli(etFrom) + if err != nil { + return err + } + + toMs, err := util.ParseTimeToUnixMilli(etTo) + if err != nil { + return err + } + + api := datadogV2.NewErrorTrackingApi(client.V2()) + + orderBy := datadogV2.IssuesSearchRequestDataAttributesOrderBy(etOrderBy) + body := datadogV2.IssuesSearchRequest{ + Data: datadogV2.IssuesSearchRequestData{ + Attributes: datadogV2.IssuesSearchRequestDataAttributes{ + From: fromMs, + To: toMs, + Query: etQuery, + OrderBy: &orderBy, + Persona: datadogV2.ISSUESSEARCHREQUESTDATAATTRIBUTESPERSONA_ALL.Ptr(), + }, + Type: datadogV2.ISSUESSEARCHREQUESTDATATYPE_SEARCH_REQUEST, }, } - output, err := formatter.FormatOutput(result, formatter.OutputFormat(outputFormat)) + opts := *datadogV2.NewSearchIssuesOptionalParameters().WithInclude( + []datadogV2.SearchIssuesIncludeQueryParameterItem{ + datadogV2.SEARCHISSUESINCLUDEQUERYPARAMETERITEM_ISSUE, + }, + ) + + resp, r, err := api.SearchIssues(client.Context(), body, opts) + if err != nil { + return formatAPIError("search error tracking issues", err, r) + } + + if len(resp.Data) == 0 { + printOutput("No error tracking issues found matching the specified criteria.\n") + return nil + } + + if etLimit > 0 && len(resp.Data) > etLimit { + resp.Data = resp.Data[:etLimit] + } + + output, err := formatter.FormatOutput(resp, formatter.OutputFormat(outputFormat)) if err != nil { return err } - fmt.Println(output) + + printOutput("%s\n", output) return nil } func runErrorTrackingIssuesGet(cmd *cobra.Command, args []string) error { + client, err := getClient() + if err != nil { + return err + } + issueID := args[0] - result := map[string]interface{}{ - "data": map[string]interface{}{ - "id": issueID, - "type": "error_tracking_issue", - "attributes": map[string]interface{}{ - "message": "Error tracking details - API endpoint implementation pending", - }, + api := datadogV2.NewErrorTrackingApi(client.V2()) + + opts := *datadogV2.NewGetIssueOptionalParameters().WithInclude( + []datadogV2.GetIssueIncludeQueryParameterItem{ + datadogV2.GETISSUEINCLUDEQUERYPARAMETERITEM_ASSIGNEE, + datadogV2.GETISSUEINCLUDEQUERYPARAMETERITEM_CASE, + datadogV2.GETISSUEINCLUDEQUERYPARAMETERITEM_TEAM_OWNERS, }, + ) + + resp, r, err := api.GetIssue(client.Context(), issueID, opts) + if err != nil { + return formatAPIError("get error tracking issue", err, r) } - output, err := formatter.FormatOutput(result, formatter.OutputFormat(outputFormat)) + output, err := formatter.FormatOutput(resp, formatter.OutputFormat(outputFormat)) if err != nil { return err } - fmt.Println(output) + + printOutput("%s\n", output) return nil } diff --git a/cmd/error_tracking_test.go b/cmd/error_tracking_test.go index 3c303f6c..b7d4b408 100644 --- a/cmd/error_tracking_test.go +++ b/cmd/error_tracking_test.go @@ -63,8 +63,8 @@ func TestErrorTrackingIssuesCmd(t *testing.T) { commandMap[cmd.Use] = true } - if !commandMap["list"] { - t.Error("Missing issues list subcommand") + if !commandMap["search"] { + t.Error("Missing issues search subcommand") } // Check if get command exists @@ -79,24 +79,51 @@ func TestErrorTrackingIssuesCmd(t *testing.T) { } } -func TestErrorTrackingIssuesListCmd(t *testing.T) { - if errorTrackingIssuesListCmd == nil { - t.Fatal("errorTrackingIssuesListCmd is nil") +func TestErrorTrackingIssuesSearchCmd(t *testing.T) { + if errorTrackingIssuesSearchCmd == nil { + t.Fatal("errorTrackingIssuesSearchCmd is nil") } - if errorTrackingIssuesListCmd.Use != "list" { - t.Errorf("Use = %s, want list", errorTrackingIssuesListCmd.Use) + if errorTrackingIssuesSearchCmd.Use != "search" { + t.Errorf("Use = %s, want search", errorTrackingIssuesSearchCmd.Use) } - if errorTrackingIssuesListCmd.Short == "" { + if errorTrackingIssuesSearchCmd.Short == "" { t.Error("Short description is empty") } - if errorTrackingIssuesListCmd.RunE == nil { + if errorTrackingIssuesSearchCmd.RunE == nil { t.Error("RunE is nil") } } +func TestErrorTrackingIssuesSearchCmd_Flags(t *testing.T) { + flags := errorTrackingIssuesSearchCmd.Flags() + + tests := []struct { + name string + defaultValue string + }{ + {"query", "*"}, + {"from", "1d"}, + {"to", "now"}, + {"order-by", "TOTAL_COUNT"}, + {"limit", "10"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f := flags.Lookup(tt.name) + if f == nil { + t.Fatalf("Missing --%s flag", tt.name) + } + if f.DefValue != tt.defaultValue { + t.Errorf("--%s default = %q, want %q", tt.name, f.DefValue, tt.defaultValue) + } + }) + } +} + func TestErrorTrackingIssuesGetCmd(t *testing.T) { if errorTrackingIssuesGetCmd == nil { t.Fatal("errorTrackingIssuesGetCmd is nil") @@ -143,3 +170,5 @@ func TestErrorTrackingCmd_CommandHierarchy(t *testing.T) { } } } + + diff --git a/docs/COMMANDS.md b/docs/COMMANDS.md index ce33daea..5866d4b8 100644 --- a/docs/COMMANDS.md +++ b/docs/COMMANDS.md @@ -44,7 +44,7 @@ pup [options] # Nested commands | security | rules, signals, findings (search) | cmd/security.go | ✅ | | organizations | get, list | cmd/organizations.go | ✅ | | service-catalog | list, get | cmd/service_catalog.go | ✅ | -| error-tracking | issues (list, get) | cmd/error_tracking.go | ✅ | +| error-tracking | issues (search, get) | cmd/error_tracking.go | ✅ | | scorecards | list, get | cmd/scorecards.go | ✅ | | usage | summary, hourly | cmd/usage.go | ✅ | | apm | services (list, stats, operations, resources), entities (list), dependencies (list), flow-map | cmd/apm.go | ✅ | @@ -135,7 +135,7 @@ pup infrastructure hosts list ### Development & Quality - **cicd** - CI/CD visibility (pipelines, events) -- **error-tracking** - Error management (issues list, issues get) +- **error-tracking** - Error management (issues search, issues get) - **scorecards** - Service quality (list, get) - **service-catalog** - Service registry (list, get)