diff --git a/cmd/project/create.go b/cmd/project/create.go index 736cee45..4fcd1b12 100644 --- a/cmd/project/create.go +++ b/cmd/project/create.go @@ -210,6 +210,8 @@ func runCreateCommand(clients *shared.ClientFactory, cmd *cobra.Command, args [] Subdir: subdir, } clients.EventTracker.SetAppTemplate(template.GetTemplatePath()) + clients.EventTracker.SetAppTemplateShortcut(categoryShortcut) + clients.EventTracker.SetAppTemplateSubdir(subdir) appDirPath, err := CreateFunc(ctx, clients, createArgs) if err != nil { diff --git a/cmd/project/create_test.go b/cmd/project/create_test.go index a81bc30b..6783d50c 100644 --- a/cmd/project/create_test.go +++ b/cmd/project/create_test.go @@ -168,6 +168,9 @@ func TestCreateCommand(t *testing.T) { Subdir: "claude-agent-sdk", } createClientMock.AssertCalled(t, "Create", mock.Anything, mock.Anything, expected) + cm.EventTracker.AssertCalled(t, "SetAppTemplate", "slack-samples/bolt-js-starter-agent") + cm.EventTracker.AssertCalled(t, "SetAppTemplateShortcut", "agent") + cm.EventTracker.AssertCalled(t, "SetAppTemplateSubdir", "claude-agent-sdk") // Verify that category prompt was NOT called cm.IO.AssertNotCalled(t, "SelectPrompt", mock.Anything, "Select a category:", mock.Anything, mock.Anything) cm.IO.AssertCalled(t, "InputPrompt", mock.Anything, "Name your app:", mock.Anything) @@ -279,6 +282,8 @@ func TestCreateCommand(t *testing.T) { Template: template, } createClientMock.AssertCalled(t, "Create", mock.Anything, mock.Anything, expected) + cm.EventTracker.AssertCalled(t, "SetAppTemplateShortcut", "") + cm.EventTracker.AssertCalled(t, "SetAppTemplateSubdir", "") // Verify that name prompt was NOT called since name was provided as arg cm.IO.AssertNotCalled(t, "InputPrompt", mock.Anything, "Name your app:", mock.Anything) }, @@ -648,6 +653,7 @@ func TestCreateCommand(t *testing.T) { createClientMock.AssertCalled(t, "Create", mock.Anything, mock.Anything, mock.MatchedBy(func(args create.CreateArgs) bool { return args.AppPath != "" && args.Template == template && args.Subdir == "apps/my-app" })) + cm.EventTracker.AssertCalled(t, "SetAppTemplateSubdir", "apps/my-app") }, }, "list flag ignores subdir": { diff --git a/internal/tracking/logstash_event.go b/internal/tracking/logstash_event.go index a0f58a16..8df45d22 100644 --- a/internal/tracking/logstash_event.go +++ b/internal/tracking/logstash_event.go @@ -78,4 +78,8 @@ type AppEventData struct { UserID string `json:"user_id,omitempty"` // Template holds information for the sample app template used as part of the `create` command Template string `json:"template,omitempty"` + // TemplateShortcut holds the category shortcut argument used with the `create` command, such as "agent" + TemplateShortcut string `json:"template_shortcut,omitempty"` + // TemplateSubdir holds the subdirectory of the template used as part of the `create` command + TemplateSubdir string `json:"template_subdir,omitempty"` } diff --git a/internal/tracking/tracking.go b/internal/tracking/tracking.go index 01cfc6ac..559d661e 100644 --- a/internal/tracking/tracking.go +++ b/internal/tracking/tracking.go @@ -50,6 +50,8 @@ type TrackingManager interface { SetAppTeamID(id string) SetAppUserID(id string) SetAppTemplate(template string) + SetAppTemplateShortcut(shortcut string) + SetAppTemplateSubdir(subdir string) } type EventTracker struct { @@ -146,6 +148,20 @@ func (e *EventTracker) SetAppTemplate(template string) { e.setSessionData(data) } +// SetAppTemplateShortcut sets the category shortcut used to select an app template in this CLI execution for metrics +func (e *EventTracker) SetAppTemplateShortcut(shortcut string) { + data := e.getSessionData() + data.App.TemplateShortcut = shortcut + e.setSessionData(data) +} + +// SetAppTemplateSubdir sets the subdirectory of the app template used in this CLI execution for metrics +func (e *EventTracker) SetAppTemplateSubdir(subdir string) { + data := e.getSessionData() + data.App.TemplateSubdir = subdir + e.setSessionData(data) +} + // cleanSessionData ensures every string value the provided object has PII redacted func (e *EventTracker) cleanSessionData(data EventData) EventData { if len(data.ErrorMessage) > 0 { @@ -154,6 +170,9 @@ func (e *EventTracker) cleanSessionData(data EventData) EventData { if len(data.App.Template) > 0 { data.App.Template = goutils.RedactPII(data.App.Template) } + if len(data.App.TemplateSubdir) > 0 { + data.App.TemplateSubdir = goutils.RedactPII(data.App.TemplateSubdir) + } return data } diff --git a/internal/tracking/tracking_mock.go b/internal/tracking/tracking_mock.go index 31cbbea0..c123af9c 100644 --- a/internal/tracking/tracking_mock.go +++ b/internal/tracking/tracking_mock.go @@ -30,6 +30,8 @@ func (m *EventTrackerMock) AddDefaultMocks() { m.On("SetAppEnterpriseID", mock.Anything) m.On("SetAppTeamID", mock.Anything) m.On("SetAppTemplate", mock.Anything) + m.On("SetAppTemplateShortcut", mock.Anything) + m.On("SetAppTemplateSubdir", mock.Anything) m.On("SetAppUserID", mock.Anything) m.On("SetAuthEnterpriseID", mock.Anything) m.On("SetAuthTeamID", mock.Anything) @@ -88,3 +90,11 @@ func (m *EventTrackerMock) SetAppUserID(id string) { func (m *EventTrackerMock) SetAppTemplate(template string) { m.Called(template) } + +func (m *EventTrackerMock) SetAppTemplateShortcut(shortcut string) { + m.Called(shortcut) +} + +func (m *EventTrackerMock) SetAppTemplateSubdir(subdir string) { + m.Called(subdir) +} diff --git a/internal/tracking/tracking_test.go b/internal/tracking/tracking_test.go index 31e5e250..ae23ee2b 100644 --- a/internal/tracking/tracking_test.go +++ b/internal/tracking/tracking_test.go @@ -64,6 +64,18 @@ func Test_Tracking_cleanSessionData(t *testing.T) { }, }, }, + "should redact app template subdir": { + input: EventData{ + App: AppEventData{ + TemplateSubdir: "apps/my-xapp-example", + }, + }, + expectedOutput: EventData{ + App: AppEventData{ + TemplateSubdir: "apps/my-...", + }, + }, + }, } for name, tc := range tests { @@ -272,6 +284,24 @@ func Test_Tracking_SetSessionData(t *testing.T) { }, value: "slack-samples/deno-hello-world", }, + "app template shortcut can be retrieved": { + setterFunc: func(e *EventTracker, value string) { + e.SetAppTemplateShortcut(value) + }, + getterFunc: func(e *EventTracker) string { + return e.getSessionData().App.TemplateShortcut + }, + value: "agent", + }, + "app template subdir can be retrieved": { + setterFunc: func(e *EventTracker, value string) { + e.SetAppTemplateSubdir(value) + }, + getterFunc: func(e *EventTracker) string { + return e.getSessionData().App.TemplateSubdir + }, + value: "claude-agent-sdk", + }, } for name, tc := range tests {