From bf0f6aa51ee92acae053a6ee53b5fb49693dc782 Mon Sep 17 00:00:00 2001 From: Sunny Guduru Date: Thu, 25 Apr 2024 09:39:56 -0700 Subject: [PATCH] feat: added cmd completed to commands (#200) --- cmd/analytics/analytics.go | 27 ++++++++ cmd/cmdtest.go | 15 +++++ cmd/config/config.go | 6 +- cmd/environments/environments.go | 6 +- cmd/environments/get_test.go | 2 +- cmd/flags/create_test.go | 2 +- cmd/flags/flags.go | 6 +- cmd/flags/get_test.go | 34 +++++++++- cmd/flags/update_test.go | 4 +- cmd/members/create_test.go | 4 +- cmd/members/invite_test.go | 6 +- cmd/members/members.go | 6 +- cmd/projects/create_test.go | 2 +- cmd/projects/list_test.go | 2 +- cmd/projects/projects.go | 6 +- cmd/root.go | 9 +++ internal/analytics/client.go | 104 ++++++++++++++++++++++++++++--- internal/analytics/events.go | 48 +++++--------- main.go | 4 +- 19 files changed, 223 insertions(+), 70 deletions(-) create mode 100644 cmd/analytics/analytics.go diff --git a/cmd/analytics/analytics.go b/cmd/analytics/analytics.go new file mode 100644 index 00000000..81fab0d3 --- /dev/null +++ b/cmd/analytics/analytics.go @@ -0,0 +1,27 @@ +package analytics + +import ( + "ldcli/cmd/cliflags" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/spf13/viper" +) + +func CmdRunEventProperties(cmd *cobra.Command, name string) map[string]interface{} { + baseURI := viper.GetString(cliflags.BaseURIFlag) + var flags []string + cmd.Flags().Visit(func(f *pflag.Flag) { + flags = append(flags, f.Name) + }) + + properties := map[string]interface{}{ + "name": name, + "action": cmd.CalledAs(), + "flags": flags, + } + if baseURI != cliflags.BaseURIDefault { + properties["baseURI"] = baseURI + } + return properties +} diff --git a/cmd/cmdtest.go b/cmd/cmdtest.go index 5c98b384..a3999f9b 100644 --- a/cmd/cmdtest.go +++ b/cmd/cmdtest.go @@ -6,8 +6,10 @@ import ( "os" "testing" + "github.com/spf13/viper" "github.com/stretchr/testify/require" + "ldcli/cmd/cliflags" "ldcli/internal/analytics" ) @@ -35,9 +37,22 @@ func CallCmd( err = rootCmd.Execute() if err != nil { + tracker.SendCommandCompletedEvent( + viper.GetString(cliflags.AccessTokenFlag), + viper.GetString(cliflags.BaseURIFlag), + viper.GetBool(cliflags.AnalyticsOptOut), + analytics.ERROR, + ) return nil, err } + tracker.SendCommandCompletedEvent( + viper.GetString(cliflags.AccessTokenFlag), + viper.GetString(cliflags.BaseURIFlag), + viper.GetBool(cliflags.AnalyticsOptOut), + analytics.SUCCESS, + ) + out, err := io.ReadAll(b) require.NoError(t, err) diff --git a/cmd/config/config.go b/cmd/config/config.go index 7746c252..399db8b5 100644 --- a/cmd/config/config.go +++ b/cmd/config/config.go @@ -10,6 +10,7 @@ import ( "github.com/spf13/viper" "gopkg.in/yaml.v3" + cmdAnalytics "ldcli/cmd/analytics" "ldcli/cmd/cliflags" "ldcli/internal/analytics" "ldcli/internal/config" @@ -29,12 +30,11 @@ func NewConfigCmd(analyticsTracker analytics.Tracker) *cobra.Command { Short: "View and modify specific configuration values", Use: "config", PreRun: func(cmd *cobra.Command, args []string) { - analyticsTracker.SendEvent( + analyticsTracker.SendCommandRunEvent( viper.GetString(cliflags.AccessTokenFlag), viper.GetString(cliflags.BaseURIFlag), viper.GetBool(cliflags.AnalyticsOptOut), - "CLI Command Run", - analytics.CmdRunEventProperties(cmd, "config"), + cmdAnalytics.CmdRunEventProperties(cmd, "config"), ) }, } diff --git a/cmd/environments/environments.go b/cmd/environments/environments.go index cbda5f4a..04833832 100644 --- a/cmd/environments/environments.go +++ b/cmd/environments/environments.go @@ -4,6 +4,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" + cmdAnalytics "ldcli/cmd/analytics" "ldcli/cmd/cliflags" "ldcli/internal/analytics" "ldcli/internal/environments" @@ -18,12 +19,11 @@ func NewEnvironmentsCmd( Short: "Make requests (list, create, etc.) on environments", Long: "Make requests (list, create, etc.) on environments", PersistentPreRun: func(cmd *cobra.Command, args []string) { - analyticsTracker.SendEvent( + analyticsTracker.SendCommandRunEvent( viper.GetString(cliflags.AccessTokenFlag), viper.GetString(cliflags.BaseURIFlag), viper.GetBool(cliflags.AnalyticsOptOut), - "CLI Command Run", - analytics.CmdRunEventProperties(cmd, "environments"), + cmdAnalytics.CmdRunEventProperties(cmd, "environments"), ) }, } diff --git a/cmd/environments/get_test.go b/cmd/environments/get_test.go index 7c7c4ce2..9dba82ac 100644 --- a/cmd/environments/get_test.go +++ b/cmd/environments/get_test.go @@ -173,7 +173,7 @@ func TestGet(t *testing.T) { "base-uri", "environment", "project", - }) + }, analytics.SUCCESS) client := environments.MockClient{} client. On("Get", mockArgs...). diff --git a/cmd/flags/create_test.go b/cmd/flags/create_test.go index 799ee4aa..adc98ed8 100644 --- a/cmd/flags/create_test.go +++ b/cmd/flags/create_test.go @@ -156,7 +156,7 @@ func TestCreate(t *testing.T) { "base-uri", "data", "project", - }) + }, analytics.SUCCESS) client := flags.MockClient{} client. diff --git a/cmd/flags/flags.go b/cmd/flags/flags.go index 118f99d6..b50c56ce 100644 --- a/cmd/flags/flags.go +++ b/cmd/flags/flags.go @@ -4,6 +4,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" + cmdAnalytics "ldcli/cmd/analytics" "ldcli/cmd/cliflags" "ldcli/internal/analytics" "ldcli/internal/flags" @@ -15,12 +16,11 @@ func NewFlagsCmd(analyticsTracker analytics.Tracker, client flags.Client) (*cobr Short: "Make requests (list, create, etc.) on flags", Long: "Make requests (list, create, etc.) on flags", PersistentPreRun: func(cmd *cobra.Command, args []string) { - analyticsTracker.SendEvent( + analyticsTracker.SendCommandRunEvent( viper.GetString(cliflags.AccessTokenFlag), viper.GetString(cliflags.BaseURIFlag), viper.GetBool(cliflags.AnalyticsOptOut), - "CLI Command Run", - analytics.CmdRunEventProperties(cmd, "flags"), + cmdAnalytics.CmdRunEventProperties(cmd, "flags"), ) }, } diff --git a/cmd/flags/get_test.go b/cmd/flags/get_test.go index ee65f641..39320d63 100644 --- a/cmd/flags/get_test.go +++ b/cmd/flags/get_test.go @@ -133,7 +133,7 @@ func TestGet(t *testing.T) { "flag", "output", "project", - }) + }, analytics.SUCCESS) client := flags.MockClient{} client. @@ -156,4 +156,36 @@ func TestGet(t *testing.T) { _, err := cmd.CallCmd(t, clients, tracker, args) require.NoError(t, err) }) + + t.Run("will track analytics for api error", func(t *testing.T) { + tracker := analytics.MockedTracker( + "flags", + "get", + []string{ + "access-token", + "base-uri", + "environment", + "flag", + "project", + }, analytics.ERROR) + client := flags.MockClient{} + client. + On("Get", mockArgs...). + Return([]byte(`{}`), errors.NewError(`{"message": "An error"}`)) + clients := cmd.APIClients{ + FlagsClient: &client, + } + + args := []string{ + "flags", "get", + "--access-token", "testAccessToken", + "--base-uri", "http://test.com", + "--flag", "test-key", + "--project", "test-proj-key", + "--environment", "test-env-key", + } + + _, err := cmd.CallCmd(t, clients, tracker, args) + require.EqualError(t, err, "An error") + }) } diff --git a/cmd/flags/update_test.go b/cmd/flags/update_test.go index 70a1fde3..a8e2c999 100644 --- a/cmd/flags/update_test.go +++ b/cmd/flags/update_test.go @@ -139,7 +139,7 @@ func TestUpdate(t *testing.T) { "flag", "output", "project", - }) + }, analytics.SUCCESS) client := flags.MockClient{} client. @@ -267,7 +267,7 @@ func TestToggle(t *testing.T) { "flag", "output", "project", - }) + }, analytics.SUCCESS) client := flags.MockClient{} client. diff --git a/cmd/members/create_test.go b/cmd/members/create_test.go index feb2efcb..ad102cc5 100644 --- a/cmd/members/create_test.go +++ b/cmd/members/create_test.go @@ -131,8 +131,8 @@ func TestCreate(t *testing.T) { "access-token", "base-uri", "data", - "output", - }) + "output", + }, analytics.SUCCESS) client := members.MockClient{} client. diff --git a/cmd/members/invite_test.go b/cmd/members/invite_test.go index c98197d4..4e8e2d85 100644 --- a/cmd/members/invite_test.go +++ b/cmd/members/invite_test.go @@ -131,8 +131,8 @@ func TestInvite(t *testing.T) { "access-token", "base-uri", "emails", - "output", - }) + "output", + }, analytics.SUCCESS) client := members.MockClient{} client. @@ -225,7 +225,7 @@ func TestInviteWithOptionalRole(t *testing.T) { "emails", "output", "role", - }) + }, analytics.SUCCESS) client := members.MockClient{} client. diff --git a/cmd/members/members.go b/cmd/members/members.go index 57e74e1f..c8cbcdce 100644 --- a/cmd/members/members.go +++ b/cmd/members/members.go @@ -4,6 +4,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" + cmdAnalytics "ldcli/cmd/analytics" "ldcli/cmd/cliflags" "ldcli/internal/analytics" "ldcli/internal/members" @@ -15,12 +16,11 @@ func NewMembersCmd(analyticsTracker analytics.Tracker, client members.Client) (* Short: "Make requests (list, create, etc.) on members", Long: "Make requests (list, create, etc.) on members", PersistentPreRun: func(cmd *cobra.Command, args []string) { - analyticsTracker.SendEvent( + analyticsTracker.SendCommandRunEvent( viper.GetString(cliflags.AccessTokenFlag), viper.GetString(cliflags.BaseURIFlag), viper.GetBool(cliflags.AnalyticsOptOut), - "CLI Command Run", - analytics.CmdRunEventProperties(cmd, "members"), + cmdAnalytics.CmdRunEventProperties(cmd, "members"), ) }, } diff --git a/cmd/projects/create_test.go b/cmd/projects/create_test.go index 06cdeb10..0b9e62f1 100644 --- a/cmd/projects/create_test.go +++ b/cmd/projects/create_test.go @@ -158,7 +158,7 @@ func TestCreate(t *testing.T) { "access-token", "base-uri", "data", - }) + }, analytics.SUCCESS) client := projects.MockClient{} client. diff --git a/cmd/projects/list_test.go b/cmd/projects/list_test.go index d0ec5381..70ed2a0a 100644 --- a/cmd/projects/list_test.go +++ b/cmd/projects/list_test.go @@ -138,7 +138,7 @@ func TestList(t *testing.T) { []string{ "access-token", "base-uri", - }) + }, analytics.SUCCESS) client := projects.MockClient{} client. diff --git a/cmd/projects/projects.go b/cmd/projects/projects.go index 669abe9d..d2df0757 100644 --- a/cmd/projects/projects.go +++ b/cmd/projects/projects.go @@ -4,6 +4,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" + cmdAnalytics "ldcli/cmd/analytics" "ldcli/cmd/cliflags" "ldcli/internal/analytics" "ldcli/internal/projects" @@ -15,12 +16,11 @@ func NewProjectsCmd(analyticsTracker analytics.Tracker, client projects.Client) Short: "Make requests (list, create, etc.) on projects", Long: "Make requests (list, create, etc.) on projects", PersistentPreRun: func(cmd *cobra.Command, args []string) { - analyticsTracker.SendEvent( + analyticsTracker.SendCommandRunEvent( viper.GetString(cliflags.AccessTokenFlag), viper.GetString(cliflags.BaseURIFlag), viper.GetBool(cliflags.AnalyticsOptOut), - "CLI Command Run", - analytics.CmdRunEventProperties(cmd, "projects"), + cmdAnalytics.CmdRunEventProperties(cmd, "projects"), ) }, } diff --git a/cmd/root.go b/cmd/root.go index 9fb406ad..8339801a 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -168,9 +168,18 @@ func Execute(analyticsTracker analytics.Tracker, version string) { } err = rootCmd.Execute() + outcome := analytics.SUCCESS if err != nil { + outcome = analytics.ERROR fmt.Fprintln(os.Stderr, err.Error()) } + + analyticsTracker.SendCommandCompletedEvent( + viper.GetString(cliflags.AccessTokenFlag), + viper.GetString(cliflags.BaseURIDefault), + viper.GetBool(cliflags.AnalyticsOptOut), + outcome, + ) } // setFlagsFromConfig reads in the config file if it exists and uses any flag values for commands. diff --git a/internal/analytics/client.go b/internal/analytics/client.go index 17715646..0bce52c3 100644 --- a/internal/analytics/client.go +++ b/internal/analytics/client.go @@ -12,22 +12,29 @@ import ( ) type Tracker interface { - SendEvent( - accessToken string, + SendCommandRunEvent( + accessToken, baseURI string, optOut bool, - eventName string, properties map[string]interface{}, ) + SendCommandCompletedEvent( + accessToken, + baseURI string, + optOut bool, + outcome string, + ) } type Client struct { - HTTPClient *http.Client - wg sync.WaitGroup + ID string + HTTPClient *http.Client + sentRunEvent bool + wg sync.WaitGroup } // SendEvent makes an async request to track the given event with properties. -func (c *Client) SendEvent( +func (c *Client) sendEvent( accessToken string, baseURI string, optOut bool, @@ -37,6 +44,7 @@ func (c *Client) SendEvent( if optOut { return } + properties["id"] = c.ID input := struct { Event string `json:"event"` Properties map[string]interface{} `json:"properties"` @@ -83,27 +91,71 @@ func (c *Client) SendEvent( }() } +func (c *Client) SendCommandRunEvent( + accessToken, + baseURI string, + optOut bool, + properties map[string]interface{}, +) { + c.sendEvent( + accessToken, + baseURI, + optOut, + "CLI Command Run", + properties, + ) + if !optOut { + c.sentRunEvent = true + } +} + +func (c *Client) SendCommandCompletedEvent( + accessToken, + baseURI string, + optOut bool, + outcome string, +) { + if c.sentRunEvent { + c.sendEvent( + accessToken, + baseURI, + optOut, + "CLI Command Completed", + map[string]interface{}{ + "outcome": outcome, + }, + ) + } +} + func (a *Client) Wait() { a.wg.Wait() } type NoopClient struct{} -func (c *NoopClient) SendEvent( - accessToken string, +func (c *NoopClient) SendCommandRunEvent( + accessToken, baseURI string, optOut bool, - eventName string, properties map[string]interface{}, ) { } +func (c *NoopClient) SendCommandCompletedEvent( + accessToken, + baseURI string, + optOut bool, + outcome string, +) { +} + type MockTracker struct { mock.Mock ID string } -func (m *MockTracker) SendEvent( +func (m *MockTracker) sendEvent( accessToken string, baseURI string, optOut bool, @@ -113,3 +165,35 @@ func (m *MockTracker) SendEvent( properties["id"] = m.ID m.Called(accessToken, baseURI, eventName, properties) } + +func (m *MockTracker) SendCommandRunEvent( + accessToken, + baseURI string, + optOut bool, + properties map[string]interface{}, +) { + m.sendEvent( + accessToken, + baseURI, + optOut, + "CLI Command Run", + properties, + ) +} + +func (m *MockTracker) SendCommandCompletedEvent( + accessToken, + baseURI string, + optOut bool, + outcome string, +) { + m.sendEvent( + accessToken, + baseURI, + optOut, + "CLI Command Completed", + map[string]interface{}{ + "outcome": outcome, + }, + ) +} diff --git a/internal/analytics/events.go b/internal/analytics/events.go index fc6c4f30..3f34440f 100644 --- a/internal/analytics/events.go +++ b/internal/analytics/events.go @@ -1,37 +1,14 @@ package analytics -import ( - "ldcli/cmd/cliflags" - - "github.com/google/uuid" - "github.com/spf13/cobra" - "github.com/spf13/pflag" - "github.com/spf13/viper" +const ( + SUCCESS = "success" + ERROR = "error" ) -func CmdRunEventProperties(cmd *cobra.Command, name string) map[string]interface{} { - id := uuid.New() - baseURI := viper.GetString(cliflags.BaseURIFlag) - var flags []string - cmd.Flags().Visit(func(f *pflag.Flag) { - flags = append(flags, f.Name) - }) - - properties := map[string]interface{}{ - "name": name, - "action": cmd.CalledAs(), - "flags": flags, - "id": id.String(), - } - if baseURI != cliflags.BaseURIDefault { - properties["baseURI"] = baseURI - } - return properties -} - -func MockedTracker(name string, action string, flags []string) *MockTracker { +func MockedTracker(name string, action string, flags []string, outcome string) *MockTracker { id := "test-id" - mockedTrackingArgs := []interface{}{ + tracker := MockTracker{ID: id} + tracker.On("sendEvent", []interface{}{ "testAccessToken", "http://test.com", "CLI Command Run", @@ -42,8 +19,15 @@ func MockedTracker(name string, action string, flags []string) *MockTracker { "id": id, "name": name, }, - } - tracker := MockTracker{ID: id} - tracker.On("SendEvent", mockedTrackingArgs...) + }...) + tracker.On("sendEvent", []interface{}{ + "testAccessToken", + "http://test.com", + "CLI Command Completed", + map[string]interface{}{ + "id": id, + "outcome": outcome, + }, + }...) return &tracker } diff --git a/main.go b/main.go index 24e5bf64..4db601a1 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,8 @@ import ( "ldcli/cmd" "ldcli/internal/analytics" + + "github.com/google/uuid" ) // main.version is set at build time via ldflags by go releaser https://goreleaser.com/cookbooks/using-main.version/ @@ -17,7 +19,7 @@ func main() { httpClient := &http.Client{ Timeout: time.Second * 3, } - analyticsClient := &analytics.Client{HTTPClient: httpClient} + analyticsClient := &analytics.Client{HTTPClient: httpClient, ID: uuid.New().String()} cmd.Execute(analyticsClient, version) analyticsClient.Wait() }