From 91caef47781dff570ff344f819717efd7842406e Mon Sep 17 00:00:00 2001 From: JulesFaucherre Date: Wed, 5 Jul 2023 16:27:40 +0200 Subject: [PATCH] feat: Added `circleci telemetry enable` and `circleci telemetry disable` commands --- cmd/root.go | 1 + cmd/root_test.go | 2 +- cmd/telemetry.go | 73 +++++++++++++++++++++ cmd/telemetry_test.go | 143 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 218 insertions(+), 1 deletion(-) create mode 100644 cmd/telemetry.go create mode 100644 cmd/telemetry_test.go diff --git a/cmd/root.go b/cmd/root.go index 12ce075e4..8139a2bd2 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -175,6 +175,7 @@ func MakeCommands() *cobra.Command { rootCmd.AddCommand(newAdminCommand(rootOptions)) rootCmd.AddCommand(newCompletionCommand()) rootCmd.AddCommand(newEnvCmd()) + rootCmd.AddCommand(newTelemetryCommand(rootOptions)) flags := rootCmd.PersistentFlags() diff --git a/cmd/root_test.go b/cmd/root_test.go index c23982b18..515a9294e 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -16,7 +16,7 @@ var _ = Describe("Root", func() { Describe("subcommands", func() { It("can create commands", func() { commands := cmd.MakeCommands() - Expect(len(commands.Commands())).To(Equal(24)) + Expect(len(commands.Commands())).To(Equal(25)) }) }) diff --git a/cmd/telemetry.go b/cmd/telemetry.go new file mode 100644 index 000000000..df2be673c --- /dev/null +++ b/cmd/telemetry.go @@ -0,0 +1,73 @@ +package cmd + +import ( + "os" + + "github.com/CircleCI-Public/circleci-cli/api/rest" + "github.com/CircleCI-Public/circleci-cli/settings" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +func newTelemetryCommand(config *settings.Config) *cobra.Command { + apiClient := telemetryCircleCIAPI{ + cli: rest.NewFromConfig(config.Host, config), + } + + telemetryEnable := &cobra.Command{ + Use: "enable", + Short: "Allow telemetry events to be sent to CircleCI servers", + RunE: func(_ *cobra.Command, _ []string) error { + return setIsTelemetryActive(apiClient, true) + }, + Args: cobra.ExactArgs(0), + } + + telemetryDisable := &cobra.Command{ + Use: "disable", + Short: "Make sure no telemetry events is sent to CircleCI servers", + RunE: func(_ *cobra.Command, _ []string) error { + return setIsTelemetryActive(apiClient, false) + }, + Args: cobra.ExactArgs(0), + } + + telemetryCommand := &cobra.Command{ + Use: "telemetry", + Short: "Configure telemetry preferences", + Long: `Configure telemetry preferences. + +Note: If you have not configured your telemetry preferences and call the CLI with a closed stdin, telemetry will be disabled`, + } + + telemetryCommand.AddCommand(telemetryEnable) + telemetryCommand.AddCommand(telemetryDisable) + + return telemetryCommand +} + +func setIsTelemetryActive(apiClient telemetryAPIClient, isActive bool) error { + settings := settings.TelemetrySettings{} + if err := settings.Load(); err != nil && !os.IsNotExist(err) { + return errors.Wrap(err, "Loading telemetry configuration") + } + + settings.HasAnsweredPrompt = true + settings.IsActive = isActive + + if settings.UniqueID == "" { + settings.UniqueID = createUUID() + } + + if settings.UserID == "" { + if myID, err := apiClient.getMyUserId(); err == nil { + settings.UserID = myID + } + } + + if err := settings.Write(); err != nil { + return errors.Wrap(err, "Writing telemetry configuration") + } + + return nil +} diff --git a/cmd/telemetry_test.go b/cmd/telemetry_test.go new file mode 100644 index 000000000..35eeb848b --- /dev/null +++ b/cmd/telemetry_test.go @@ -0,0 +1,143 @@ +package cmd + +import ( + "path/filepath" + "testing" + + "github.com/CircleCI-Public/circleci-cli/settings" + "github.com/spf13/afero" + "gotest.tools/v3/assert" +) + +func TestSetIsTelemetryActive(t *testing.T) { + type args struct { + apiClient telemetryAPIClient + isActive bool + settings *settings.TelemetrySettings + } + type want struct { + settings *settings.TelemetrySettings + } + + type testCase struct { + name string + args args + want want + } + + userId := "user-id" + uniqueId := "unique-id" + + testCases := []testCase{ + { + name: "Enabling telemetry with settings should just update the is active field", + args: args{ + apiClient: telemetryTestAPIClient{}, + isActive: true, + settings: &settings.TelemetrySettings{ + IsActive: false, + HasAnsweredPrompt: true, + UniqueID: uniqueId, + UserID: userId, + }, + }, + want: want{ + settings: &settings.TelemetrySettings{ + IsActive: true, + HasAnsweredPrompt: true, + UniqueID: uniqueId, + UserID: userId, + }, + }, + }, + { + name: "Enabling telemetry without settings should fill the settings fields", + args: args{ + apiClient: telemetryTestAPIClient{id: userId, err: nil}, + isActive: true, + settings: nil, + }, + want: want{ + settings: &settings.TelemetrySettings{ + IsActive: true, + HasAnsweredPrompt: true, + UniqueID: uniqueId, + UserID: userId, + }, + }, + }, + { + name: "Disabling telemetry with settings should just update the is active field", + args: args{ + apiClient: telemetryTestAPIClient{}, + isActive: false, + settings: &settings.TelemetrySettings{ + IsActive: true, + HasAnsweredPrompt: true, + UniqueID: uniqueId, + UserID: userId, + }, + }, + want: want{ + settings: &settings.TelemetrySettings{ + IsActive: false, + HasAnsweredPrompt: true, + UniqueID: uniqueId, + UserID: userId, + }, + }, + }, + { + name: "Enabling telemetry without settings should fill the settings fields", + args: args{ + apiClient: telemetryTestAPIClient{id: userId, err: nil}, + isActive: false, + settings: nil, + }, + want: want{ + settings: &settings.TelemetrySettings{ + IsActive: false, + HasAnsweredPrompt: true, + UniqueID: uniqueId, + UserID: userId, + }, + }, + }, + } + + // Mock create UUID + oldUUIDCreate := createUUID + createUUID = func() string { return uniqueId } + defer (func() { createUUID = oldUUIDCreate })() + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + // Mock FS + oldFS := settings.FS.Fs + settings.FS.Fs = afero.NewMemMapFs() + defer (func() { settings.FS.Fs = oldFS })() + + if tt.args.settings != nil { + err := tt.args.settings.Write() + assert.NilError(t, err) + } + + err := setIsTelemetryActive(tt.args.apiClient, tt.args.isActive) + assert.NilError(t, err) + + exist, err := settings.FS.Exists(filepath.Join(settings.SettingsPath(), "telemetry.yml")) + assert.NilError(t, err) + if tt.want.settings == nil { + assert.Equal(t, exist, false) + } else { + assert.Equal(t, exist, true) + + loadedSettings := &settings.TelemetrySettings{} + err := loadedSettings.Load() + assert.NilError(t, err) + + assert.DeepEqual(t, tt.want.settings, loadedSettings) + } + }) + } +}