From 4672e6b3462393bc634168c6f5b769e624930374 Mon Sep 17 00:00:00 2001 From: Clemens W Date: Thu, 10 Oct 2024 10:10:18 +0200 Subject: [PATCH 01/11] Add function to get copilot org usage --- github/copilot.go | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/github/copilot.go b/github/copilot.go index 2697b71850c..4dd08fe65e9 100644 --- a/github/copilot.go +++ b/github/copilot.go @@ -63,6 +63,29 @@ type SeatCancellations struct { SeatsCancelled int `json:"seats_cancelled"` } +type CopilotUsageBreakdown struct { + Language string `json:"language"` + Editor string `json:"editor"` + SuggestionsCount int `json:"suggestions_count"` + AcceptancesCount int `json:"acceptances_count"` + LinesSuggested int `json:"lines_suggested"` + LinesAccepted int `json:"lines_accepted"` + ActiveUsers int `json:"active_users"` +} + +type CopilotUsageSummary struct { + Day string `json:"day"` + TotalSuggestionsCount int `json:"total_suggestions_count"` + TotalAcceptancesCount int `json:"total_acceptances_count"` + TotalLinesSuggested int `json:"total_lines_suggested"` + TotalLinesAccepted int `json:"total_lines_accepted"` + TotalActiveUsers int `json:"total_active_users"` + TotalChatAcceptances int `json:"total_chat_acceptances"` + TotalChatTurns int `json:"total_chat_turns"` + TotalActiveChatUsers int `json:"total_active_chat_users"` + Breakdown []CopilotUsageBreakdown `json:"breakdown"` +} + func (cp *CopilotSeatDetails) UnmarshalJSON(data []byte) error { // Using an alias to avoid infinite recursion when calling json.Unmarshal type alias CopilotSeatDetails @@ -313,3 +336,25 @@ func (s *CopilotService) GetSeatDetails(ctx context.Context, org, user string) ( return seatDetails, resp, nil } + +// GetOrganizationUsage gets daily breakdown of aggregated usage metrics for Copilot completions and Copilot Chat in the IDE across an organization +// +// GitHub API docs: https://docs.github.com/en/rest/copilot/copilot-usage#get-a-summary-of-copilot-usage-for-organization-members +// +//meta:operation GET /orgs/{org}/copilot/usage +func (s *CopilotService) GetOrganizationUsage(ctx context.Context, org string) ([]*CopilotUsageSummary, *Response, error) { + u := fmt.Sprintf("orgs/%v/copilot/usage", org) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var usage []*CopilotUsageSummary + resp, err := s.client.Do(ctx, req, &usage) + if err != nil { + return nil, resp, err + } + + return usage, resp, nil +} From c56a1fea0df96bf95b5d7e496de28c38058a09a2 Mon Sep 17 00:00:00 2001 From: Clemens W Date: Thu, 10 Oct 2024 10:10:33 +0200 Subject: [PATCH 02/11] Support list options for Copilot usage summary --- github/copilot.go | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/github/copilot.go b/github/copilot.go index 4dd08fe65e9..7be6650c0b0 100644 --- a/github/copilot.go +++ b/github/copilot.go @@ -9,6 +9,7 @@ import ( "context" "encoding/json" "fmt" + "time" ) // CopilotService provides access to the Copilot-related functions @@ -63,6 +64,13 @@ type SeatCancellations struct { SeatsCancelled int `json:"seats_cancelled"` } +type CopilotUsageSummaryListOptions struct { + Since time.Time `url:"since,omitempty"` + Until time.Time `url:"until,omitempty"` + + ListOptions +} + type CopilotUsageBreakdown struct { Language string `json:"language"` Editor string `json:"editor"` @@ -74,16 +82,16 @@ type CopilotUsageBreakdown struct { } type CopilotUsageSummary struct { - Day string `json:"day"` - TotalSuggestionsCount int `json:"total_suggestions_count"` - TotalAcceptancesCount int `json:"total_acceptances_count"` - TotalLinesSuggested int `json:"total_lines_suggested"` - TotalLinesAccepted int `json:"total_lines_accepted"` - TotalActiveUsers int `json:"total_active_users"` - TotalChatAcceptances int `json:"total_chat_acceptances"` - TotalChatTurns int `json:"total_chat_turns"` - TotalActiveChatUsers int `json:"total_active_chat_users"` - Breakdown []CopilotUsageBreakdown `json:"breakdown"` + Day string `json:"day"` + TotalSuggestionsCount int `json:"total_suggestions_count"` + TotalAcceptancesCount int `json:"total_acceptances_count"` + TotalLinesSuggested int `json:"total_lines_suggested"` + TotalLinesAccepted int `json:"total_lines_accepted"` + TotalActiveUsers int `json:"total_active_users"` + TotalChatAcceptances int `json:"total_chat_acceptances"` + TotalChatTurns int `json:"total_chat_turns"` + TotalActiveChatUsers int `json:"total_active_chat_users"` + Breakdown []*CopilotUsageBreakdown `json:"breakdown"` } func (cp *CopilotSeatDetails) UnmarshalJSON(data []byte) error { @@ -342,8 +350,12 @@ func (s *CopilotService) GetSeatDetails(ctx context.Context, org, user string) ( // GitHub API docs: https://docs.github.com/en/rest/copilot/copilot-usage#get-a-summary-of-copilot-usage-for-organization-members // //meta:operation GET /orgs/{org}/copilot/usage -func (s *CopilotService) GetOrganizationUsage(ctx context.Context, org string) ([]*CopilotUsageSummary, *Response, error) { +func (s *CopilotService) GetOrganizationUsage(ctx context.Context, org string, opts *CopilotUsageSummaryListOptions) ([]*CopilotUsageSummary, *Response, error) { u := fmt.Sprintf("orgs/%v/copilot/usage", org) + u, err := addOptions(u, opts) + if err != nil { + return nil, nil, err + } req, err := s.client.NewRequest("GET", u, nil) if err != nil { From a732767f40c874bcf74debf7388b841685553100 Mon Sep 17 00:00:00 2001 From: Clemens W Date: Wed, 9 Oct 2024 14:29:18 +0200 Subject: [PATCH 03/11] Added test case --- github/copilot_test.go | 200 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) diff --git a/github/copilot_test.go b/github/copilot_test.go index 355ff130c85..7f1913a3512 100644 --- a/github/copilot_test.go +++ b/github/copilot_test.go @@ -883,3 +883,203 @@ func TestCopilotService_GetSeatDetails(t *testing.T) { return resp, err }) } + +func TestCopilotService_GetOrganisationUsage(t *testing.T) { + client, mux, _ := setup(t) + + mux.HandleFunc("/orgs/o/copilot/usage", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[ + { + "day": "2023-10-15", + "total_suggestions_count": 1000, + "total_acceptances_count": 800, + "total_lines_suggested": 1800, + "total_lines_accepted": 1200, + "total_active_users": 10, + "total_chat_acceptances": 32, + "total_chat_turns": 200, + "total_active_chat_users": 4, + "breakdown": [ + { + "language": "python", + "editor": "vscode", + "suggestions_count": 300, + "acceptances_count": 250, + "lines_suggested": 900, + "lines_accepted": 700, + "active_users": 5 + }, + { + "language": "python", + "editor": "jetbrains", + "suggestions_count": 300, + "acceptances_count": 200, + "lines_suggested": 400, + "lines_accepted": 300, + "active_users": 2 + }, + { + "language": "ruby", + "editor": "vscode", + "suggestions_count": 400, + "acceptances_count": 350, + "lines_suggested": 500, + "lines_accepted": 200, + "active_users": 3 + } + ] + }, + { + "day": "2023-10-16", + "total_suggestions_count": 800, + "total_acceptances_count": 600, + "total_lines_suggested": 1100, + "total_lines_accepted": 700, + "total_active_users": 12, + "total_chat_acceptances": 57, + "total_chat_turns": 426, + "total_active_chat_users": 8, + "breakdown": [ + { + "language": "python", + "editor": "vscode", + "suggestions_count": 300, + "acceptances_count": 200, + "lines_suggested": 600, + "lines_accepted": 300, + "active_users": 2 + }, + { + "language": "python", + "editor": "jetbrains", + "suggestions_count": 300, + "acceptances_count": 150, + "lines_suggested": 300, + "lines_accepted": 250, + "active_users": 6 + }, + { + "language": "ruby", + "editor": "vscode", + "suggestions_count": 200, + "acceptances_count": 150, + "lines_suggested": 200, + "lines_accepted": 150, + "active_users": 3 + } + ] + } + ]`) + }) + + summaryOne := time.Date(2023, time.October, 15, 0, 0, 0, 0, time.UTC) + summaryTwoDate := time.Date(2023, time.October, 16, 0, 0, 0, 0, time.UTC) + ctx := context.Background() + got, _, err := client.Copilot.GetOrganizationUsage(ctx, "o", &CopilotUsageSummaryListOptions{}) + if err != nil { + t.Errorf("Copilot.GetOrganizationUsage returned error: %v", err) + } + + want := []*CopilotUsageSummary{ + { + Day: summaryOne.Format("2006-01-02"), + TotalSuggestionsCount: 1000, + TotalAcceptancesCount: 800, + TotalLinesSuggested: 1800, + TotalLinesAccepted: 1200, + TotalActiveUsers: 10, + TotalChatAcceptances: 32, + TotalChatTurns: 200, + TotalActiveChatUsers: 4, + Breakdown: []*CopilotUsageBreakdown{ + { + Language: "python", + Editor: "vscode", + SuggestionsCount: 300, + AcceptancesCount: 250, + LinesSuggested: 900, + LinesAccepted: 700, + ActiveUsers: 5, + }, + { + Language: "python", + Editor: "jetbrains", + SuggestionsCount: 300, + AcceptancesCount: 200, + LinesSuggested: 400, + LinesAccepted: 300, + ActiveUsers: 2, + }, + { + Language: "ruby", + Editor: "vscode", + SuggestionsCount: 400, + AcceptancesCount: 350, + LinesSuggested: 500, + LinesAccepted: 200, + ActiveUsers: 3, + }, + }, + }, + { + Day: summaryTwoDate.Format("2006-01-02"), + TotalSuggestionsCount: 800, + TotalAcceptancesCount: 600, + TotalLinesSuggested: 1100, + TotalLinesAccepted: 700, + TotalActiveUsers: 12, + TotalChatAcceptances: 57, + TotalChatTurns: 426, + TotalActiveChatUsers: 8, + Breakdown: []*CopilotUsageBreakdown{ + { + Language: "python", + Editor: "vscode", + SuggestionsCount: 300, + AcceptancesCount: 200, + LinesSuggested: 600, + LinesAccepted: 300, + ActiveUsers: 2, + }, + { + Language: "python", + Editor: "jetbrains", + SuggestionsCount: 300, + AcceptancesCount: 150, + LinesSuggested: 300, + LinesAccepted: 250, + ActiveUsers: 6, + }, + { + Language: "ruby", + Editor: "vscode", + SuggestionsCount: 200, + AcceptancesCount: 150, + LinesSuggested: 200, + LinesAccepted: 150, + ActiveUsers: 3, + }, + }, + }, + } + + if !cmp.Equal(got, want) { + t.Errorf("Copilot.GetOrganizationUsage returned %+v, want %+v", got, want) + } + + const methodName = "GetOrganizationUsage" + + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Copilot.GetOrganizationUsage(ctx, "\n", &CopilotUsageSummaryListOptions{}) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Copilot.GetOrganizationUsage(ctx, "o", &CopilotUsageSummaryListOptions{}) + if got != nil { + t.Errorf("Copilot.GetOrganizationUsage returned %+v, want nil", got) + } + return resp, err + }) +} From fa3c5fccf596e608192ee9511758f5cf8672f24d Mon Sep 17 00:00:00 2001 From: Clemens W Date: Thu, 10 Oct 2024 10:15:10 +0200 Subject: [PATCH 04/11] Resolved PR Feedback --- github/copilot.go | 9 ++++++--- github/copilot_test.go | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/github/copilot.go b/github/copilot.go index 7be6650c0b0..789676907b4 100644 --- a/github/copilot.go +++ b/github/copilot.go @@ -64,13 +64,15 @@ type SeatCancellations struct { SeatsCancelled int `json:"seats_cancelled"` } +// CopilotUsageSummaryListOptions represents the optional parameters to the CopilotService.GetOrganizationUsage method. type CopilotUsageSummaryListOptions struct { - Since time.Time `url:"since,omitempty"` - Until time.Time `url:"until,omitempty"` + Since *time.Time `url:"since,omitempty"` + Until *time.Time `url:"until,omitempty"` ListOptions } +// CopilotUsageBreakdown represents the breakdown of Copilot usage for a specific language and editor. type CopilotUsageBreakdown struct { Language string `json:"language"` Editor string `json:"editor"` @@ -81,6 +83,7 @@ type CopilotUsageBreakdown struct { ActiveUsers int `json:"active_users"` } +// CopilotUsageSummary represents the daily breakdown of aggregated usage metrics for Copilot completions and Copilot Chat in the IDE across an organization. type CopilotUsageSummary struct { Day string `json:"day"` TotalSuggestionsCount int `json:"total_suggestions_count"` @@ -345,7 +348,7 @@ func (s *CopilotService) GetSeatDetails(ctx context.Context, org, user string) ( return seatDetails, resp, nil } -// GetOrganizationUsage gets daily breakdown of aggregated usage metrics for Copilot completions and Copilot Chat in the IDE across an organization +// GetOrganizationUsage gets daily breakdown of aggregated usage metrics for Copilot completions and Copilot Chat in the IDE across an organization. // // GitHub API docs: https://docs.github.com/en/rest/copilot/copilot-usage#get-a-summary-of-copilot-usage-for-organization-members // diff --git a/github/copilot_test.go b/github/copilot_test.go index 335b1a8b827..672cac52e07 100644 --- a/github/copilot_test.go +++ b/github/copilot_test.go @@ -898,6 +898,7 @@ func TestCopilotService_GetSeatDetails(t *testing.T) { } func TestCopilotService_GetOrganisationUsage(t *testing.T) { + t.Parallel() client, mux, _ := setup(t) mux.HandleFunc("/orgs/o/copilot/usage", func(w http.ResponseWriter, r *http.Request) { From 353789fef6aadcd71f3d52847d35ac3188f12758 Mon Sep 17 00:00:00 2001 From: Clemens W Date: Thu, 10 Oct 2024 13:49:47 +0200 Subject: [PATCH 05/11] Added enterprise usage endpoint --- github/copilot.go | 26 ++++++ github/copilot_test.go | 201 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 227 insertions(+) diff --git a/github/copilot.go b/github/copilot.go index 789676907b4..524fccd49a0 100644 --- a/github/copilot.go +++ b/github/copilot.go @@ -373,3 +373,29 @@ func (s *CopilotService) GetOrganizationUsage(ctx context.Context, org string, o return usage, resp, nil } + +// GetEnterpriseUsage gets daily breakdown of aggregated usage metrics for Copilot completions and Copilot Chat in the IDE across an enterprise. +// +// GitHub API docs: https://docs.github.com/en/rest/copilot/copilot-usage?apiVersion=2022-11-28#get-a-summary-of-copilot-usage-for-enterprise-members +// +//meta:operation GET /enterprises/{enterprise}/copilot/usage +func (s *CopilotService) GetEnterpriseUsage(ctx context.Context, enterprise string, opts *CopilotUsageSummaryListOptions) ([]*CopilotUsageSummary, *Response, error) { + u := fmt.Sprintf("enterprises/%v/copilot/usage", enterprise) + u, err := addOptions(u, opts) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var usage []*CopilotUsageSummary + resp, err := s.client.Do(ctx, req, &usage) + if err != nil { + return nil, resp, err + } + + return usage, resp, nil +} diff --git a/github/copilot_test.go b/github/copilot_test.go index 672cac52e07..fbfbd2fb955 100644 --- a/github/copilot_test.go +++ b/github/copilot_test.go @@ -1097,3 +1097,204 @@ func TestCopilotService_GetOrganisationUsage(t *testing.T) { return resp, err }) } + +func TestCopilotService_GetEnterpriseUsage(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/enterprises/e/copilot/usage", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[ + { + "day": "2023-10-15", + "total_suggestions_count": 5000, + "total_acceptances_count": 3000, + "total_lines_suggested": 7000, + "total_lines_accepted": 3500, + "total_active_users": 15, + "total_chat_acceptances": 45, + "total_chat_turns": 350, + "total_active_chat_users": 8, + "breakdown": [ + { + "language": "python", + "editor": "vscode", + "suggestions_count": 3000, + "acceptances_count": 2000, + "lines_suggested": 3000, + "lines_accepted": 1500, + "active_users": 5 + }, + { + "language": "python", + "editor": "jetbrains", + "suggestions_count": 1000, + "acceptances_count": 500, + "lines_suggested": 2000, + "lines_accepted": 1000, + "active_users": 5 + }, + { + "language": "javascript", + "editor": "vscode", + "suggestions_count": 1000, + "acceptances_count": 500, + "lines_suggested": 2000, + "lines_accepted": 1000, + "active_users": 5 + } + ] + }, + { + "day": "2023-10-16", + "total_suggestions_count": 5200, + "total_acceptances_count": 5100, + "total_lines_suggested": 5300, + "total_lines_accepted": 5000, + "total_active_users": 15, + "total_chat_acceptances": 57, + "total_chat_turns": 455, + "total_active_chat_users": 12, + "breakdown": [ + { + "language": "python", + "editor": "vscode", + "suggestions_count": 3100, + "acceptances_count": 3000, + "lines_suggested": 3200, + "lines_accepted": 3100, + "active_users": 5 + }, + { + "language": "python", + "editor": "jetbrains", + "suggestions_count": 1100, + "acceptances_count": 1000, + "lines_suggested": 1200, + "lines_accepted": 1100, + "active_users": 5 + }, + { + "language": "javascript", + "editor": "vscode", + "suggestions_count": 1000, + "acceptances_count": 900, + "lines_suggested": 1100, + "lines_accepted": 1000, + "active_users": 5 + } + ] + } + ]`) + }) + + summaryOne := time.Date(2023, time.October, 15, 0, 0, 0, 0, time.UTC) + summaryTwoDate := time.Date(2023, time.October, 16, 0, 0, 0, 0, time.UTC) + ctx := context.Background() + got, _, err := client.Copilot.GetEnterpriseUsage(ctx, "e", &CopilotUsageSummaryListOptions{}) + if err != nil { + t.Errorf("Copilot.GetEnterpriseUsage returned error: %v", err) + } + + want := []*CopilotUsageSummary{ + { + Day: summaryOne.Format("2006-01-02"), + TotalSuggestionsCount: 5000, + TotalAcceptancesCount: 3000, + TotalLinesSuggested: 7000, + TotalLinesAccepted: 3500, + TotalActiveUsers: 15, + TotalChatAcceptances: 45, + TotalChatTurns: 350, + TotalActiveChatUsers: 8, + Breakdown: []*CopilotUsageBreakdown{ + { + Language: "python", + Editor: "vscode", + SuggestionsCount: 3000, + AcceptancesCount: 2000, + LinesSuggested: 3000, + LinesAccepted: 1500, + ActiveUsers: 5, + }, + { + Language: "python", + Editor: "jetbrains", + SuggestionsCount: 1000, + AcceptancesCount: 500, + LinesSuggested: 2000, + LinesAccepted: 1000, + ActiveUsers: 5, + }, + { + Language: "javascript", + Editor: "vscode", + SuggestionsCount: 1000, + AcceptancesCount: 500, + LinesSuggested: 2000, + LinesAccepted: 1000, + ActiveUsers: 5, + }, + }, + }, + { + Day: summaryTwoDate.Format("2006-01-02"), + TotalSuggestionsCount: 5200, + TotalAcceptancesCount: 5100, + TotalLinesSuggested: 5300, + TotalLinesAccepted: 5000, + TotalActiveUsers: 15, + TotalChatAcceptances: 57, + TotalChatTurns: 455, + TotalActiveChatUsers: 12, + Breakdown: []*CopilotUsageBreakdown{ + { + Language: "python", + Editor: "vscode", + SuggestionsCount: 3100, + AcceptancesCount: 3000, + LinesSuggested: 3200, + LinesAccepted: 3100, + ActiveUsers: 5, + }, + { + Language: "python", + Editor: "jetbrains", + SuggestionsCount: 1100, + AcceptancesCount: 1000, + LinesSuggested: 1200, + LinesAccepted: 1100, + ActiveUsers: 5, + }, + { + Language: "javascript", + Editor: "vscode", + SuggestionsCount: 1000, + AcceptancesCount: 900, + LinesSuggested: 1100, + LinesAccepted: 1000, + ActiveUsers: 5, + }, + }, + }, + } + + if !cmp.Equal(got, want) { + t.Errorf("Copilot.GetEnterpriseUsage returned %+v, want %+v", got, want) + } + + const methodName = "GetEnterpriseUsage" + + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Copilot.GetEnterpriseUsage(ctx, "\n", &CopilotUsageSummaryListOptions{}) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Copilot.GetEnterpriseUsage(ctx, "e", &CopilotUsageSummaryListOptions{}) + if got != nil { + t.Errorf("Copilot.GetEnterpriseUsage returned %+v, want nil", got) + } + return resp, err + }) +} From 17f47cb123cd3347b46407243204f85069df563a Mon Sep 17 00:00:00 2001 From: Clemens W Date: Thu, 10 Oct 2024 14:03:59 +0200 Subject: [PATCH 06/11] Add Enterprise Team usage --- github/copilot.go | 26 ++++++ github/copilot_test.go | 201 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 227 insertions(+) diff --git a/github/copilot.go b/github/copilot.go index 524fccd49a0..32c4040e2fa 100644 --- a/github/copilot.go +++ b/github/copilot.go @@ -399,3 +399,29 @@ func (s *CopilotService) GetEnterpriseUsage(ctx context.Context, enterprise stri return usage, resp, nil } + +// GetEnterpriseTeamUsage gets daily breakdown of aggregated usage metrics for Copilot completions and Copilot Chat in the IDE for a team in the enterprise. +// +// GitHub API docs: https://docs.github.com/en/rest/copilot/copilot-usage?apiVersion=2022-11-28#get-a-summary-of-copilot-usage-for-an-enterprise-team +// +//meta:operation GET /enterprises/{enterprise}/team/{team_slug}/copilot/usage +func (s *CopilotService) GetEnterpriseTeamUsage(ctx context.Context, enterprise string, team string, opts *CopilotUsageSummaryListOptions) ([]*CopilotUsageSummary, *Response, error) { + u := fmt.Sprintf("enterprises/%v/team/%v/copilot/usage", enterprise, team) + u, err := addOptions(u, opts) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var usage []*CopilotUsageSummary + resp, err := s.client.Do(ctx, req, &usage) + if err != nil { + return nil, resp, err + } + + return usage, resp, nil +} diff --git a/github/copilot_test.go b/github/copilot_test.go index fbfbd2fb955..412a30bf237 100644 --- a/github/copilot_test.go +++ b/github/copilot_test.go @@ -1298,3 +1298,204 @@ func TestCopilotService_GetEnterpriseUsage(t *testing.T) { return resp, err }) } + +func TestCopilotService_GetEnterpriseTeamUsage(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/enterprises/e/team/t/copilot/usage", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[ + { + "day": "2023-10-15", + "total_suggestions_count": 1000, + "total_acceptances_count": 800, + "total_lines_suggested": 1800, + "total_lines_accepted": 1200, + "total_active_users": 10, + "total_chat_acceptances": 32, + "total_chat_turns": 200, + "total_active_chat_users": 4, + "breakdown": [ + { + "language": "python", + "editor": "vscode", + "suggestions_count": 300, + "acceptances_count": 250, + "lines_suggested": 900, + "lines_accepted": 700, + "active_users": 5 + }, + { + "language": "python", + "editor": "jetbrains", + "suggestions_count": 300, + "acceptances_count": 200, + "lines_suggested": 400, + "lines_accepted": 300, + "active_users": 2 + }, + { + "language": "ruby", + "editor": "vscode", + "suggestions_count": 400, + "acceptances_count": 350, + "lines_suggested": 500, + "lines_accepted": 200, + "active_users": 3 + } + ] + }, + { + "day": "2023-10-16", + "total_suggestions_count": 800, + "total_acceptances_count": 600, + "total_lines_suggested": 1100, + "total_lines_accepted": 700, + "total_active_users": 12, + "total_chat_acceptances": 57, + "total_chat_turns": 426, + "total_active_chat_users": 8, + "breakdown": [ + { + "language": "python", + "editor": "vscode", + "suggestions_count": 300, + "acceptances_count": 200, + "lines_suggested": 600, + "lines_accepted": 300, + "active_users": 2 + }, + { + "language": "python", + "editor": "jetbrains", + "suggestions_count": 300, + "acceptances_count": 150, + "lines_suggested": 300, + "lines_accepted": 250, + "active_users": 6 + }, + { + "language": "ruby", + "editor": "vscode", + "suggestions_count": 200, + "acceptances_count": 150, + "lines_suggested": 200, + "lines_accepted": 150, + "active_users": 3 + } + ] + } + ]`) + }) + + summaryOne := time.Date(2023, time.October, 15, 0, 0, 0, 0, time.UTC) + summaryTwoDate := time.Date(2023, time.October, 16, 0, 0, 0, 0, time.UTC) + ctx := context.Background() + got, _, err := client.Copilot.GetEnterpriseTeamUsage(ctx, "e", "t", &CopilotUsageSummaryListOptions{}) + if err != nil { + t.Errorf("Copilot.GetEnterpriseTeamUsage returned error: %v", err) + } + + want := []*CopilotUsageSummary{ + { + Day: summaryOne.Format("2006-01-02"), + TotalSuggestionsCount: 1000, + TotalAcceptancesCount: 800, + TotalLinesSuggested: 1800, + TotalLinesAccepted: 1200, + TotalActiveUsers: 10, + TotalChatAcceptances: 32, + TotalChatTurns: 200, + TotalActiveChatUsers: 4, + Breakdown: []*CopilotUsageBreakdown{ + { + Language: "python", + Editor: "vscode", + SuggestionsCount: 300, + AcceptancesCount: 250, + LinesSuggested: 900, + LinesAccepted: 700, + ActiveUsers: 5, + }, + { + Language: "python", + Editor: "jetbrains", + SuggestionsCount: 300, + AcceptancesCount: 200, + LinesSuggested: 400, + LinesAccepted: 300, + ActiveUsers: 2, + }, + { + Language: "ruby", + Editor: "vscode", + SuggestionsCount: 400, + AcceptancesCount: 350, + LinesSuggested: 500, + LinesAccepted: 200, + ActiveUsers: 3, + }, + }, + }, + { + Day: summaryTwoDate.Format("2006-01-02"), + TotalSuggestionsCount: 800, + TotalAcceptancesCount: 600, + TotalLinesSuggested: 1100, + TotalLinesAccepted: 700, + TotalActiveUsers: 12, + TotalChatAcceptances: 57, + TotalChatTurns: 426, + TotalActiveChatUsers: 8, + Breakdown: []*CopilotUsageBreakdown{ + { + Language: "python", + Editor: "vscode", + SuggestionsCount: 300, + AcceptancesCount: 200, + LinesSuggested: 600, + LinesAccepted: 300, + ActiveUsers: 2, + }, + { + Language: "python", + Editor: "jetbrains", + SuggestionsCount: 300, + AcceptancesCount: 150, + LinesSuggested: 300, + LinesAccepted: 250, + ActiveUsers: 6, + }, + { + Language: "ruby", + Editor: "vscode", + SuggestionsCount: 200, + AcceptancesCount: 150, + LinesSuggested: 200, + LinesAccepted: 150, + ActiveUsers: 3, + }, + }, + }, + } + + if !cmp.Equal(got, want) { + t.Errorf("Copilot.GetEnterpriseTeamUsage returned %+v, want %+v", got, want) + } + + const methodName = "GetEnterpriseTeamUsage" + + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Copilot.GetEnterpriseTeamUsage(ctx, "\n", "\n", &CopilotUsageSummaryListOptions{}) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Copilot.GetEnterpriseTeamUsage(ctx, "e", "t", &CopilotUsageSummaryListOptions{}) + if got != nil { + t.Errorf("Copilot.GetEnterpriseTeamUsage returned %+v, want nil", got) + } + return resp, err + }) +} From 3edee00aed0efd58426c498a8b0492fefb7e0a35 Mon Sep 17 00:00:00 2001 From: Clemens W Date: Thu, 10 Oct 2024 14:19:11 +0200 Subject: [PATCH 07/11] Added Organization Team usage api --- github/copilot.go | 26 ++++++ github/copilot_test.go | 201 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 227 insertions(+) diff --git a/github/copilot.go b/github/copilot.go index 32c4040e2fa..3402dbf3130 100644 --- a/github/copilot.go +++ b/github/copilot.go @@ -425,3 +425,29 @@ func (s *CopilotService) GetEnterpriseTeamUsage(ctx context.Context, enterprise return usage, resp, nil } + +// GetOrganizationTeamUsage gets daily breakdown of aggregated usage metrics for Copilot completions and Copilot Chat in the IDE for a team in the organization. +// +// GitHub API docs: https://docs.github.com/en/rest/copilot/copilot-usage?apiVersion=2022-11-28#get-a-summary-of-copilot-usage-for-a-team +// +//meta:operation GET /orgs/{org}/team/{team_slug}/copilot/usage +func (s *CopilotService) GetOrganizationTeamUsage(ctx context.Context, org string, team string, opts *CopilotUsageSummaryListOptions) ([]*CopilotUsageSummary, *Response, error) { + u := fmt.Sprintf("orgs/%v/team/%v/copilot/usage", org, team) + u, err := addOptions(u, opts) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var usage []*CopilotUsageSummary + resp, err := s.client.Do(ctx, req, &usage) + if err != nil { + return nil, resp, err + } + + return usage, resp, nil +} diff --git a/github/copilot_test.go b/github/copilot_test.go index 412a30bf237..fb3ee6b8b60 100644 --- a/github/copilot_test.go +++ b/github/copilot_test.go @@ -1499,3 +1499,204 @@ func TestCopilotService_GetEnterpriseTeamUsage(t *testing.T) { return resp, err }) } + +func TestCopilotService_GetOrganizationTeamUsage(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/orgs/o/team/t/copilot/usage", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[ + { + "day": "2023-10-15", + "total_suggestions_count": 1000, + "total_acceptances_count": 800, + "total_lines_suggested": 1800, + "total_lines_accepted": 1200, + "total_active_users": 10, + "total_chat_acceptances": 32, + "total_chat_turns": 200, + "total_active_chat_users": 4, + "breakdown": [ + { + "language": "python", + "editor": "vscode", + "suggestions_count": 300, + "acceptances_count": 250, + "lines_suggested": 900, + "lines_accepted": 700, + "active_users": 5 + }, + { + "language": "python", + "editor": "jetbrains", + "suggestions_count": 300, + "acceptances_count": 200, + "lines_suggested": 400, + "lines_accepted": 300, + "active_users": 2 + }, + { + "language": "ruby", + "editor": "vscode", + "suggestions_count": 400, + "acceptances_count": 350, + "lines_suggested": 500, + "lines_accepted": 200, + "active_users": 3 + } + ] + }, + { + "day": "2023-10-16", + "total_suggestions_count": 800, + "total_acceptances_count": 600, + "total_lines_suggested": 1100, + "total_lines_accepted": 700, + "total_active_users": 12, + "total_chat_acceptances": 57, + "total_chat_turns": 426, + "total_active_chat_users": 8, + "breakdown": [ + { + "language": "python", + "editor": "vscode", + "suggestions_count": 300, + "acceptances_count": 200, + "lines_suggested": 600, + "lines_accepted": 300, + "active_users": 2 + }, + { + "language": "python", + "editor": "jetbrains", + "suggestions_count": 300, + "acceptances_count": 150, + "lines_suggested": 300, + "lines_accepted": 250, + "active_users": 6 + }, + { + "language": "ruby", + "editor": "vscode", + "suggestions_count": 200, + "acceptances_count": 150, + "lines_suggested": 200, + "lines_accepted": 150, + "active_users": 3 + } + ] + } + ]`) + }) + + summaryOne := time.Date(2023, time.October, 15, 0, 0, 0, 0, time.UTC) + summaryTwoDate := time.Date(2023, time.October, 16, 0, 0, 0, 0, time.UTC) + ctx := context.Background() + got, _, err := client.Copilot.GetOrganizationTeamUsage(ctx, "o", "t", &CopilotUsageSummaryListOptions{}) + if err != nil { + t.Errorf("Copilot.GetOrganizationTeamUsage returned error: %v", err) + } + + want := []*CopilotUsageSummary{ + { + Day: summaryOne.Format("2006-01-02"), + TotalSuggestionsCount: 1000, + TotalAcceptancesCount: 800, + TotalLinesSuggested: 1800, + TotalLinesAccepted: 1200, + TotalActiveUsers: 10, + TotalChatAcceptances: 32, + TotalChatTurns: 200, + TotalActiveChatUsers: 4, + Breakdown: []*CopilotUsageBreakdown{ + { + Language: "python", + Editor: "vscode", + SuggestionsCount: 300, + AcceptancesCount: 250, + LinesSuggested: 900, + LinesAccepted: 700, + ActiveUsers: 5, + }, + { + Language: "python", + Editor: "jetbrains", + SuggestionsCount: 300, + AcceptancesCount: 200, + LinesSuggested: 400, + LinesAccepted: 300, + ActiveUsers: 2, + }, + { + Language: "ruby", + Editor: "vscode", + SuggestionsCount: 400, + AcceptancesCount: 350, + LinesSuggested: 500, + LinesAccepted: 200, + ActiveUsers: 3, + }, + }, + }, + { + Day: summaryTwoDate.Format("2006-01-02"), + TotalSuggestionsCount: 800, + TotalAcceptancesCount: 600, + TotalLinesSuggested: 1100, + TotalLinesAccepted: 700, + TotalActiveUsers: 12, + TotalChatAcceptances: 57, + TotalChatTurns: 426, + TotalActiveChatUsers: 8, + Breakdown: []*CopilotUsageBreakdown{ + { + Language: "python", + Editor: "vscode", + SuggestionsCount: 300, + AcceptancesCount: 200, + LinesSuggested: 600, + LinesAccepted: 300, + ActiveUsers: 2, + }, + { + Language: "python", + Editor: "jetbrains", + SuggestionsCount: 300, + AcceptancesCount: 150, + LinesSuggested: 300, + LinesAccepted: 250, + ActiveUsers: 6, + }, + { + Language: "ruby", + Editor: "vscode", + SuggestionsCount: 200, + AcceptancesCount: 150, + LinesSuggested: 200, + LinesAccepted: 150, + ActiveUsers: 3, + }, + }, + }, + } + + if !cmp.Equal(got, want) { + t.Errorf("Copilot.GetOrganizationTeamUsage returned %+v, want %+v", got, want) + } + + const methodName = "GetOrganizationTeamUsage" + + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Copilot.GetOrganizationTeamUsage(ctx, "\n", "\n", &CopilotUsageSummaryListOptions{}) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Copilot.GetOrganizationTeamUsage(ctx, "o", "t", &CopilotUsageSummaryListOptions{}) + if got != nil { + t.Errorf("Copilot.GetOrganizationTeamUsage returned %+v, want nil", got) + } + return resp, err + }) +} From f92eaca934d0361c98f3bf80117dda3d81d06006 Mon Sep 17 00:00:00 2001 From: Clemens W Date: Thu, 10 Oct 2024 17:39:06 +0200 Subject: [PATCH 08/11] Add List Copilot Enterprise Seats API --- github/copilot.go | 30 ++++++ github/copilot_test.go | 230 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 260 insertions(+) diff --git a/github/copilot.go b/github/copilot.go index 3402dbf3130..34f2516eb18 100644 --- a/github/copilot.go +++ b/github/copilot.go @@ -52,6 +52,7 @@ type CopilotSeatDetails struct { LastActivityEditor *string `json:"last_activity_editor,omitempty"` CreatedAt *Timestamp `json:"created_at"` UpdatedAt *Timestamp `json:"updated_at,omitempty"` + PlanType *string `json:"plan_type,omitempty"` } // SeatAssignments represents the number of seats assigned. @@ -112,6 +113,7 @@ func (cp *CopilotSeatDetails) UnmarshalJSON(data []byte) error { cp.LastActivityEditor = seatDetail.LastActivityEditor cp.CreatedAt = seatDetail.CreatedAt cp.UpdatedAt = seatDetail.UpdatedAt + cp.PlanType = seatDetail.PlanType switch v := seatDetail.Assignee.(type) { case map[string]interface{}: @@ -214,6 +216,34 @@ func (s *CopilotService) ListCopilotSeats(ctx context.Context, org string, opts return copilotSeats, resp, nil } +// ListCopilotEnterpriseSeats lists Copilot for Business seat assignments for an enterprise. +// +// To paginate through all seats, populate 'Page' with the number of the last page. +// +// GitHub API docs: https://docs.github.com/en/rest/copilot/copilot-user-management?apiVersion=2022-11-28#list-all-copilot-seat-assignments-for-an-enterprise +// +//meta:operation GET /enterprises/{enterprise}/copilot/billing/seats +func (s *CopilotService) ListCopilotEnterpriseSeats(ctx context.Context, enterprise string, opts *ListOptions) (*ListCopilotSeatsResponse, *Response, error) { + u := fmt.Sprintf("enterprises/%v/copilot/billing/seats", enterprise) + u, err := addOptions(u, opts) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var copilotSeats *ListCopilotSeatsResponse + resp, err := s.client.Do(ctx, req, &copilotSeats) + if err != nil { + return nil, resp, err + } + + return copilotSeats, resp, nil +} + // AddCopilotTeams adds teams to the Copilot for Business subscription for an organization. // // GitHub API docs: https://docs.github.com/rest/copilot/copilot-user-management#add-teams-to-the-copilot-subscription-for-an-organization diff --git a/github/copilot_test.go b/github/copilot_test.go index fb3ee6b8b60..9d552af3d67 100644 --- a/github/copilot_test.go +++ b/github/copilot_test.go @@ -9,6 +9,7 @@ import ( "context" "encoding/json" "fmt" + "log" "net/http" "testing" "time" @@ -609,6 +610,235 @@ func TestCopilotService_ListCopilotSeats(t *testing.T) { }) } +func TestCopilotService_ListCopilotEnterpriseSeats(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/enterprises/e/copilot/billing/seats", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "per_page": "100", + "page": "1", + }) + fmt.Fprint(w, `{ + "total_seats": 2, + "seats": [ + { + "created_at": "2021-08-03T18:00:00-06:00", + "updated_at": "2021-09-23T15:00:00-06:00", + "pending_cancellation_date": null, + "last_activity_at": "2021-10-14T00:53:32-06:00", + "last_activity_editor": "vscode/1.77.3/copilot/1.86.82", + "plan_type": "business", + "assignee": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "assigning_team": { + "id": 1, + "node_id": "MDQ6VGVhbTE=", + "url": "https://api.github.com/teams/1", + "html_url": "https://github.com/orgs/github/teams/justice-league", + "name": "Justice League", + "slug": "justice-league", + "description": "A great team.", + "privacy": "closed", + "notification_setting": "notifications_enabled", + "permission": "admin", + "members_url": "https://api.github.com/teams/1/members{/member}", + "repositories_url": "https://api.github.com/teams/1/repos", + "parent": null + } + }, + { + "created_at": "2021-09-23T18:00:00-06:00", + "updated_at": "2021-09-23T15:00:00-06:00", + "pending_cancellation_date": "2021-11-01", + "last_activity_at": "2021-10-13T00:53:32-06:00", + "last_activity_editor": "vscode/1.77.3/copilot/1.86.82", + "assignee": { + "login": "octokitten", + "id": 1, + "node_id": "MDQ76VNlcjE=", + "avatar_url": "https://github.com/images/error/octokitten_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octokitten", + "html_url": "https://github.com/octokitten", + "followers_url": "https://api.github.com/users/octokitten/followers", + "following_url": "https://api.github.com/users/octokitten/following{/other_user}", + "gists_url": "https://api.github.com/users/octokitten/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octokitten/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octokitten/subscriptions", + "organizations_url": "https://api.github.com/users/octokitten/orgs", + "repos_url": "https://api.github.com/users/octokitten/repos", + "events_url": "https://api.github.com/users/octokitten/events{/privacy}", + "received_events_url": "https://api.github.com/users/octokitten/received_events", + "type": "User", + "site_admin": false + } + } + ] + }`) + }) + + tmp, err := time.Parse(time.RFC3339, "2021-08-03T18:00:00-06:00") + if err != nil { + panic(err) + } + createdAt1 := Timestamp{tmp} + + tmp, err = time.Parse(time.RFC3339, "2021-09-23T15:00:00-06:00") + if err != nil { + panic(err) + } + updatedAt1 := Timestamp{tmp} + + tmp, err = time.Parse(time.RFC3339, "2021-10-14T00:53:32-06:00") + if err != nil { + panic(err) + } + lastActivityAt1 := Timestamp{tmp} + + tmp, err = time.Parse(time.RFC3339, "2021-09-23T18:00:00-06:00") + if err != nil { + panic(err) + } + createdAt2 := Timestamp{tmp} + + tmp, err = time.Parse(time.RFC3339, "2021-09-23T15:00:00-06:00") + if err != nil { + panic(err) + } + updatedAt2 := Timestamp{tmp} + + tmp, err = time.Parse(time.RFC3339, "2021-10-13T00:53:32-06:00") + if err != nil { + panic(err) + } + lastActivityAt2 := Timestamp{tmp} + + ctx := context.Background() + opts := &ListOptions{Page: 1, PerPage: 100} + got, _, err := client.Copilot.ListCopilotEnterpriseSeats(ctx, "e", opts) + if err != nil { + t.Errorf("Copilot.ListCopilotEnterpriseSeats returned error: %v", err) + } + + want := &ListCopilotSeatsResponse{ + TotalSeats: 2, + Seats: []*CopilotSeatDetails{ + { + Assignee: &User{ + Login: String("octocat"), + ID: Int64(1), + NodeID: String("MDQ6VXNlcjE="), + AvatarURL: String("https://github.com/images/error/octocat_happy.gif"), + GravatarID: String(""), + URL: String("https://api.github.com/users/octocat"), + HTMLURL: String("https://github.com/octocat"), + FollowersURL: String("https://api.github.com/users/octocat/followers"), + FollowingURL: String("https://api.github.com/users/octocat/following{/other_user}"), + GistsURL: String("https://api.github.com/users/octocat/gists{/gist_id}"), + StarredURL: String("https://api.github.com/users/octocat/starred{/owner}{/repo}"), + SubscriptionsURL: String("https://api.github.com/users/octocat/subscriptions"), + OrganizationsURL: String("https://api.github.com/users/octocat/orgs"), + ReposURL: String("https://api.github.com/users/octocat/repos"), + EventsURL: String("https://api.github.com/users/octocat/events{/privacy}"), + ReceivedEventsURL: String("https://api.github.com/users/octocat/received_events"), + Type: String("User"), + SiteAdmin: Bool(false), + }, + AssigningTeam: &Team{ + ID: Int64(1), + NodeID: String("MDQ6VGVhbTE="), + URL: String("https://api.github.com/teams/1"), + HTMLURL: String("https://github.com/orgs/github/teams/justice-league"), + Name: String("Justice League"), + Slug: String("justice-league"), + Description: String("A great team."), + Privacy: String("closed"), + Permission: String("admin"), + MembersURL: String("https://api.github.com/teams/1/members{/member}"), + RepositoriesURL: String("https://api.github.com/teams/1/repos"), + Parent: nil, + }, + CreatedAt: &createdAt1, + UpdatedAt: &updatedAt1, + PendingCancellationDate: nil, + LastActivityAt: &lastActivityAt1, + LastActivityEditor: String("vscode/1.77.3/copilot/1.86.82"), + PlanType: String("business"), + }, + { + Assignee: &User{ + Login: String("octokitten"), + ID: Int64(1), + NodeID: String("MDQ76VNlcjE="), + AvatarURL: String("https://github.com/images/error/octokitten_happy.gif"), + GravatarID: String(""), + URL: String("https://api.github.com/users/octokitten"), + HTMLURL: String("https://github.com/octokitten"), + FollowersURL: String("https://api.github.com/users/octokitten/followers"), + FollowingURL: String("https://api.github.com/users/octokitten/following{/other_user}"), + GistsURL: String("https://api.github.com/users/octokitten/gists{/gist_id}"), + StarredURL: String("https://api.github.com/users/octokitten/starred{/owner}{/repo}"), + SubscriptionsURL: String("https://api.github.com/users/octokitten/subscriptions"), + OrganizationsURL: String("https://api.github.com/users/octokitten/orgs"), + ReposURL: String("https://api.github.com/users/octokitten/repos"), + EventsURL: String("https://api.github.com/users/octokitten/events{/privacy}"), + ReceivedEventsURL: String("https://api.github.com/users/octokitten/received_events"), + Type: String("User"), + SiteAdmin: Bool(false), + }, + AssigningTeam: nil, + CreatedAt: &createdAt2, + UpdatedAt: &updatedAt2, + PendingCancellationDate: String("2021-11-01"), + LastActivityAt: &lastActivityAt2, + LastActivityEditor: String("vscode/1.77.3/copilot/1.86.82"), + PlanType: nil, + }, + }, + } + + if !cmp.Equal(got, want) { + log.Printf("got: %+v", got.Seats[1]) + log.Printf("want: %+v", want.Seats[1]) + t.Errorf("Copilot.ListCopilotEnterpriseSeats returned %+v, want %+v", got, want) + } + + const methodName = "ListCopilotEnterpriseSeats" + + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Copilot.ListCopilotEnterpriseSeats(ctx, "\n", opts) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Copilot.ListCopilotEnterpriseSeats(ctx, "e", opts) + if got != nil { + t.Errorf("Copilot.ListCopilotEnterpriseSeats returned %+v, want nil", got) + } + return resp, err + }) +} + func TestCopilotService_AddCopilotTeams(t *testing.T) { t.Parallel() client, mux, _ := setup(t) From 9b203ca45c00b7f10cf801dde661bcce4e60390f Mon Sep 17 00:00:00 2001 From: Clemens W Date: Thu, 10 Oct 2024 17:54:55 +0200 Subject: [PATCH 09/11] Ran scripts --- github/copilot.go | 10 +++++----- github/github-accessors.go | 24 ++++++++++++++++++++++++ github/github-accessors_test.go | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 5 deletions(-) diff --git a/github/copilot.go b/github/copilot.go index 34f2516eb18..bea299212de 100644 --- a/github/copilot.go +++ b/github/copilot.go @@ -220,7 +220,7 @@ func (s *CopilotService) ListCopilotSeats(ctx context.Context, org string, opts // // To paginate through all seats, populate 'Page' with the number of the last page. // -// GitHub API docs: https://docs.github.com/en/rest/copilot/copilot-user-management?apiVersion=2022-11-28#list-all-copilot-seat-assignments-for-an-enterprise +// GitHub API docs: https://docs.github.com/rest/copilot/copilot-user-management#list-all-copilot-seat-assignments-for-an-enterprise // //meta:operation GET /enterprises/{enterprise}/copilot/billing/seats func (s *CopilotService) ListCopilotEnterpriseSeats(ctx context.Context, enterprise string, opts *ListOptions) (*ListCopilotSeatsResponse, *Response, error) { @@ -380,7 +380,7 @@ func (s *CopilotService) GetSeatDetails(ctx context.Context, org, user string) ( // GetOrganizationUsage gets daily breakdown of aggregated usage metrics for Copilot completions and Copilot Chat in the IDE across an organization. // -// GitHub API docs: https://docs.github.com/en/rest/copilot/copilot-usage#get-a-summary-of-copilot-usage-for-organization-members +// GitHub API docs: https://docs.github.com/rest/copilot/copilot-usage#get-a-summary-of-copilot-usage-for-organization-members // //meta:operation GET /orgs/{org}/copilot/usage func (s *CopilotService) GetOrganizationUsage(ctx context.Context, org string, opts *CopilotUsageSummaryListOptions) ([]*CopilotUsageSummary, *Response, error) { @@ -406,7 +406,7 @@ func (s *CopilotService) GetOrganizationUsage(ctx context.Context, org string, o // GetEnterpriseUsage gets daily breakdown of aggregated usage metrics for Copilot completions and Copilot Chat in the IDE across an enterprise. // -// GitHub API docs: https://docs.github.com/en/rest/copilot/copilot-usage?apiVersion=2022-11-28#get-a-summary-of-copilot-usage-for-enterprise-members +// GitHub API docs: https://docs.github.com/rest/copilot/copilot-usage#get-a-summary-of-copilot-usage-for-enterprise-members // //meta:operation GET /enterprises/{enterprise}/copilot/usage func (s *CopilotService) GetEnterpriseUsage(ctx context.Context, enterprise string, opts *CopilotUsageSummaryListOptions) ([]*CopilotUsageSummary, *Response, error) { @@ -432,7 +432,7 @@ func (s *CopilotService) GetEnterpriseUsage(ctx context.Context, enterprise stri // GetEnterpriseTeamUsage gets daily breakdown of aggregated usage metrics for Copilot completions and Copilot Chat in the IDE for a team in the enterprise. // -// GitHub API docs: https://docs.github.com/en/rest/copilot/copilot-usage?apiVersion=2022-11-28#get-a-summary-of-copilot-usage-for-an-enterprise-team +// GitHub API docs: https://docs.github.com/rest/copilot/copilot-usage#get-a-summary-of-copilot-usage-for-an-enterprise-team // //meta:operation GET /enterprises/{enterprise}/team/{team_slug}/copilot/usage func (s *CopilotService) GetEnterpriseTeamUsage(ctx context.Context, enterprise string, team string, opts *CopilotUsageSummaryListOptions) ([]*CopilotUsageSummary, *Response, error) { @@ -458,7 +458,7 @@ func (s *CopilotService) GetEnterpriseTeamUsage(ctx context.Context, enterprise // GetOrganizationTeamUsage gets daily breakdown of aggregated usage metrics for Copilot completions and Copilot Chat in the IDE for a team in the organization. // -// GitHub API docs: https://docs.github.com/en/rest/copilot/copilot-usage?apiVersion=2022-11-28#get-a-summary-of-copilot-usage-for-a-team +// GitHub API docs: https://docs.github.com/rest/copilot/copilot-usage#get-a-summary-of-copilot-usage-for-a-team // //meta:operation GET /orgs/{org}/team/{team_slug}/copilot/usage func (s *CopilotService) GetOrganizationTeamUsage(ctx context.Context, org string, team string, opts *CopilotUsageSummaryListOptions) ([]*CopilotUsageSummary, *Response, error) { diff --git a/github/github-accessors.go b/github/github-accessors.go index f3a16c10ae1..19a9929f817 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -4110,6 +4110,14 @@ func (c *CopilotSeatDetails) GetPendingCancellationDate() string { return *c.PendingCancellationDate } +// GetPlanType returns the PlanType field if it's non-nil, zero value otherwise. +func (c *CopilotSeatDetails) GetPlanType() string { + if c == nil || c.PlanType == nil { + return "" + } + return *c.PlanType +} + // GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise. func (c *CopilotSeatDetails) GetUpdatedAt() Timestamp { if c == nil || c.UpdatedAt == nil { @@ -4118,6 +4126,22 @@ func (c *CopilotSeatDetails) GetUpdatedAt() Timestamp { return *c.UpdatedAt } +// GetSince returns the Since field if it's non-nil, zero value otherwise. +func (c *CopilotUsageSummaryListOptions) GetSince() time.Time { + if c == nil || c.Since == nil { + return time.Time{} + } + return *c.Since +} + +// GetUntil returns the Until field if it's non-nil, zero value otherwise. +func (c *CopilotUsageSummaryListOptions) GetUntil() time.Time { + if c == nil || c.Until == nil { + return time.Time{} + } + return *c.Until +} + // GetCompletedAt returns the CompletedAt field if it's non-nil, zero value otherwise. func (c *CreateCheckRunOptions) GetCompletedAt() Timestamp { if c == nil || c.CompletedAt == nil { diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index 2086baa0fda..5756c968c4a 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -5326,6 +5326,17 @@ func TestCopilotSeatDetails_GetPendingCancellationDate(tt *testing.T) { c.GetPendingCancellationDate() } +func TestCopilotSeatDetails_GetPlanType(tt *testing.T) { + tt.Parallel() + var zeroValue string + c := &CopilotSeatDetails{PlanType: &zeroValue} + c.GetPlanType() + c = &CopilotSeatDetails{} + c.GetPlanType() + c = nil + c.GetPlanType() +} + func TestCopilotSeatDetails_GetUpdatedAt(tt *testing.T) { tt.Parallel() var zeroValue Timestamp @@ -5337,6 +5348,28 @@ func TestCopilotSeatDetails_GetUpdatedAt(tt *testing.T) { c.GetUpdatedAt() } +func TestCopilotUsageSummaryListOptions_GetSince(tt *testing.T) { + tt.Parallel() + var zeroValue time.Time + c := &CopilotUsageSummaryListOptions{Since: &zeroValue} + c.GetSince() + c = &CopilotUsageSummaryListOptions{} + c.GetSince() + c = nil + c.GetSince() +} + +func TestCopilotUsageSummaryListOptions_GetUntil(tt *testing.T) { + tt.Parallel() + var zeroValue time.Time + c := &CopilotUsageSummaryListOptions{Until: &zeroValue} + c.GetUntil() + c = &CopilotUsageSummaryListOptions{} + c.GetUntil() + c = nil + c.GetUntil() +} + func TestCreateCheckRunOptions_GetCompletedAt(tt *testing.T) { tt.Parallel() var zeroValue Timestamp From 6cb4959cf2d660cefcb3a3fcace27b78efbc4b5c Mon Sep 17 00:00:00 2001 From: Clemens W Date: Thu, 10 Oct 2024 18:02:49 +0200 Subject: [PATCH 10/11] Possible big numbers converted to int64 --- github/copilot.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/github/copilot.go b/github/copilot.go index bea299212de..ef04cd90e43 100644 --- a/github/copilot.go +++ b/github/copilot.go @@ -77,23 +77,23 @@ type CopilotUsageSummaryListOptions struct { type CopilotUsageBreakdown struct { Language string `json:"language"` Editor string `json:"editor"` - SuggestionsCount int `json:"suggestions_count"` - AcceptancesCount int `json:"acceptances_count"` - LinesSuggested int `json:"lines_suggested"` - LinesAccepted int `json:"lines_accepted"` + SuggestionsCount int64 `json:"suggestions_count"` + AcceptancesCount int64 `json:"acceptances_count"` + LinesSuggested int64 `json:"lines_suggested"` + LinesAccepted int64 `json:"lines_accepted"` ActiveUsers int `json:"active_users"` } // CopilotUsageSummary represents the daily breakdown of aggregated usage metrics for Copilot completions and Copilot Chat in the IDE across an organization. type CopilotUsageSummary struct { Day string `json:"day"` - TotalSuggestionsCount int `json:"total_suggestions_count"` - TotalAcceptancesCount int `json:"total_acceptances_count"` - TotalLinesSuggested int `json:"total_lines_suggested"` - TotalLinesAccepted int `json:"total_lines_accepted"` - TotalActiveUsers int `json:"total_active_users"` - TotalChatAcceptances int `json:"total_chat_acceptances"` - TotalChatTurns int `json:"total_chat_turns"` + TotalSuggestionsCount int64 `json:"total_suggestions_count"` + TotalAcceptancesCount int64 `json:"total_acceptances_count"` + TotalLinesSuggested int64 `json:"total_lines_suggested"` + TotalLinesAccepted int64 `json:"total_lines_accepted"` + TotalActiveUsers int64 `json:"total_active_users"` + TotalChatAcceptances int64 `json:"total_chat_acceptances"` + TotalChatTurns int64 `json:"total_chat_turns"` TotalActiveChatUsers int `json:"total_active_chat_users"` Breakdown []*CopilotUsageBreakdown `json:"breakdown"` } @@ -435,7 +435,7 @@ func (s *CopilotService) GetEnterpriseUsage(ctx context.Context, enterprise stri // GitHub API docs: https://docs.github.com/rest/copilot/copilot-usage#get-a-summary-of-copilot-usage-for-an-enterprise-team // //meta:operation GET /enterprises/{enterprise}/team/{team_slug}/copilot/usage -func (s *CopilotService) GetEnterpriseTeamUsage(ctx context.Context, enterprise string, team string, opts *CopilotUsageSummaryListOptions) ([]*CopilotUsageSummary, *Response, error) { +func (s *CopilotService) GetEnterpriseTeamUsage(ctx context.Context, enterprise, team string, opts *CopilotUsageSummaryListOptions) ([]*CopilotUsageSummary, *Response, error) { u := fmt.Sprintf("enterprises/%v/team/%v/copilot/usage", enterprise, team) u, err := addOptions(u, opts) if err != nil { From af0e1402f046eb9a917e7e5b38ed43fedd8af909 Mon Sep 17 00:00:00 2001 From: Clemens W Date: Thu, 10 Oct 2024 19:02:19 +0200 Subject: [PATCH 11/11] Fix typing in method --- github/copilot.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/github/copilot.go b/github/copilot.go index ef04cd90e43..75e97770be6 100644 --- a/github/copilot.go +++ b/github/copilot.go @@ -461,7 +461,7 @@ func (s *CopilotService) GetEnterpriseTeamUsage(ctx context.Context, enterprise, // GitHub API docs: https://docs.github.com/rest/copilot/copilot-usage#get-a-summary-of-copilot-usage-for-a-team // //meta:operation GET /orgs/{org}/team/{team_slug}/copilot/usage -func (s *CopilotService) GetOrganizationTeamUsage(ctx context.Context, org string, team string, opts *CopilotUsageSummaryListOptions) ([]*CopilotUsageSummary, *Response, error) { +func (s *CopilotService) GetOrganizationTeamUsage(ctx context.Context, org, team string, opts *CopilotUsageSummaryListOptions) ([]*CopilotUsageSummary, *Response, error) { u := fmt.Sprintf("orgs/%v/team/%v/copilot/usage", org, team) u, err := addOptions(u, opts) if err != nil {