diff --git a/pkg/cmd/api/api.go b/pkg/cmd/api/api.go index c1bdc911fd4..cb2b86dcc0b 100644 --- a/pkg/cmd/api/api.go +++ b/pkg/cmd/api/api.go @@ -18,6 +18,7 @@ import ( "github.com/MakeNowJust/heredoc" "github.com/cli/cli/v2/api" "github.com/cli/cli/v2/internal/gh" + "github.com/cli/cli/v2/internal/gh/ghtelemetry" "github.com/cli/cli/v2/internal/ghinstance" "github.com/cli/cli/v2/internal/ghrepo" "github.com/cli/cli/v2/pkg/cmd/factory" @@ -34,13 +35,14 @@ const ( ) type ApiOptions struct { - AppVersion string - InvokingAgent string - BaseRepo func() (ghrepo.Interface, error) - Branch func() (string, error) - Config func() (gh.Config, error) - HttpClient func() (*http.Client, error) - IO *iostreams.IOStreams + AppVersion string + InvokingAgent string + BaseRepo func() (ghrepo.Interface, error) + Branch func() (string, error) + Config func() (gh.Config, error) + HttpClient func(api.HTTPClientOptions) (*http.Client, error) + IO *iostreams.IOStreams + TelemetryDisabler ghtelemetry.Disabler Hostname string RequestMethod string @@ -65,12 +67,14 @@ type ApiOptions struct { func NewCmdApi(f *cmdutil.Factory, runF func(*ApiOptions) error) *cobra.Command { opts := ApiOptions{ - AppVersion: f.AppVersion, - InvokingAgent: f.InvokingAgent, - BaseRepo: f.BaseRepo, - Branch: f.Branch, - Config: f.Config, - IO: f.IOStreams, + AppVersion: f.AppVersion, + InvokingAgent: f.InvokingAgent, + BaseRepo: f.BaseRepo, + Branch: f.Branch, + Config: f.Config, + HttpClient: api.NewHTTPClient, + IO: f.IOStreams, + TelemetryDisabler: f.TelemetryDisabler, } cmd := &cobra.Command{ @@ -389,25 +393,23 @@ func apiRun(opts *ApiOptions) error { } if opts.HttpClient == nil { - opts.HttpClient = func() (*http.Client, error) { - log := opts.IO.ErrOut - if opts.Verbose { - log = opts.IO.Out - } - opts := api.HTTPClientOptions{ - AppVersion: opts.AppVersion, - InvokingAgent: opts.InvokingAgent, - CacheTTL: opts.CacheTTL, - Config: cfg.Authentication(), - EnableCache: opts.CacheTTL > 0, - Log: log, - LogColorize: opts.IO.ColorEnabled(), - LogVerboseHTTP: opts.Verbose, - } - return api.NewHTTPClient(opts) - } + opts.HttpClient = api.NewHTTPClient } - httpClient, err := opts.HttpClient() + log := opts.IO.ErrOut + if opts.Verbose { + log = opts.IO.Out + } + httpClient, err := opts.HttpClient(api.HTTPClientOptions{ + AppVersion: opts.AppVersion, + InvokingAgent: opts.InvokingAgent, + CacheTTL: opts.CacheTTL, + Config: cfg.Authentication(), + EnableCache: opts.CacheTTL > 0, + Log: log, + LogColorize: opts.IO.ColorEnabled(), + LogVerboseHTTP: opts.Verbose, + TelemetryDisabler: opts.TelemetryDisabler, + }) if err != nil { return err } diff --git a/pkg/cmd/api/api_test.go b/pkg/cmd/api/api_test.go index f5ba71cd324..e67ca517a76 100644 --- a/pkg/cmd/api/api_test.go +++ b/pkg/cmd/api/api_test.go @@ -14,11 +14,14 @@ import ( "time" "github.com/MakeNowJust/heredoc" + rootapi "github.com/cli/cli/v2/api" "github.com/cli/cli/v2/git" "github.com/cli/cli/v2/internal/config" "github.com/cli/cli/v2/internal/gh" + "github.com/cli/cli/v2/internal/gh/ghtelemetry" ghmock "github.com/cli/cli/v2/internal/gh/mock" "github.com/cli/cli/v2/internal/ghrepo" + "github.com/cli/cli/v2/internal/telemetry" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/iostreams" "github.com/cli/go-gh/v2/pkg/template" @@ -422,6 +425,35 @@ func Test_NewCmdApi_WindowsAbsPath(t *testing.T) { assert.EqualError(t, err, `invalid API endpoint: "C:\users\repos". Your shell might be rewriting URL paths as filesystem paths. To avoid this, omit the leading slash from the endpoint argument`) } +func TestNewCmdApiTelemetry(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + fmt.Fprint(w, "{}") + })) + t.Cleanup(server.Close) + + var payload telemetry.SendTelemetryPayload + recorder := telemetry.NewService(func(p telemetry.SendTelemetryPayload) { + payload = p + }) + recorder.Record(ghtelemetry.Event{Type: "command"}) + + ios, _, _, _ := iostreams.Test() + f := &cmdutil.Factory{ + Config: func() (gh.Config, error) { return config.NewMockConfig(), nil }, + IOStreams: ios, + TelemetryDisabler: recorder, + } + cmd := NewCmdApi(f, nil) + cmd.SetArgs([]string{server.URL}) + + _, err := cmd.ExecuteC() + require.NoError(t, err) + recorder.Flush() + + assert.Empty(t, payload.Events) +} + func Test_apiRun(t *testing.T) { tests := []struct { name string @@ -741,7 +773,7 @@ func Test_apiRun(t *testing.T) { tt.options.IO = ios tt.options.Config = func() (gh.Config, error) { return config.NewMockConfig(), nil } - tt.options.HttpClient = func() (*http.Client, error) { + tt.options.HttpClient = func(rootapi.HTTPClientOptions) (*http.Client, error) { var tr roundTripper = func(req *http.Request) (*http.Response, error) { resp := tt.httpResponse resp.Request = req @@ -810,7 +842,7 @@ func Test_apiRun_paginationREST(t *testing.T) { options := ApiOptions{ IO: ios, - HttpClient: func() (*http.Client, error) { + HttpClient: func(rootapi.HTTPClientOptions) (*http.Client, error) { var tr roundTripper = func(req *http.Request) (*http.Response, error) { resp := responses[requestCount] resp.Request = req @@ -882,7 +914,7 @@ func Test_apiRun_arrayPaginationREST(t *testing.T) { options := ApiOptions{ IO: ios, - HttpClient: func() (*http.Client, error) { + HttpClient: func(rootapi.HTTPClientOptions) (*http.Client, error) { var tr roundTripper = func(req *http.Request) (*http.Response, error) { resp := responses[requestCount] resp.Request = req @@ -954,7 +986,7 @@ func Test_apiRun_arrayPaginationREST_with_headers(t *testing.T) { options := ApiOptions{ IO: ios, - HttpClient: func() (*http.Client, error) { + HttpClient: func(rootapi.HTTPClientOptions) (*http.Client, error) { var tr roundTripper = func(req *http.Request) (*http.Response, error) { resp := responses[requestCount] resp.Request = req @@ -1023,7 +1055,7 @@ func Test_apiRun_paginationGraphQL(t *testing.T) { options := ApiOptions{ IO: ios, - HttpClient: func() (*http.Client, error) { + HttpClient: func(rootapi.HTTPClientOptions) (*http.Client, error) { var tr roundTripper = func(req *http.Request) (*http.Response, error) { resp := responses[requestCount] resp.Request = req @@ -1122,7 +1154,7 @@ func Test_apiRun_paginationGraphQL_slurp(t *testing.T) { options := ApiOptions{ IO: ios, - HttpClient: func() (*http.Client, error) { + HttpClient: func(rootapi.HTTPClientOptions) (*http.Client, error) { var tr roundTripper = func(req *http.Request) (*http.Response, error) { resp := responses[requestCount] resp.Request = req @@ -1234,7 +1266,7 @@ func Test_apiRun_paginated_template(t *testing.T) { options := ApiOptions{ IO: ios, - HttpClient: func() (*http.Client, error) { + HttpClient: func(rootapi.HTTPClientOptions) (*http.Client, error) { var tr roundTripper = func(req *http.Request) (*http.Response, error) { resp := responses[requestCount] resp.Request = req @@ -1293,7 +1325,7 @@ func Test_apiRun_DELETE(t *testing.T) { Config: func() (gh.Config, error) { return config.NewMockConfig(), nil }, - HttpClient: func() (*http.Client, error) { + HttpClient: func(rootapi.HTTPClientOptions) (*http.Client, error) { var tr roundTripper = func(req *http.Request) (*http.Response, error) { gotRequest = req return &http.Response{StatusCode: 204, Request: req}, nil @@ -1322,7 +1354,7 @@ func Test_apiRun_HEAD(t *testing.T) { Config: func() (gh.Config, error) { return config.NewMockConfig(), nil }, - HttpClient: func() (*http.Client, error) { + HttpClient: func(rootapi.HTTPClientOptions) (*http.Client, error) { var tr roundTripper = func(req *http.Request) (*http.Response, error) { return &http.Response{ StatusCode: 422, @@ -1393,7 +1425,7 @@ func Test_apiRun_inputFile(t *testing.T) { RawFields: []string{"a=b", "c=d"}, IO: ios, - HttpClient: func() (*http.Client, error) { + HttpClient: func(rootapi.HTTPClientOptions) (*http.Client, error) { var tr roundTripper = func(req *http.Request) (*http.Response, error) { var err error if bodyBytes, err = io.ReadAll(req.Body); err != nil { @@ -1889,7 +1921,7 @@ func Test_apiRun_acceptHeader(t *testing.T) { } var gotReq *http.Request - tt.options.HttpClient = func() (*http.Client, error) { + tt.options.HttpClient = func(rootapi.HTTPClientOptions) (*http.Client, error) { var tr roundTripper = func(req *http.Request) (*http.Response, error) { gotReq = req resp := &http.Response{ diff --git a/pkg/cmd/factory/default.go b/pkg/cmd/factory/default.go index cc10075f203..202f7ae556c 100644 --- a/pkg/cmd/factory/default.go +++ b/pkg/cmd/factory/default.go @@ -32,6 +32,7 @@ func New(appVersion string, invokingAgent string, cfgFunc func() (gh.Config, err } f.IOStreams = ios + f.TelemetryDisabler = telemetryDisabler f.HttpClient = HttpClientFunc(cfgFunc, ios, appVersion, invokingAgent, telemetryDisabler) f.PlainHttpClient = plainHttpClientFunc(ios, appVersion, invokingAgent, telemetryDisabler) f.ExternalHttpClient = externalHttpClientFunc(ios, appVersion) diff --git a/pkg/cmdutil/factory.go b/pkg/cmdutil/factory.go index 200314038b2..abbcdf0408b 100644 --- a/pkg/cmdutil/factory.go +++ b/pkg/cmdutil/factory.go @@ -7,6 +7,7 @@ import ( "github.com/cli/cli/v2/git" "github.com/cli/cli/v2/internal/browser" "github.com/cli/cli/v2/internal/gh" + "github.com/cli/cli/v2/internal/gh/ghtelemetry" "github.com/cli/cli/v2/internal/ghrepo" "github.com/cli/cli/v2/internal/prompter" "github.com/cli/cli/v2/pkg/extensions" @@ -18,11 +19,12 @@ type Factory struct { ExecutablePath string InvokingAgent string - Browser browser.Browser - ExtensionManager extensions.ExtensionManager - GitClient *git.Client - IOStreams *iostreams.IOStreams - Prompter prompter.Prompter + Browser browser.Browser + ExtensionManager extensions.ExtensionManager + GitClient *git.Client + IOStreams *iostreams.IOStreams + Prompter prompter.Prompter + TelemetryDisabler ghtelemetry.Disabler BaseRepo func() (ghrepo.Interface, error) Branch func() (string, error)