Skip to content

Commit

Permalink
Merge pull request #312 from newbootz/add-analytics
Browse files Browse the repository at this point in the history
teams and services analytics endpoints
  • Loading branch information
theckman committed Mar 18, 2021
2 parents 0c26903 + 51b672f commit a9bc8eb
Show file tree
Hide file tree
Showing 6 changed files with 402 additions and 54 deletions.
103 changes: 70 additions & 33 deletions analytics.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,21 @@ import (
// 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 is the filter is part of the request to PagerDuty when
// requesting incident analytics.
// 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"`
Expand All @@ -30,40 +30,39 @@ 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 float64 `json:"up_time_pct,omitempty"`
UserDefinedEffortSeconds int `json:"user_defined_effort_seconds,omitempty"`
RangeStart string `json:"range_start,omitempty"`
}

// GetAggregatedIncidentData gets the aggregated analytics for the requested data.
// 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
Expand All @@ -76,3 +75,41 @@ func (c *Client) GetAggregatedIncidentData(ctx context.Context, analytics Analyt

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
}

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
}
109 changes: 100 additions & 9 deletions analytics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
Expand All @@ -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")
Expand All @@ -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)
Expand Down
22 changes: 13 additions & 9 deletions command/analytics_show.go → command/analytics_incident_show.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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()
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down
Loading

0 comments on commit a9bc8eb

Please sign in to comment.