From bea9bdea25995926fc1dbb9b2f17125fd33270e2 Mon Sep 17 00:00:00 2001 From: Mike Zorn Date: Mon, 19 Aug 2024 11:50:26 -0700 Subject: [PATCH 1/8] Standardize command run events --- cmd/analytics/analytics.go | 6 ++---- cmd/config/config.go | 33 ++++++--------------------------- cmd/quickstart.go | 12 ++---------- cmd/resources/resources.go | 12 +++--------- cmd/root.go | 24 ++++++++++++------------ 5 files changed, 25 insertions(+), 62 deletions(-) diff --git a/cmd/analytics/analytics.go b/cmd/analytics/analytics.go index 6c714195..e2aa4605 100644 --- a/cmd/analytics/analytics.go +++ b/cmd/analytics/analytics.go @@ -10,8 +10,6 @@ import ( func CmdRunEventProperties( cmd *cobra.Command, - name string, - overrides map[string]interface{}, ) map[string]interface{} { baseURI := viper.GetString(cliflags.BaseURIFlag) var flags []string @@ -20,7 +18,7 @@ func CmdRunEventProperties( }) properties := map[string]interface{}{ - "name": name, + "name": cmd.Name(), "action": cmd.CalledAs(), "flags": flags, } @@ -28,7 +26,7 @@ func CmdRunEventProperties( properties["baseURI"] = baseURI } - for k, v := range overrides { + for k, v := range cmd.Annotations { properties[k] = v } diff --git a/cmd/config/config.go b/cmd/config/config.go index bf432a80..cc9b6d94 100644 --- a/cmd/config/config.go +++ b/cmd/config/config.go @@ -12,7 +12,6 @@ import ( "github.com/spf13/viper" "gopkg.in/yaml.v3" - cmdAnalytics "github.com/launchdarkly/ldcli/cmd/analytics" "github.com/launchdarkly/ldcli/cmd/cliflags" "github.com/launchdarkly/ldcli/internal/analytics" "github.com/launchdarkly/ldcli/internal/config" @@ -41,20 +40,11 @@ func (cmd ConfigCmd) HelpCalled() bool { func NewConfigCmd(service config.Service, analyticsTrackerFn analytics.TrackerFn) *ConfigCmd { cmd := &cobra.Command{ - Long: "View and modify specific configuration values", - RunE: run(service), - Short: "View and modify specific configuration values", - Use: "config", - PreRun: func(cmd *cobra.Command, args []string) { - // only track event if there are flags - if len(os.Args[1:]) > 1 { - analyticsTrackerFn( - viper.GetString(cliflags.AccessTokenFlag), - viper.GetString(cliflags.BaseURIFlag), - viper.GetBool(cliflags.AnalyticsOptOut), - ).SendCommandRunEvent(cmdAnalytics.CmdRunEventProperties(cmd, "config", nil)) - } - }, + Long: "View and modify specific configuration values", + RunE: run(service), + Short: "View and modify specific configuration values", + Use: "config", + Annotations: make(map[string]string), } configCmd := ConfigCmd{ cmd: cmd, @@ -68,18 +58,7 @@ func NewConfigCmd(service config.Service, analyticsTrackerFn analytics.TrackerFn sb.WriteString("\n\nSupported settings:\n") writeAlphabetizedFlags(&sb) cmd.Long += sb.String() - - analyticsTrackerFn( - viper.GetString(cliflags.AccessTokenFlag), - viper.GetString(cliflags.BaseURIFlag), - viper.GetBool(cliflags.AnalyticsOptOut), - ).SendCommandRunEvent(cmdAnalytics.CmdRunEventProperties( - cmd, - "config", - map[string]interface{}{ - "action": "help", - }, - )) + cmd.Annotations["action"] = "help" helpFun(cmd, args) }) diff --git a/cmd/quickstart.go b/cmd/quickstart.go index fe40523f..885f47e6 100644 --- a/cmd/quickstart.go +++ b/cmd/quickstart.go @@ -9,7 +9,6 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" - cmdAnalytics "github.com/launchdarkly/ldcli/cmd/analytics" "github.com/launchdarkly/ldcli/cmd/cliflags" "github.com/launchdarkly/ldcli/cmd/validators" "github.com/launchdarkly/ldcli/internal/analytics" @@ -24,15 +23,8 @@ func NewQuickStartCmd( flagsClient flags.Client, ) *cobra.Command { return &cobra.Command{ - Args: validators.Validate(), - Long: "", - PreRun: func(cmd *cobra.Command, args []string) { - analyticsTrackerFn( - viper.GetString(cliflags.AccessTokenFlag), - viper.GetString(cliflags.BaseURIFlag), - viper.GetBool(cliflags.AnalyticsOptOut), - ).SendCommandRunEvent(cmdAnalytics.CmdRunEventProperties(cmd, "setup", nil)) - }, + Args: validators.Validate(), + Long: "", RunE: runQuickStart(analyticsTrackerFn, environmentsClient, flagsClient), Short: "Setup guide to create your first feature flag", Use: "setup", diff --git a/cmd/resources/resources.go b/cmd/resources/resources.go index 5c34da78..396efb42 100644 --- a/cmd/resources/resources.go +++ b/cmd/resources/resources.go @@ -15,7 +15,6 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" - cmdAnalytics "github.com/launchdarkly/ldcli/cmd/analytics" "github.com/launchdarkly/ldcli/cmd/cliflags" "github.com/launchdarkly/ldcli/cmd/validators" "github.com/launchdarkly/ldcli/internal/analytics" @@ -211,17 +210,12 @@ func NewResourceCmd( cmd := &cobra.Command{ Use: resourceName, Long: longAsMarkdown, - PersistentPreRun: func(cmd *cobra.Command, args []string) { - tracker := analyticsTrackerFn( - viper.GetString(cliflags.AccessTokenFlag), - viper.GetString(cliflags.BaseURIFlag), - viper.GetBool(cliflags.AnalyticsOptOut), - ) - tracker.SendCommandRunEvent(cmdAnalytics.CmdRunEventProperties(cmd, resourceName, nil)) - }, } cmd.SetUsageTemplate(SubcommandUsageTemplate()) + cmd.Annotations = map[string]string{ + "resource": resourceName, + } parentCmd.AddCommand(cmd) parentCmd.Annotations[resourceName] = "resource" diff --git a/cmd/root.go b/cmd/root.go index fbc36290..0c8e88f7 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -98,6 +98,12 @@ func NewRootCommand( cmd.DisableFlagParsing = true } } + tracker := analyticsTrackerFn( + viper.GetString(cliflags.AccessTokenFlag), + viper.GetString(cliflags.BaseURIFlag), + viper.GetBool(cliflags.AnalyticsOptOut), + ) + tracker.SendCommandRunEvent(cmdAnalytics.CmdRunEventProperties(cmd)) }, Annotations: make(map[string]string), // Handle errors differently based on type. @@ -116,18 +122,12 @@ func NewRootCommand( // get the resource for the tracking event, not the action resourceCommand := getResourceCommand(c) - analyticsTrackerFn( - viper.GetString(cliflags.AccessTokenFlag), - viper.GetString(cliflags.BaseURIFlag), - viper.GetBool(cliflags.AnalyticsOptOut), - ).SendCommandRunEvent( - cmdAnalytics.CmdRunEventProperties(c, - resourceCommand.Name(), - map[string]interface{}{ - "action": "help", - }, - ), - ) + + c.Annotations = resourceCommand.Annotations + if c.Annotations == nil { + c.Annotations = make(map[string]string) + } + c.Annotations["action"] = "help" hf(c, args) }) From a2a40382a3808dc964f24b9321089af6eb13eb6a Mon Sep 17 00:00:00 2001 From: Mike Zorn Date: Wed, 21 Aug 2024 17:33:19 -0700 Subject: [PATCH 2/8] split out tracker implementations --- internal/analytics/client.go | 87 ------------------------------- internal/analytics/mock.go | 60 +++++++++++++++++++++ internal/analytics/noop_client.go | 18 +++++++ internal/analytics/tracker.go | 12 +++++ 4 files changed, 90 insertions(+), 87 deletions(-) create mode 100644 internal/analytics/mock.go create mode 100644 internal/analytics/noop_client.go create mode 100644 internal/analytics/tracker.go diff --git a/internal/analytics/client.go b/internal/analytics/client.go index 9104306e..7dff9d33 100644 --- a/internal/analytics/client.go +++ b/internal/analytics/client.go @@ -9,12 +9,8 @@ import ( "net/url" "sync" "time" - - "github.com/stretchr/testify/mock" ) -type TrackerFn func(accessToken string, baseURI string, optOut bool) Tracker - type ClientFn struct { ID string } @@ -37,23 +33,6 @@ func (fn ClientFn) Tracker(version string) TrackerFn { } } -type NoopClientFn struct{} - -func (fn NoopClientFn) Tracker() TrackerFn { - return func(_ string, _ string, _ bool) Tracker { - return &NoopClient{} - } -} - -type Tracker interface { - SendCommandRunEvent(properties map[string]interface{}) - SendCommandCompletedEvent(outcome string) - SendSetupStepStartedEvent(step string) - SendSetupSDKSelectedEvent(sdk string) - SendSetupFlagToggledEvent(on bool, count int, duration_ms int64) - Wait() -} - type Client struct { accessToken string baseURI string @@ -164,69 +143,3 @@ func (c *Client) SendSetupFlagToggledEvent(on bool, count int, duration_ms int64 func (a *Client) Wait() { a.wg.Wait() } - -type NoopClient struct{} - -func (c *NoopClient) SendCommandRunEvent(properties map[string]interface{}) {} -func (c *NoopClient) SendCommandCompletedEvent(outcome string) {} -func (c *NoopClient) SendSetupStepStartedEvent(step string) {} -func (c *NoopClient) SendSetupSDKSelectedEvent(sdk string) {} -func (c *NoopClient) SendSetupFlagToggledEvent(on bool, count int, duration_ms int64) {} -func (a *NoopClient) Wait() {} - -type MockTracker struct { - mock.Mock - ID string -} - -func (m *MockTracker) sendEvent(eventName string, properties map[string]interface{}) { - properties["id"] = m.ID - m.Called(eventName, properties) -} - -func (m *MockTracker) SendCommandRunEvent(properties map[string]interface{}) { - m.sendEvent( - "CLI Command Run", - properties, - ) -} - -func (m *MockTracker) SendCommandCompletedEvent(outcome string) { - m.sendEvent( - "CLI Command Completed", - map[string]interface{}{ - "outcome": outcome, - }, - ) -} - -func (m *MockTracker) SendSetupStepStartedEvent(step string) { - m.sendEvent( - "CLI Setup Step Started", - map[string]interface{}{ - "step": step, - }, - ) -} - -func (m *MockTracker) SendSetupSDKSelectedEvent(sdk string) { - m.sendEvent( - "CLI Setup SDK Selected", - map[string]interface{}{ - "sdk": sdk, - }, - ) -} - -func (m *MockTracker) SendSetupFlagToggledEvent(on bool, count int, duration_ms int64) { - m.sendEvent( - "CLI Setup Flag Toggled", - map[string]interface{}{ - "on": on, - "count": count, - "duration_ms": duration_ms, - }, - ) -} - -func (a *MockTracker) Wait() {} diff --git a/internal/analytics/mock.go b/internal/analytics/mock.go new file mode 100644 index 00000000..88e8870f --- /dev/null +++ b/internal/analytics/mock.go @@ -0,0 +1,60 @@ +package analytics + +import "github.com/stretchr/testify/mock" + +type MockTracker struct { + mock.Mock + ID string +} + +func (m *MockTracker) sendEvent(eventName string, properties map[string]interface{}) { + properties["id"] = m.ID + m.Called(eventName, properties) +} + +func (m *MockTracker) SendCommandRunEvent(properties map[string]interface{}) { + m.sendEvent( + "CLI Command Run", + properties, + ) +} + +func (m *MockTracker) SendCommandCompletedEvent(outcome string) { + m.sendEvent( + "CLI Command Completed", + map[string]interface{}{ + "outcome": outcome, + }, + ) +} + +func (m *MockTracker) SendSetupStepStartedEvent(step string) { + m.sendEvent( + "CLI Setup Step Started", + map[string]interface{}{ + "step": step, + }, + ) +} + +func (m *MockTracker) SendSetupSDKSelectedEvent(sdk string) { + m.sendEvent( + "CLI Setup SDK Selected", + map[string]interface{}{ + "sdk": sdk, + }, + ) +} + +func (m *MockTracker) SendSetupFlagToggledEvent(on bool, count int, duration_ms int64) { + m.sendEvent( + "CLI Setup Flag Toggled", + map[string]interface{}{ + "on": on, + "count": count, + "duration_ms": duration_ms, + }, + ) +} + +func (a *MockTracker) Wait() {} diff --git a/internal/analytics/noop_client.go b/internal/analytics/noop_client.go new file mode 100644 index 00000000..d304e09b --- /dev/null +++ b/internal/analytics/noop_client.go @@ -0,0 +1,18 @@ +package analytics + +type NoopClientFn struct{} + +func (fn NoopClientFn) Tracker() TrackerFn { + return func(_ string, _ string, _ bool) Tracker { + return &NoopClient{} + } +} + +type NoopClient struct{} + +func (c *NoopClient) SendCommandRunEvent(properties map[string]interface{}) {} +func (c *NoopClient) SendCommandCompletedEvent(outcome string) {} +func (c *NoopClient) SendSetupStepStartedEvent(step string) {} +func (c *NoopClient) SendSetupSDKSelectedEvent(sdk string) {} +func (c *NoopClient) SendSetupFlagToggledEvent(on bool, count int, duration_ms int64) {} +func (a *NoopClient) Wait() {} diff --git a/internal/analytics/tracker.go b/internal/analytics/tracker.go new file mode 100644 index 00000000..f54bc4fd --- /dev/null +++ b/internal/analytics/tracker.go @@ -0,0 +1,12 @@ +package analytics + +type TrackerFn func(accessToken string, baseURI string, optOut bool) Tracker + +type Tracker interface { + SendCommandRunEvent(properties map[string]interface{}) + SendCommandCompletedEvent(outcome string) + SendSetupStepStartedEvent(step string) + SendSetupSDKSelectedEvent(sdk string) + SendSetupFlagToggledEvent(on bool, count int, duration_ms int64) + Wait() +} From 8c24fbc8a15a46c3cb580bacfd6573845485530d Mon Sep 17 00:00:00 2001 From: Mike Zorn Date: Wed, 21 Aug 2024 18:42:10 -0700 Subject: [PATCH 3/8] Add log client implementaion DO NOT MERGE use log tracker --- cmd/root.go | 9 +++------ internal/analytics/log_client.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 internal/analytics/log_client.go diff --git a/cmd/root.go b/cmd/root.go index 0c8e88f7..16774659 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -10,7 +10,6 @@ import ( "path/filepath" "strings" - "github.com/google/uuid" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -223,12 +222,10 @@ func Execute(version string) { ResourcesClient: resources.NewClient(version), } configService := config.NewService(resources.NewClient(version)) - trackerFn := analytics.ClientFn{ - ID: uuid.New().String(), - } + trackerFn := analytics.LogClientFn{} rootCmd, err := NewRootCommand( configService, - trackerFn.Tracker(version), + trackerFn.Tracker(), clients, version, true, @@ -266,7 +263,7 @@ See each command's help for details on how to use the generated script.`, rootCm outcome = analytics.SUCCESS } - analyticsClient := trackerFn.Tracker(version)( + analyticsClient := trackerFn.Tracker()( viper.GetString(cliflags.AccessTokenFlag), viper.GetString(cliflags.BaseURIFlag), viper.GetBool(cliflags.AnalyticsOptOut), diff --git a/internal/analytics/log_client.go b/internal/analytics/log_client.go new file mode 100644 index 00000000..8b2ebc31 --- /dev/null +++ b/internal/analytics/log_client.go @@ -0,0 +1,30 @@ +package analytics + +import "log" + +type LogClientFn struct{} + +func (fn LogClientFn) Tracker() TrackerFn { + return func(_ string, _ string, _ bool) Tracker { + return &LogClient{} + } +} + +type LogClient struct{} + +func (c *LogClient) SendCommandRunEvent(properties map[string]interface{}) { + log.Printf("SendCommandRunEvent, properties: %v", properties) +} +func (c *LogClient) SendCommandCompletedEvent(outcome string) { + log.Printf("SendCommandCompletedEvent, outcome: %v", outcome) +} +func (c *LogClient) SendSetupStepStartedEvent(step string) { + log.Printf("SendSetupStepStartedEvent, step: %v", step) +} +func (c *LogClient) SendSetupSDKSelectedEvent(sdk string) { + log.Printf("SendSetupSDKSelectedEvent, sdk: %v", sdk) +} +func (c *LogClient) SendSetupFlagToggledEvent(on bool, count int, duration_ms int64) { + log.Printf("SendSetupFlagToggledEvent, count: %v", count) +} +func (a *LogClient) Wait() {} From e89c814ee62197228083c7c628b53acd82ae7aac Mon Sep 17 00:00:00 2001 From: Mike Zorn Date: Wed, 21 Aug 2024 18:56:43 -0700 Subject: [PATCH 4/8] Send run event on help --- cmd/root.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 16774659..54ede973 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -77,6 +77,11 @@ func NewRootCommand( version string, useConfigFile bool, ) (*RootCmd, error) { + tracker := analyticsTrackerFn( + viper.GetString(cliflags.AccessTokenFlag), + viper.GetString(cliflags.BaseURIFlag), + viper.GetBool(cliflags.AnalyticsOptOut), + ) cmd := &cobra.Command{ Use: "ldcli", Short: "LaunchDarkly CLI", @@ -97,11 +102,6 @@ func NewRootCommand( cmd.DisableFlagParsing = true } } - tracker := analyticsTrackerFn( - viper.GetString(cliflags.AccessTokenFlag), - viper.GetString(cliflags.BaseURIFlag), - viper.GetBool(cliflags.AnalyticsOptOut), - ) tracker.SendCommandRunEvent(cmdAnalytics.CmdRunEventProperties(cmd)) }, Annotations: make(map[string]string), @@ -127,6 +127,7 @@ func NewRootCommand( c.Annotations = make(map[string]string) } c.Annotations["action"] = "help" + tracker.SendCommandRunEvent(cmdAnalytics.CmdRunEventProperties(cmd)) hf(c, args) }) From cbea8b97a74e9cfeb29299fb3c8d17b5a92b2ccd Mon Sep 17 00:00:00 2001 From: Mike Zorn Date: Wed, 28 Aug 2024 14:54:45 -0700 Subject: [PATCH 5/8] Abort attempt to unify event handling --- cmd/analytics/analytics.go | 6 ++++-- cmd/config/config.go | 33 +++++++++++++++++++++++++++------ cmd/quickstart.go | 12 ++++++++++-- cmd/resources/resources.go | 12 +++++++++--- cmd/root.go | 34 ++++++++++++++++++---------------- 5 files changed, 68 insertions(+), 29 deletions(-) diff --git a/cmd/analytics/analytics.go b/cmd/analytics/analytics.go index e2aa4605..6c714195 100644 --- a/cmd/analytics/analytics.go +++ b/cmd/analytics/analytics.go @@ -10,6 +10,8 @@ import ( func CmdRunEventProperties( cmd *cobra.Command, + name string, + overrides map[string]interface{}, ) map[string]interface{} { baseURI := viper.GetString(cliflags.BaseURIFlag) var flags []string @@ -18,7 +20,7 @@ func CmdRunEventProperties( }) properties := map[string]interface{}{ - "name": cmd.Name(), + "name": name, "action": cmd.CalledAs(), "flags": flags, } @@ -26,7 +28,7 @@ func CmdRunEventProperties( properties["baseURI"] = baseURI } - for k, v := range cmd.Annotations { + for k, v := range overrides { properties[k] = v } diff --git a/cmd/config/config.go b/cmd/config/config.go index cc9b6d94..bf432a80 100644 --- a/cmd/config/config.go +++ b/cmd/config/config.go @@ -12,6 +12,7 @@ import ( "github.com/spf13/viper" "gopkg.in/yaml.v3" + cmdAnalytics "github.com/launchdarkly/ldcli/cmd/analytics" "github.com/launchdarkly/ldcli/cmd/cliflags" "github.com/launchdarkly/ldcli/internal/analytics" "github.com/launchdarkly/ldcli/internal/config" @@ -40,11 +41,20 @@ func (cmd ConfigCmd) HelpCalled() bool { func NewConfigCmd(service config.Service, analyticsTrackerFn analytics.TrackerFn) *ConfigCmd { cmd := &cobra.Command{ - Long: "View and modify specific configuration values", - RunE: run(service), - Short: "View and modify specific configuration values", - Use: "config", - Annotations: make(map[string]string), + Long: "View and modify specific configuration values", + RunE: run(service), + Short: "View and modify specific configuration values", + Use: "config", + PreRun: func(cmd *cobra.Command, args []string) { + // only track event if there are flags + if len(os.Args[1:]) > 1 { + analyticsTrackerFn( + viper.GetString(cliflags.AccessTokenFlag), + viper.GetString(cliflags.BaseURIFlag), + viper.GetBool(cliflags.AnalyticsOptOut), + ).SendCommandRunEvent(cmdAnalytics.CmdRunEventProperties(cmd, "config", nil)) + } + }, } configCmd := ConfigCmd{ cmd: cmd, @@ -58,7 +68,18 @@ func NewConfigCmd(service config.Service, analyticsTrackerFn analytics.TrackerFn sb.WriteString("\n\nSupported settings:\n") writeAlphabetizedFlags(&sb) cmd.Long += sb.String() - cmd.Annotations["action"] = "help" + + analyticsTrackerFn( + viper.GetString(cliflags.AccessTokenFlag), + viper.GetString(cliflags.BaseURIFlag), + viper.GetBool(cliflags.AnalyticsOptOut), + ).SendCommandRunEvent(cmdAnalytics.CmdRunEventProperties( + cmd, + "config", + map[string]interface{}{ + "action": "help", + }, + )) helpFun(cmd, args) }) diff --git a/cmd/quickstart.go b/cmd/quickstart.go index 885f47e6..fe40523f 100644 --- a/cmd/quickstart.go +++ b/cmd/quickstart.go @@ -9,6 +9,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" + cmdAnalytics "github.com/launchdarkly/ldcli/cmd/analytics" "github.com/launchdarkly/ldcli/cmd/cliflags" "github.com/launchdarkly/ldcli/cmd/validators" "github.com/launchdarkly/ldcli/internal/analytics" @@ -23,8 +24,15 @@ func NewQuickStartCmd( flagsClient flags.Client, ) *cobra.Command { return &cobra.Command{ - Args: validators.Validate(), - Long: "", + Args: validators.Validate(), + Long: "", + PreRun: func(cmd *cobra.Command, args []string) { + analyticsTrackerFn( + viper.GetString(cliflags.AccessTokenFlag), + viper.GetString(cliflags.BaseURIFlag), + viper.GetBool(cliflags.AnalyticsOptOut), + ).SendCommandRunEvent(cmdAnalytics.CmdRunEventProperties(cmd, "setup", nil)) + }, RunE: runQuickStart(analyticsTrackerFn, environmentsClient, flagsClient), Short: "Setup guide to create your first feature flag", Use: "setup", diff --git a/cmd/resources/resources.go b/cmd/resources/resources.go index 396efb42..5c34da78 100644 --- a/cmd/resources/resources.go +++ b/cmd/resources/resources.go @@ -15,6 +15,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" + cmdAnalytics "github.com/launchdarkly/ldcli/cmd/analytics" "github.com/launchdarkly/ldcli/cmd/cliflags" "github.com/launchdarkly/ldcli/cmd/validators" "github.com/launchdarkly/ldcli/internal/analytics" @@ -210,12 +211,17 @@ func NewResourceCmd( cmd := &cobra.Command{ Use: resourceName, Long: longAsMarkdown, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + tracker := analyticsTrackerFn( + viper.GetString(cliflags.AccessTokenFlag), + viper.GetString(cliflags.BaseURIFlag), + viper.GetBool(cliflags.AnalyticsOptOut), + ) + tracker.SendCommandRunEvent(cmdAnalytics.CmdRunEventProperties(cmd, resourceName, nil)) + }, } cmd.SetUsageTemplate(SubcommandUsageTemplate()) - cmd.Annotations = map[string]string{ - "resource": resourceName, - } parentCmd.AddCommand(cmd) parentCmd.Annotations[resourceName] = "resource" diff --git a/cmd/root.go b/cmd/root.go index 54ede973..fbc36290 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -10,6 +10,7 @@ import ( "path/filepath" "strings" + "github.com/google/uuid" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -77,11 +78,6 @@ func NewRootCommand( version string, useConfigFile bool, ) (*RootCmd, error) { - tracker := analyticsTrackerFn( - viper.GetString(cliflags.AccessTokenFlag), - viper.GetString(cliflags.BaseURIFlag), - viper.GetBool(cliflags.AnalyticsOptOut), - ) cmd := &cobra.Command{ Use: "ldcli", Short: "LaunchDarkly CLI", @@ -102,7 +98,6 @@ func NewRootCommand( cmd.DisableFlagParsing = true } } - tracker.SendCommandRunEvent(cmdAnalytics.CmdRunEventProperties(cmd)) }, Annotations: make(map[string]string), // Handle errors differently based on type. @@ -121,13 +116,18 @@ func NewRootCommand( // get the resource for the tracking event, not the action resourceCommand := getResourceCommand(c) - - c.Annotations = resourceCommand.Annotations - if c.Annotations == nil { - c.Annotations = make(map[string]string) - } - c.Annotations["action"] = "help" - tracker.SendCommandRunEvent(cmdAnalytics.CmdRunEventProperties(cmd)) + analyticsTrackerFn( + viper.GetString(cliflags.AccessTokenFlag), + viper.GetString(cliflags.BaseURIFlag), + viper.GetBool(cliflags.AnalyticsOptOut), + ).SendCommandRunEvent( + cmdAnalytics.CmdRunEventProperties(c, + resourceCommand.Name(), + map[string]interface{}{ + "action": "help", + }, + ), + ) hf(c, args) }) @@ -223,10 +223,12 @@ func Execute(version string) { ResourcesClient: resources.NewClient(version), } configService := config.NewService(resources.NewClient(version)) - trackerFn := analytics.LogClientFn{} + trackerFn := analytics.ClientFn{ + ID: uuid.New().String(), + } rootCmd, err := NewRootCommand( configService, - trackerFn.Tracker(), + trackerFn.Tracker(version), clients, version, true, @@ -264,7 +266,7 @@ See each command's help for details on how to use the generated script.`, rootCm outcome = analytics.SUCCESS } - analyticsClient := trackerFn.Tracker()( + analyticsClient := trackerFn.Tracker(version)( viper.GetString(cliflags.AccessTokenFlag), viper.GetString(cliflags.BaseURIFlag), viper.GetBool(cliflags.AnalyticsOptOut), From 345770cdf9786458e113eb0debb7fb79b5cc02fe Mon Sep 17 00:00:00 2001 From: Mike Zorn Date: Wed, 28 Aug 2024 14:58:32 -0700 Subject: [PATCH 6/8] Tweak tracker interface to make swapping easier --- cmd/root.go | 7 ++++--- internal/analytics/client.go | 29 ++++++++++++++--------------- internal/analytics/log_client.go | 6 ++---- 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index fbc36290..bda54f60 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -224,11 +224,12 @@ func Execute(version string) { } configService := config.NewService(resources.NewClient(version)) trackerFn := analytics.ClientFn{ - ID: uuid.New().String(), + ID: uuid.New().String(), + Version: version, } rootCmd, err := NewRootCommand( configService, - trackerFn.Tracker(version), + trackerFn.Tracker, clients, version, true, @@ -266,7 +267,7 @@ See each command's help for details on how to use the generated script.`, rootCm outcome = analytics.SUCCESS } - analyticsClient := trackerFn.Tracker(version)( + analyticsClient := trackerFn.Tracker( viper.GetString(cliflags.AccessTokenFlag), viper.GetString(cliflags.BaseURIFlag), viper.GetBool(cliflags.AnalyticsOptOut), diff --git a/internal/analytics/client.go b/internal/analytics/client.go index 7dff9d33..938882b1 100644 --- a/internal/analytics/client.go +++ b/internal/analytics/client.go @@ -12,24 +12,23 @@ import ( ) type ClientFn struct { - ID string + ID string + Version string } -func (fn ClientFn) Tracker(version string) TrackerFn { - return func(accessToken string, baseURI string, optOut bool) Tracker { - if optOut { - return &NoopClient{} - } +func (fn ClientFn) Tracker(accessToken string, baseURI string, optOut bool) Tracker { + if optOut { + return &NoopClient{} + } - return &Client{ - httpClient: &http.Client{ - Timeout: time.Second * 3, - }, - id: fn.ID, - version: version, - accessToken: accessToken, - baseURI: baseURI, - } + return &Client{ + httpClient: &http.Client{ + Timeout: time.Second * 3, + }, + id: fn.ID, + version: fn.Version, + accessToken: accessToken, + baseURI: baseURI, } } diff --git a/internal/analytics/log_client.go b/internal/analytics/log_client.go index 8b2ebc31..ae99ca48 100644 --- a/internal/analytics/log_client.go +++ b/internal/analytics/log_client.go @@ -4,10 +4,8 @@ import "log" type LogClientFn struct{} -func (fn LogClientFn) Tracker() TrackerFn { - return func(_ string, _ string, _ bool) Tracker { - return &LogClient{} - } +func (fn LogClientFn) Tracker(_ string, _ string, _ bool) Tracker { + return &LogClient{} } type LogClient struct{} From 4ed482db37e2b92e2c76dd472e41cdfd6d7e59b9 Mon Sep 17 00:00:00 2001 From: Mike Zorn Date: Wed, 28 Aug 2024 15:48:41 -0700 Subject: [PATCH 7/8] Implement events for dev server --- cmd/dev_server/dev_server.go | 18 +++++++++++++++++- cmd/root.go | 2 +- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/cmd/dev_server/dev_server.go b/cmd/dev_server/dev_server.go index 947fdd2c..f04b0417 100644 --- a/cmd/dev_server/dev_server.go +++ b/cmd/dev_server/dev_server.go @@ -3,6 +3,8 @@ package dev_server import ( "fmt" + cmdAnalytics "github.com/launchdarkly/ldcli/cmd/analytics" + "github.com/launchdarkly/ldcli/internal/analytics" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -12,11 +14,25 @@ import ( "github.com/launchdarkly/ldcli/internal/resources" ) -func NewDevServerCmd(client resources.Client, ldClient dev_server.Client) *cobra.Command { +func NewDevServerCmd(client resources.Client, analyticsTrackerFn analytics.TrackerFn, ldClient dev_server.Client) *cobra.Command { cmd := &cobra.Command{ Use: "dev-server", Short: "Development server", Long: "Start and use a local development server for overriding flag values.", + PersistentPreRun: func(cmd *cobra.Command, args []string) { + + tracker := analyticsTrackerFn( + viper.GetString(cliflags.AccessTokenFlag), + viper.GetString(cliflags.BaseURIFlag), + viper.GetBool(cliflags.AnalyticsOptOut), + ) + tracker.SendCommandRunEvent(cmdAnalytics.CmdRunEventProperties( + cmd, + "dev-server", + map[string]interface{}{ + "action": cmd.Name(), + })) + }, } cmd.PersistentFlags().String( diff --git a/cmd/root.go b/cmd/root.go index bda54f60..4f95e4a3 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -193,7 +193,7 @@ func NewRootCommand( cmd.AddCommand(NewQuickStartCmd(analyticsTrackerFn, clients.EnvironmentsClient, clients.FlagsClient)) cmd.AddCommand(logincmd.NewLoginCmd(resources.NewClient(version))) cmd.AddCommand(resourcecmd.NewResourcesCmd()) - cmd.AddCommand(devcmd.NewDevServerCmd(resources.NewClient(version), dev_server.NewClient(version))) + cmd.AddCommand(devcmd.NewDevServerCmd(resources.NewClient(version), analyticsTrackerFn, dev_server.NewClient(version))) resourcecmd.AddAllResourceCmds(cmd, clients.ResourcesClient, analyticsTrackerFn) // add non-generated commands From 8080bd227a4241bfcb46d450cb4b0e48bdbbbcec Mon Sep 17 00:00:00 2001 From: Mike Zorn Date: Wed, 28 Aug 2024 16:09:45 -0700 Subject: [PATCH 8/8] Add notes about how to add a new command --- CONTRIBUTING.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 62ee3614..c2e1dad3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,3 +16,33 @@ To install the repo's git hooks, run `make install-hooks`. The pre-commit hook checks that relevant project files are formatted with `go fmt`, and that the `go.mod/go.sum` files are tidy. + +## Adding a new command + +There are a few things you need to do in order to wire up a new top-level command. + +1. Add your command to the root command by calling `cmd.AddComand` in the `NewRootCommand` method of the `cmd` package. +2. Update the root command's usage template by modifying the `getUsageTemplate` method in the `cmd` package. +3. Instrument your command by setting a `PreRun` or `PersistentPreRun` on your command which calls `tracker.SendCommandRunEvent`. Example below. +```go +cmd := &cobra.Command{ + Use: "dev-server", + Short: "Development server", + Long: "Start and use a local development server for overriding flag values.", + PersistentPreRun: func(cmd *cobra.Command, args []string) { + + tracker := analyticsTrackerFn( + viper.GetString(cliflags.AccessTokenFlag), + viper.GetString(cliflags.BaseURIFlag), + viper.GetBool(cliflags.AnalyticsOptOut), + ) + tracker.SendCommandRunEvent(cmdAnalytics.CmdRunEventProperties( + cmd, + "dev-server", + map[string]interface{}{ + "action": cmd.Name(), + })) + }, +} + +```