diff --git a/github/copilot.go b/github/copilot.go index d9e6c601874..876ba8c4a16 100644 --- a/github/copilot.go +++ b/github/copilot.go @@ -10,6 +10,7 @@ import ( "encoding/json" "errors" "fmt" + "net/http" "time" ) @@ -800,3 +801,32 @@ func (s *CopilotService) GetOrganizationUsersMetricsReport(ctx context.Context, return report, resp, nil } + +// DownloadCopilotMetrics downloads a Copilot metrics report from the provided download link +// and returns the metric data. This can be used to download metrics from a link returned by +// GetEnterpriseDailyMetricsReport, GetEnterpriseMetricsReport, GetEnterpriseUsersDailyMetricsReport, +// GetEnterpriseUsersMetricsReport, GetOrganizationDailyMetricsReport, GetOrganizationMetricsReport, +// GetOrganizationUsersDailyMetricsReport, GetOrganizationUsersMetricsReport. +func (s *CopilotService) DownloadCopilotMetrics(ctx context.Context, url string) ([]*CopilotMetrics, *Response, error) { + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return nil, nil, err + } + + resp, err := s.client.client.Do(req) + if err != nil { + return nil, nil, err + } + defer resp.Body.Close() + + if err := CheckResponse(resp); err != nil { + return nil, newResponse(resp), err + } + + var metrics []*CopilotMetrics + if err := json.NewDecoder(resp.Body).Decode(&metrics); err != nil { + return nil, newResponse(resp), err + } + + return metrics, newResponse(resp), nil +} diff --git a/github/copilot_test.go b/github/copilot_test.go index eb87edef7cd..81c1f06cf13 100644 --- a/github/copilot_test.go +++ b/github/copilot_test.go @@ -2778,3 +2778,139 @@ func TestCopilotService_GetOrganizationUsersMetricsReport(t *testing.T) { return resp, err }) } + +func TestCopilotService_DownloadCopilotMetrics(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/path/to/download", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{ + "date": "2023-01-01", + "total_active_users": 100, + "total_engaged_users": 50, + "copilot_ide_code_completions": { + "total_engaged_users": 50, + "languages": [ + { + "name": "go", + "total_engaged_users": 10 + } + ], + "editors": [ + { + "name": "vscode", + "total_engaged_users": 10, + "models": [ + { + "name": "model1", + "is_custom_model": false, + "custom_model_training_date": null, + "total_engaged_users": 10, + "languages": [ + { + "name": "go", + "total_engaged_users": 10, + "total_code_suggestions": 100, + "total_code_acceptances": 50, + "total_code_lines_suggested": 1000, + "total_code_lines_accepted": 500 + } + ] + } + ] + } + ] + } + }]`) + }) + + ctx := t.Context() + url := client.BaseURL.String() + "path/to/download" + got, resp, err := client.Copilot.DownloadCopilotMetrics(ctx, url) + if err != nil { + t.Errorf("Copilot.DownloadCopilotMetrics returned error: %v", err) + } + if resp.StatusCode != http.StatusOK { + t.Errorf("Copilot.DownloadCopilotMetrics returned status code: %v", resp.StatusCode) + } + + want := []*CopilotMetrics{ + { + Date: "2023-01-01", + TotalActiveUsers: Ptr(100), + TotalEngagedUsers: Ptr(50), + CopilotIDECodeCompletions: &CopilotIDECodeCompletions{ + TotalEngagedUsers: 50, + Languages: []*CopilotIDECodeCompletionsLanguage{ + { + Name: "go", + TotalEngagedUsers: 10, + }, + }, + Editors: []*CopilotIDECodeCompletionsEditor{ + { + Name: "vscode", + TotalEngagedUsers: 10, + Models: []*CopilotIDECodeCompletionsModel{ + { + Name: "model1", + IsCustomModel: false, + TotalEngagedUsers: 10, + Languages: []*CopilotIDECodeCompletionsModelLanguage{ + { + Name: "go", + TotalEngagedUsers: 10, + TotalCodeSuggestions: 100, + TotalCodeAcceptances: 50, + TotalCodeLinesSuggested: 1000, + TotalCodeLinesAccepted: 500, + }, + }, + }, + }, + }, + }, + }, + }, + } + + if !cmp.Equal(got, want) { + t.Errorf("Copilot.DownloadCopilotMetrics returned %+v, want %+v", got, want) + } + + // Test unexpected status code + mux.HandleFunc("/path/to/download/error", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNotFound) + }) + + urlErr := client.BaseURL.String() + "path/to/download/error" + _, _, err = client.Copilot.DownloadCopilotMetrics(ctx, urlErr) + if err == nil { + t.Error("Copilot.DownloadCopilotMetrics expected error but got none") + } + + // Test invalid URL (fails http.NewRequestWithContext) + _, _, err = client.Copilot.DownloadCopilotMetrics(ctx, "\n") + if err == nil { + t.Error("Copilot.DownloadCopilotMetrics expected error for invalid URL, got none") + } + + // Test invalid scheme (fails client.Do) + _, _, err = client.Copilot.DownloadCopilotMetrics(ctx, "invalid-scheme://test") + if err == nil { + t.Error("Copilot.DownloadCopilotMetrics expected error for invalid scheme, got none") + } + + // Test json decoding error + mux.HandleFunc("/path/to/download/badjson", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{invalid JSON`) + }) + urlBadJSON := client.BaseURL.String() + "path/to/download/badjson" + _, _, err = client.Copilot.DownloadCopilotMetrics(ctx, urlBadJson) + if err == nil { + t.Error("Copilot.DownloadCopilotMetrics expected error for bad JSON, got none") + } +} diff --git a/tools/metadata/metadata.go b/tools/metadata/metadata.go index dd4bcfe47a4..1d14d223238 100644 --- a/tools/metadata/metadata.go +++ b/tools/metadata/metadata.go @@ -547,4 +547,5 @@ var skipServiceMethod = map[string]bool{ "BillingService.GetOrganizationStorageBilling": true, "BillingService.GetPackagesBilling": true, "BillingService.GetStorageBilling": true, + "CopilotService.DownloadCopilotMetrics": true, }