diff --git a/analytics.go b/analytics.go index 95cb70c2..8cd13226 100644 --- a/analytics.go +++ b/analytics.go @@ -4,18 +4,24 @@ import ( "context" ) +// AnalyticsRequest represents the request to be sent to PagerDuty when you want +// aggregated analytics. type AnalyticsRequest struct { - AnalyticsFilter *AnalyticsFilter `json:"filters,omitempty"` - AggregateUnit string `json:"aggregate_unit,omitempty"` - TimeZone string `json:"time_zone,omitempty"` + Filters *AnalyticsFilter `json:"filters,omitempty"` + AggregateUnit string `json:"aggregate_unit,omitempty"` + TimeZone string `json:"time_zone,omitempty"` } + +// AnalyticsResponse represents the response from the PagerDuty API. type AnalyticsResponse struct { - Data []AnalyticsData `json:"data,omitempty"` - AnalyticsFilter *AnalyticsFilter `json:"filters,omitempty"` - AggregateUnit string `json:"aggregate_unit,omitempty"` - TimeZone string `json:"time_zone,omitempty"` + Data []AnalyticsData `json:"data,omitempty"` + Filters *AnalyticsFilter `json:"filters,omitempty"` + AggregateUnit string `json:"aggregate_unit,omitempty"` + TimeZone string `json:"time_zone,omitempty"` } +// AnalyticsFilter represents the set of filters as part of the request to PagerDuty when +// requesting analytics. type AnalyticsFilter struct { CreatedAtStart string `json:"created_at_start,omitempty"` CreatedAtEnd string `json:"created_at_end,omitempty"` @@ -24,42 +30,87 @@ type AnalyticsFilter struct { ServiceIDs []string `json:"service_ids,omitempty"` TeamIDs []string `json:"team_ids,omitempty"` PriorityIDs []string `json:"priority_ids,omitempty"` - PriorityName []string `json:"priority_name,omitempty"` + PriorityNames []string `json:"priority_names,omitempty"` } +// AnalyticsData represents the structure of the analytics we have available. type AnalyticsData struct { - ServiceID string `json:"service_id,omitempty"` - ServiceName string `json:"service_name,omitempty"` - TeamID string `json:"team_id,omitempty"` - TeamName string `json:"team_name,omitempty"` - MeanSecondsToResolve int `json:"mean_seconds_to_resolve,omitempty"` - MeanSecondsToFirstAck int `json:"mean_seconds_to_first_ack,omitempty"` - MeanSecondsToEngage int `json:"mean_seconds_to_engage,omitempty"` - MeanSecondsToMobilize int `json:"mean_seconds_to_mobilize,omitempty"` - MeanEngagedSeconds int `json:"mean_engaged_seconds,omitempty"` - MeanEngagedUserCount int `json:"mean_engaged_user_count,omitempty"` - TotalEscalationCount int `json:"total_escalation_count,omitempty"` - MeanAssignmentCount int `json:"mean_assignment_count,omitempty"` - TotalBusinessHourInterruptions int `json:"total_business_hour_interruptions,omitempty"` - TotalSleepHourInterruptions int `json:"total_sleep_hour_interruptions,omitempty"` - TotalOffHourInterruptions int `json:"total_off_hour_interruptions,omitempty"` - TotalSnoozedSeconds int `json:"total_snoozed_seconds,omitempty"` - TotalEngagedSeconds int `json:"total_engaged_seconds,omitempty"` - TotalIncidentCount int `json:"total_incident_count,omitempty"` - UpTimePct int `json:"up_time_pct,omitempty"` - UserDefinedEffortSeconds int `json:"user_defined_effort_seconds,omitempty"` - RangeStart string `json:"range_start,omitempty"` + ServiceID string `json:"service_id,omitempty"` + ServiceName string `json:"service_name,omitempty"` + TeamID string `json:"team_id,omitempty"` + TeamName string `json:"team_name,omitempty"` + MeanSecondsToResolve int `json:"mean_seconds_to_resolve,omitempty"` + MeanSecondsToFirstAck int `json:"mean_seconds_to_first_ack,omitempty"` + MeanSecondsToEngage int `json:"mean_seconds_to_engage,omitempty"` + MeanSecondsToMobilize int `json:"mean_seconds_to_mobilize,omitempty"` + MeanEngagedSeconds int `json:"mean_engaged_seconds,omitempty"` + MeanEngagedUserCount int `json:"mean_engaged_user_count,omitempty"` + TotalEscalationCount int `json:"total_escalation_count,omitempty"` + MeanAssignmentCount int `json:"mean_assignment_count,omitempty"` + TotalBusinessHourInterruptions int `json:"total_business_hour_interruptions,omitempty"` + TotalSleepHourInterruptions int `json:"total_sleep_hour_interruptions,omitempty"` + TotalOffHourInterruptions int `json:"total_off_hour_interruptions,omitempty"` + TotalSnoozedSeconds int `json:"total_snoozed_seconds,omitempty"` + TotalEngagedSeconds int `json:"total_engaged_seconds,omitempty"` + TotalIncidentCount int `json:"total_incident_count,omitempty"` + UpTimePct float32 `json:"up_time_pct,omitempty"` + UserDefinedEffortSeconds int `json:"user_defined_effort_seconds,omitempty"` + RangeStart string `json:"range_start,omitempty"` } +// GetAggregatedIncidentData gets the aggregated incident analytics for the requested data. func (c *Client) GetAggregatedIncidentData(ctx context.Context, analytics AnalyticsRequest) (AnalyticsResponse, error) { + h := map[string]string{ + "X-EARLY-ACCESS": "analytics-v2", + } + + resp, err := c.post(ctx, "/analytics/metrics/incidents/all", analytics, h) + if err != nil { + return AnalyticsResponse{}, err + } + var analyticsResponse AnalyticsResponse - headers := make(map[string]string) - headers["X-EARLY-ACCESS"] = "analytics-v2" + if err = c.decodeJSON(resp, &analyticsResponse); err != nil { + return AnalyticsResponse{}, err + } - resp, err := c.post(ctx, "/analytics/metrics/incidents/all", analytics, headers) + return analyticsResponse, nil +} + +// GetAggregatedServiceData gets the aggregated service analytics for the requested data. +func (c *Client) GetAggregatedServiceData(ctx context.Context, analytics AnalyticsRequest) (AnalyticsResponse, error) { + h := map[string]string{ + "X-EARLY-ACCESS": "analytics-v2", + } + + resp, err := c.post(ctx, "/analytics/metrics/incidents/services", analytics, h) if err != nil { - return analyticsResponse, err + return AnalyticsResponse{}, err } - err = c.decodeJSON(resp, &analyticsResponse) - return analyticsResponse, err + + var analyticsResponse AnalyticsResponse + if err = c.decodeJSON(resp, &analyticsResponse); err != nil { + return AnalyticsResponse{}, err + } + + return analyticsResponse, nil +} + +// GetAggregatedTeamData gets the aggregated team analytics for the requested data. +func (c *Client) GetAggregatedTeamData(ctx context.Context, analytics AnalyticsRequest) (AnalyticsResponse, error) { + h := map[string]string{ + "X-EARLY-ACCESS": "analytics-v2", + } + + resp, err := c.post(ctx, "/analytics/metrics/incidents/teams", analytics, h) + if err != nil { + return AnalyticsResponse{}, err + } + + var analyticsResponse AnalyticsResponse + if err = c.decodeJSON(resp, &analyticsResponse); err != nil { + return AnalyticsResponse{}, err + } + + return analyticsResponse, nil } diff --git a/analytics_test.go b/analytics_test.go index 18162113..e19959da 100644 --- a/analytics_test.go +++ b/analytics_test.go @@ -12,7 +12,7 @@ func TestAnalytics_GetAggregatedIncidentData(t *testing.T) { defer teardown() analyticsRequest := AnalyticsRequest{ - AnalyticsFilter: &AnalyticsFilter{ + Filters: &AnalyticsFilter{ CreatedAtStart: "2021-01-01T15:00:32Z", CreatedAtEnd: "2021-01-08T15:00:32Z", TeamIDs: []string{"PCDYDX0"}, @@ -23,11 +23,12 @@ func TestAnalytics_GetAggregatedIncidentData(t *testing.T) { analyticsDataWanted := AnalyticsData{MeanSecondsToResolve: 34550, MeanSecondsToFirstAck: 70, MeanEngagedSeconds: 502, MeanAssignmentCount: 1, TotalBusinessHourInterruptions: 1, TotalEngagedSeconds: 2514, TotalIncidentCount: 5, RangeStart: "2021-01-06T00:00:00.000000"} analyticsFilterWanted := AnalyticsFilter{CreatedAtStart: "2021-01-06T09:21:41Z", CreatedAtEnd: "2021-01-13T09:21:41Z", TeamIDs: []string{"PCDYDX0"}} analyticsResponse := AnalyticsResponse{ - Data: []AnalyticsData{analyticsDataWanted}, - AnalyticsFilter: &analyticsFilterWanted, - AggregateUnit: "day", - TimeZone: "Etc/UTC", + Data: []AnalyticsData{analyticsDataWanted}, + Filters: &analyticsFilterWanted, + AggregateUnit: "day", + TimeZone: "Etc/UTC", } + bytesAnalyticsResponse, err := json.Marshal(analyticsResponse) mux.HandleFunc("/analytics/metrics/incidents/all", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "POST") @@ -41,10 +42,100 @@ func TestAnalytics_GetAggregatedIncidentData(t *testing.T) { res, err := client.GetAggregatedIncidentData(context.Background(), analyticsRequest) want := AnalyticsResponse{ - Data: []AnalyticsData{analyticsDataWanted}, - AnalyticsFilter: &analyticsFilterWanted, - AggregateUnit: "day", - TimeZone: "Etc/UTC", + Data: []AnalyticsData{analyticsDataWanted}, + Filters: &analyticsFilterWanted, + AggregateUnit: "day", + TimeZone: "Etc/UTC", + } + if err != nil { + t.Fatal(err) + } + testEqual(t, want, res) +} + +func TestAnalytics_GetAggregatedServiceData(t *testing.T) { + setup() + defer teardown() + + analyticsRequest := AnalyticsRequest{ + Filters: &AnalyticsFilter{ + CreatedAtStart: "2021-01-01T15:00:32Z", + CreatedAtEnd: "2021-01-08T15:00:32Z", + TeamIDs: []string{"PCDYDX0"}, + }, + AggregateUnit: "day", + TimeZone: "Etc/UTC", + } + analyticsDataWanted := AnalyticsData{MeanAssignmentCount: 1, MeanEngagedSeconds: 502, MeanEngagedUserCount: 0, MeanSecondsToResolve: 34550, MeanSecondsToFirstAck: 70, TotalBusinessHourInterruptions: 1, TotalEngagedSeconds: 2514, TotalIncidentCount: 5, RangeStart: "2021-01-06T00:00:00.000000", ServiceID: "PSEJLIN", ServiceName: "FooAlerts", TeamID: "PCDYDX0", TeamName: "FooTeam", UpTimePct: 89.86111111111111} + analyticsFilterWanted := AnalyticsFilter{CreatedAtStart: "2021-01-06T09:21:41Z", CreatedAtEnd: "2021-01-13T09:21:41Z", TeamIDs: []string{"PCDYDX0"}} + analyticsResponse := AnalyticsResponse{ + Data: []AnalyticsData{analyticsDataWanted}, + Filters: &analyticsFilterWanted, + AggregateUnit: "day", + TimeZone: "Etc/UTC", + } + bytesAnalyticsResponse, err := json.Marshal(analyticsResponse) + mux.HandleFunc("/analytics/metrics/incidents/services", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + w.Write(bytesAnalyticsResponse) + }) + + client := &Client{apiEndpoint: server.URL, + authToken: "foo", + HTTPClient: defaultHTTPClient, + } + + res, err := client.GetAggregatedServiceData(context.Background(), analyticsRequest) + want := AnalyticsResponse{ + Data: []AnalyticsData{analyticsDataWanted}, + Filters: &analyticsFilterWanted, + AggregateUnit: "day", + TimeZone: "Etc/UTC", + } + if err != nil { + t.Fatal(err) + } + testEqual(t, want, res) +} + +func TestAnalytics_GetAggregatedTeamData(t *testing.T) { + setup() + defer teardown() + + analyticsRequest := AnalyticsRequest{ + Filters: &AnalyticsFilter{ + CreatedAtStart: "2021-01-01T15:00:32Z", + CreatedAtEnd: "2021-01-08T15:00:32Z", + TeamIDs: []string{"PCDYDX0"}, + }, + AggregateUnit: "day", + TimeZone: "Etc/UTC", + } + analyticsDataWanted := AnalyticsData{MeanAssignmentCount: 1, MeanEngagedSeconds: 502, MeanEngagedUserCount: 0, MeanSecondsToResolve: 34550, MeanSecondsToFirstAck: 70, TotalBusinessHourInterruptions: 1, TotalEngagedSeconds: 2514, TotalIncidentCount: 5, RangeStart: "2021-01-06T00:00:00.000000", TeamID: "PCDYDX0", TeamName: "FooTeam", UpTimePct: 89.86111111111111} + analyticsFilterWanted := AnalyticsFilter{CreatedAtStart: "2021-01-06T09:21:41Z", CreatedAtEnd: "2021-01-13T09:21:41Z", TeamIDs: []string{"PCDYDX0"}} + analyticsResponse := AnalyticsResponse{ + Data: []AnalyticsData{analyticsDataWanted}, + Filters: &analyticsFilterWanted, + AggregateUnit: "day", + TimeZone: "Etc/UTC", + } + bytesAnalyticsResponse, err := json.Marshal(analyticsResponse) + mux.HandleFunc("/analytics/metrics/incidents/teams", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + w.Write(bytesAnalyticsResponse) + }) + + client := &Client{apiEndpoint: server.URL, + authToken: "foo", + HTTPClient: defaultHTTPClient, + } + + res, err := client.GetAggregatedTeamData(context.Background(), analyticsRequest) + want := AnalyticsResponse{ + Data: []AnalyticsData{analyticsDataWanted}, + Filters: &analyticsFilterWanted, + AggregateUnit: "day", + TimeZone: "Etc/UTC", } if err != nil { t.Fatal(err) diff --git a/command/analytics_show.go b/command/analytics_incident_show.go similarity index 79% rename from command/analytics_show.go rename to command/analytics_incident_show.go index 6170fea5..4df116fc 100644 --- a/command/analytics_show.go +++ b/command/analytics_incident_show.go @@ -4,24 +4,27 @@ import ( "context" "encoding/json" "fmt" + "strings" + "time" + "github.com/PagerDuty/go-pagerduty" "github.com/mitchellh/cli" log "github.com/sirupsen/logrus" - "strings" - "time" ) type AnalyticsShow struct { Meta } +// AnalyticsGetAggregatedIncidentDataCommand gets the aggregated incident analytics for the requested data. func AnalyticsGetAggregatedIncidentDataCommand() (cli.Command, error) { return &AnalyticsShow{}, nil } +// Help displays information on how to use the analytics incident cli command. func (c *AnalyticsShow) Help() string { helpText := ` - analytics show + analytics incident show Options: @@ -33,12 +36,14 @@ func (c *AnalyticsShow) Help() string { return strings.TrimSpace(helpText) } +// Synopsis returns a synopsis of the analytics incident cli command. func (c *AnalyticsShow) Synopsis() string { - return "Get analytics aggregated incident data" + return "Get aggregated incident data analytics" } +//Run executes analytics cli command and displays incident analytics for the requested data. func (c *AnalyticsShow) Run(args []string) int { - flags := c.Meta.FlagSet("analytics show") + flags := c.Meta.FlagSet("analytics incident show") flags.Usage = func() { fmt.Println(c.Help()) } servID := flags.String("service_id", "", "Service ID") now := time.Now() @@ -48,7 +53,6 @@ func (c *AnalyticsShow) Run(args []string) int { urgency := flags.String("urgency", "", "high|low") teamID := flags.String("team_id", "", "team ID") - //log.SetLevel(log.DebugLevel) if err := flags.Parse(args); err != nil { log.Errorln(err) return -1 @@ -84,9 +88,9 @@ func (c *AnalyticsShow) Run(args []string) int { } analytics := pagerduty.AnalyticsRequest{ - AnalyticsFilter: &analyticsFilter, - AggregateUnit: "day", - TimeZone: "Etc/UTC", + Filters: &analyticsFilter, + AggregateUnit: "day", + TimeZone: "Etc/UTC", } aggregatedIncidentData, err := client.GetAggregatedIncidentData(context.Background(), analytics) diff --git a/command/analytics_service_show.go b/command/analytics_service_show.go new file mode 100644 index 00000000..8cf46a41 --- /dev/null +++ b/command/analytics_service_show.go @@ -0,0 +1,107 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "strings" + "time" + + "github.com/PagerDuty/go-pagerduty" + "github.com/mitchellh/cli" + log "github.com/sirupsen/logrus" +) + +type AnalyticsServiceShow struct { + Meta +} + +// AnalyticsGetAggregatedServiceDataCommand gets the aggregated service analytics for the requested data. +func AnalyticsGetAggregatedServiceDataCommand() (cli.Command, error) { + return &AnalyticsServiceShow{}, nil +} + +// Help displays information on how to use the analytics service cli command. +func (c *AnalyticsServiceShow) Help() string { + helpText := ` + analytics service show + Options: + -(service_id|team_id) #MANDATORY provide service or team id to stats on. + -start #Optional RFC3339 format default : 7 days ago + -end #Optional RFC3339 format default : now + -urgency #Optional (high|low) + ` + c.Meta.Help() + return strings.TrimSpace(helpText) +} + +// Synopsis returns a synopsis of the analytics service cli command. +func (c *AnalyticsServiceShow) Synopsis() string { + return "Get aggregated service data analytics" +} + +//Run executes analytics cli command and displays service analytics for the requested data. +func (c *AnalyticsServiceShow) Run(args []string) int { + flags := c.Meta.FlagSet("analytics service show") + flags.Usage = func() { fmt.Println(c.Help()) } + servID := flags.String("service_id", "", "Service ID") + now := time.Now() + sevenDaysAgo := now.Add(time.Duration(-24*7) * time.Hour) + start := flags.String("start", sevenDaysAgo.Format(time.RFC3339), "start date") + end := flags.String("end", now.Format(time.RFC3339), "end date") + urgency := flags.String("urgency", "", "high|low") + teamID := flags.String("team_id", "", "team ID") + + if err := flags.Parse(args); err != nil { + log.Errorln(err) + return -1 + } + + if err := c.Meta.Setup(); err != nil { + log.Error(err) + return -1 + } + + client := c.Meta.Client() + + serviceIds := make([]string, 1) + if *servID == "" { + serviceIds = nil + } else { + serviceIds[0] = *servID + } + + teamIds := make([]string, 1) + if *teamID == "" { + teamIds = nil + } else { + teamIds[0] = *teamID + } + + analyticsFilter := pagerduty.AnalyticsFilter{ + CreatedAtStart: *start, + CreatedAtEnd: *end, + Urgency: *urgency, + ServiceIDs: serviceIds, + TeamIDs: teamIds, + } + + analytics := pagerduty.AnalyticsRequest{ + Filters: &analyticsFilter, + AggregateUnit: "day", + TimeZone: "Etc/UTC", + } + + aggregatedServiceData, err := client.GetAggregatedServiceData(context.Background(), analytics) + if err != nil { + log.Error(err) + return -1 + } + + aggregatedServiceDataBytes, err := json.Marshal(aggregatedServiceData) + if err != nil { + log.Error(err) + return -1 + } + fmt.Println(string(aggregatedServiceDataBytes)) + return 0 +} diff --git a/command/analytics_team_show.go b/command/analytics_team_show.go new file mode 100644 index 00000000..def68950 --- /dev/null +++ b/command/analytics_team_show.go @@ -0,0 +1,107 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "strings" + "time" + + "github.com/PagerDuty/go-pagerduty" + "github.com/mitchellh/cli" + log "github.com/sirupsen/logrus" +) + +type AnalyticsTeamShow struct { + Meta +} + +// AnalyticsGetAggregatedTeamDataCommand gets the aggregated team analytics for the requested data. +func AnalyticsGetAggregatedTeamDataCommand() (cli.Command, error) { + return &AnalyticsServiceShow{}, nil +} + +// Help displays information on how to use the analytics team cli command. +func (c *AnalyticsTeamShow) Help() string { + helpText := ` + analytics team show + Options: + -(service_id|team_id) #MANDATORY provide service or team id to stats on. + -start #Optional RFC3339 format default : 7 days ago + -end #Optional RFC3339 format default : now + -urgency #Optional (high|low) + ` + c.Meta.Help() + return strings.TrimSpace(helpText) +} + +// Synopsis returns a synopsis of the analytics team cli command. +func (c *AnalyticsTeamShow) Synopsis() string { + return "Get aggregated team data analytics" +} + +//Run executes analytics cli command and displays team analytics for the requested data. +func (c *AnalyticsTeamShow) Run(args []string) int { + flags := c.Meta.FlagSet("analytics team show") + flags.Usage = func() { fmt.Println(c.Help()) } + servID := flags.String("service_id", "", "Service ID") + now := time.Now() + sevenDaysAgo := now.Add(time.Duration(-24*7) * time.Hour) + start := flags.String("start", sevenDaysAgo.Format(time.RFC3339), "start date") + end := flags.String("end", now.Format(time.RFC3339), "end date") + urgency := flags.String("urgency", "", "high|low") + teamID := flags.String("team_id", "", "team ID") + + if err := flags.Parse(args); err != nil { + log.Errorln(err) + return -1 + } + + if err := c.Meta.Setup(); err != nil { + log.Error(err) + return -1 + } + + client := c.Meta.Client() + + serviceIds := make([]string, 1) + if *servID == "" { + serviceIds = nil + } else { + serviceIds[0] = *servID + } + + teamIds := make([]string, 1) + if *teamID == "" { + teamIds = nil + } else { + teamIds[0] = *teamID + } + + analyticsFilter := pagerduty.AnalyticsFilter{ + CreatedAtStart: *start, + CreatedAtEnd: *end, + Urgency: *urgency, + ServiceIDs: serviceIds, + TeamIDs: teamIds, + } + + analytics := pagerduty.AnalyticsRequest{ + Filters: &analyticsFilter, + AggregateUnit: "day", + TimeZone: "Etc/UTC", + } + + aggregatedTeamData, err := client.GetAggregatedTeamData(context.Background(), analytics) + if err != nil { + log.Error(err) + return -1 + } + + aggregatedTeamDataBytes, err := json.Marshal(aggregatedTeamData) + if err != nil { + log.Error(err) + return -1 + } + fmt.Println(string(aggregatedTeamDataBytes)) + return 0 +} diff --git a/command/main.go b/command/main.go index 6d8c57f0..27be30ad 100644 --- a/command/main.go +++ b/command/main.go @@ -101,9 +101,11 @@ func loadCommands() map[string]cli.CommandFactory { "user notification-rule show": UserNotificationRuleShowCommand, "user notification-rule update": UserNotificationRuleUpdateCommand, - "vendor list": VendorListCommand, - "vendor show": VendorShowCommand, - "analyticsAggregatedIncidentData show": AnalyticsGetAggregatedIncidentDataCommand, + "vendor list": VendorListCommand, + "vendor show": VendorShowCommand, + "analytics incident show": AnalyticsGetAggregatedIncidentDataCommand, + "analytics service show": AnalyticsGetAggregatedServiceDataCommand, + "analytics team show": AnalyticsGetAggregatedTeamDataCommand, } }