Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: dev server commands emit events #410

Merged
merged 8 commits into from
Aug 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
}))
},
}

```
18 changes: 17 additions & 1 deletion cmd/dev_server/dev_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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(
Expand Down
9 changes: 5 additions & 4 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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),
Expand Down
114 changes: 13 additions & 101 deletions internal/analytics/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,49 +9,27 @@ 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
}

func (fn ClientFn) Tracker(version string) TrackerFn {
return func(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,
}
}
ID string
Version string
}

type NoopClientFn struct{}

func (fn NoopClientFn) Tracker() TrackerFn {
return func(_ string, _ string, _ bool) Tracker {
func (fn ClientFn) Tracker(accessToken string, baseURI string, optOut bool) Tracker {
if optOut {
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()
return &Client{
httpClient: &http.Client{
Timeout: time.Second * 3,
},
id: fn.ID,
version: fn.Version,
accessToken: accessToken,
baseURI: baseURI,
}
}

type Client struct {
Expand Down Expand Up @@ -164,69 +142,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() {}
28 changes: 28 additions & 0 deletions internal/analytics/log_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package analytics

import "log"

type LogClientFn struct{}

func (fn LogClientFn) Tracker(_ 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() {}
60 changes: 60 additions & 0 deletions internal/analytics/mock.go
Original file line number Diff line number Diff line change
@@ -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() {}
18 changes: 18 additions & 0 deletions internal/analytics/noop_client.go
Original file line number Diff line number Diff line change
@@ -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() {}
12 changes: 12 additions & 0 deletions internal/analytics/tracker.go
Original file line number Diff line number Diff line change
@@ -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()
}
Loading