Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

teams and services analytics endpoints #312

Merged
merged 4 commits into from
Mar 18, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
71 changes: 49 additions & 22 deletions analytics.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ type AnalyticsRequest struct {
AggregateUnit string `json:"aggregate_unit,omitempty"`
TimeZone string `json:"time_zone,omitempty"`
}

type AnalyticsResponse struct {
Data []AnalyticsData `json:"data,omitempty"`
AnalyticsFilter *AnalyticsFilter `json:"filters,omitempty"`
Copy link
Collaborator

@theckman theckman Mar 13, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies for asking this of you since this wasn't your change, but your PR made me look at things again and I am now realizing I missed this in the original PR. Would you rename this field in your PR to just be Filters *AnalyticsFilter. We've not released this yet, and so I'd rather we get this done right versus letting it bleed out once we do release v1.4.0.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AnalyticsFilter has been changed to Filters 👍

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you may have misread my comment, I'm sorry. Was only wanting the field name updated, not the type too. The type made sense because it's the filters for the Analytics stuff, but AnalyticsResponse.AnalyticsFilters stutters a little bit, while AnalyticsResponse.Filters is easier to communicate.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did misread your comment. Apologies 😅 I went ahead and changed that in the new commit. Please let me know if you see anything else 👍

Expand All @@ -24,31 +25,31 @@ 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"`
}

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"`
}

func (c *Client) GetAggregatedIncidentData(ctx context.Context, analytics AnalyticsRequest) (AnalyticsResponse, error) {
Expand All @@ -63,3 +64,29 @@ func (c *Client) GetAggregatedIncidentData(ctx context.Context, analytics Analyt
err = c.decodeJSON(resp, &analyticsResponse)
return analyticsResponse, err
}

func (c *Client) GetAggregatedServiceData(ctx context.Context, analytics AnalyticsRequest) (AnalyticsResponse, error) {
theckman marked this conversation as resolved.
Show resolved Hide resolved
var analyticsResponse AnalyticsResponse
headers := make(map[string]string)
headers["X-EARLY-ACCESS"] = "analytics-v2"

resp, err := c.post(ctx, "/analytics/metrics/incidents/services", analytics, headers)
if err != nil {
return analyticsResponse, err
}
err = c.decodeJSON(resp, &analyticsResponse)
return analyticsResponse, err
}

func (c *Client) GetAggregatedTeamData(ctx context.Context, analytics AnalyticsRequest) (AnalyticsResponse, error) {
newbootz marked this conversation as resolved.
Show resolved Hide resolved
var analyticsResponse AnalyticsResponse
headers := make(map[string]string)
headers["X-EARLY-ACCESS"] = "analytics-v2"

resp, err := c.post(ctx, "/analytics/metrics/incidents/teams", analytics, headers)
if err != nil {
return analyticsResponse, err
}
err = c.decodeJSON(resp, &analyticsResponse)
return analyticsResponse, err
}
theckman marked this conversation as resolved.
Show resolved Hide resolved
91 changes: 91 additions & 0 deletions analytics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func TestAnalytics_GetAggregatedIncidentData(t *testing.T) {
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 @@ -51,3 +52,93 @@ func TestAnalytics_GetAggregatedIncidentData(t *testing.T) {
}
testEqual(t, want, res)
}

func TestAnalytics_GetAggregatedServiceData(t *testing.T) {
setup()
defer teardown()

analyticsRequest := AnalyticsRequest{
AnalyticsFilter: &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},
AnalyticsFilter: &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},
AnalyticsFilter: &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{
AnalyticsFilter: &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},
AnalyticsFilter: &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},
AnalyticsFilter: &analyticsFilterWanted,
AggregateUnit: "day",
TimeZone: "Etc/UTC",
}
if err != nil {
t.Fatal(err)
}
testEqual(t, want, res)
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func AnalyticsGetAggregatedIncidentDataCommand() (cli.Command, error) {

func (c *AnalyticsShow) Help() string {
helpText := `
analytics show
analytics incident show

Options:

Expand All @@ -34,11 +34,11 @@ func (c *AnalyticsShow) Help() string {
}

func (c *AnalyticsShow) Synopsis() string {
return "Get analytics aggregated incident data"
return "Get aggregated incident data analytics"
}

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 Down
104 changes: 104 additions & 0 deletions command/analytics_service_show.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
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
}

func AnalyticsGetAggregatedServiceDataCommand() (cli.Command, error) {
return &AnalyticsServiceShow{}, nil
}

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)
}

func (c *AnalyticsServiceShow) Synopsis() string {
return "Get aggregated service data analytics"
}

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")

//log.SetLevel(log.DebugLevel)
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{
AnalyticsFilter: &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
}