From 778f37aca070234e3796017c4f639121348dc95a Mon Sep 17 00:00:00 2001 From: mitchell Date: Tue, 12 Sep 2023 13:38:24 -0400 Subject: [PATCH] Add source to analytics events. This allows for differentiation between state tool, offline installer, etc. --- cmd/state-installer/cmd.go | 42 +++++++++---------- cmd/state-offline-installer/install.go | 8 ++-- cmd/state-offline-uninstaller/uninstall.go | 10 ++--- cmd/state-svc/internal/resolver/resolver.go | 12 +++--- cmd/state-svc/internal/rtwatcher/watcher.go | 4 +- .../internal/server/generated/generated.go | 31 +++++++++----- cmd/state-svc/internal/server/server.go | 6 +-- cmd/state-svc/schema/schema.graphqls | 2 +- cmd/state/autoupdate.go | 14 +++---- cmd/state/internal/cmdtree/activate.go | 8 ++-- cmd/state/internal/cmdtree/tutorial.go | 4 +- go.sum | 4 -- internal/analytics/analytics.go | 4 +- internal/analytics/client/async/client.go | 12 +++--- internal/analytics/client/blackhole/client.go | 4 +- internal/analytics/client/sync/client.go | 14 +++---- .../client/sync/reporters/ga-state.go | 2 +- .../analytics/client/sync/reporters/pixel.go | 3 +- .../analytics/client/sync/reporters/test.go | 5 ++- internal/analytics/constants/constants.go | 15 +++++++ internal/captain/command.go | 10 ++--- internal/graph/generated.go | 9 ---- internal/prompt/prompt.go | 8 ++-- internal/runbits/activation/activation.go | 2 +- internal/runbits/requirements/requirements.go | 2 +- internal/runners/activate/activate.go | 2 +- internal/runners/config/set.go | 2 +- .../runners/deploy/uninstall/uninstall.go | 2 +- internal/runners/ppm/convert.go | 16 +++---- internal/runners/ppm/shim.go | 6 +-- internal/runners/tutorial/tutorial.go | 16 +++---- internal/svcctl/comm.go | 4 +- internal/updater/checker.go | 4 +- internal/updater/fetcher.go | 2 +- internal/updater/updater.go | 2 +- pkg/cmdlets/errors/errors.go | 2 +- pkg/cmdlets/git/git.go | 2 +- pkg/platform/api/svc/request/analytics.go | 35 +++++++++------- pkg/platform/model/svc.go | 4 +- pkg/platform/runtime/runtime.go | 8 ++-- pkg/platform/runtime/setup/setup.go | 4 +- test/integration/analytics_int_test.go | 2 +- test/integration/performance_svc_int_test.go | 2 +- 43 files changed, 182 insertions(+), 168 deletions(-) diff --git a/cmd/state-installer/cmd.go b/cmd/state-installer/cmd.go index a69b8b564b..b5b1faa54b 100644 --- a/cmd/state-installer/cmd.go +++ b/cmd/state-installer/cmd.go @@ -12,6 +12,7 @@ import ( "github.com/ActiveState/cli/internal/analytics" "github.com/ActiveState/cli/internal/analytics/client/sync" + anaConst "github.com/ActiveState/cli/internal/analytics/constants" "github.com/ActiveState/cli/internal/captain" "github.com/ActiveState/cli/internal/config" "github.com/ActiveState/cli/internal/constants" @@ -37,9 +38,6 @@ import ( "golang.org/x/crypto/ssh/terminal" ) -const AnalyticsCat = "installer" -const AnalyticsFunnelCat = "installer-funnel" - type Params struct { sourceInstaller string path string @@ -126,7 +124,7 @@ func main() { logging.Debug("Processed Args: %v", processedArgs) an = sync.New(cfg, nil, out) - an.Event(AnalyticsFunnelCat, "start") + an.Event(anaConst.CatInstallerFunnel, "start", anaConst.SrcStateTool) params := newParams() cmd := captain.NewCommand( @@ -193,30 +191,30 @@ func main() { }, ) - an.Event(AnalyticsFunnelCat, "pre-exec") + an.Event(anaConst.CatInstallerFunnel, "pre-exec", anaConst.SrcStateTool) err = cmd.Execute(processedArgs[1:]) if err != nil { errors.ReportError(err, cmd, an) if locale.IsInputError(err) { - an.EventWithLabel(AnalyticsCat, "input-error", errs.JoinMessage(err)) + an.EventWithLabel(anaConst.CatInstaller, "input-error", anaConst.SrcStateTool, errs.JoinMessage(err)) logging.Debug("Installer input error: " + errs.JoinMessage(err)) } else { - an.EventWithLabel(AnalyticsCat, "error", errs.JoinMessage(err)) + an.EventWithLabel(anaConst.CatInstaller, "error", anaConst.SrcStateTool, errs.JoinMessage(err)) multilog.Critical("Installer error: " + errs.JoinMessage(err)) } - an.EventWithLabel(AnalyticsFunnelCat, "fail", errs.JoinMessage(err)) + an.EventWithLabel(anaConst.CatInstallerFunnel, "fail", anaConst.SrcStateTool, errs.JoinMessage(err)) exitCode, err = errors.ParseUserFacing(err) if err != nil { out.Error(err) } } else { - an.Event(AnalyticsFunnelCat, "success") + an.Event(anaConst.CatInstallerFunnel, "success", anaConst.SrcStateTool) } } func execute(out output.Outputer, cfg *config.Instance, an analytics.Dispatcher, args []string, params *Params) error { - an.Event(AnalyticsFunnelCat, "exec") + an.Event(anaConst.CatInstallerFunnel, "exec", anaConst.SrcStateTool) if params.path == "" { var err error @@ -274,13 +272,13 @@ func execute(out output.Outputer, cfg *config.Instance, an analytics.Dispatcher, if params.isUpdate { route = "update" } - an.Event(AnalyticsFunnelCat, route) + an.Event(anaConst.CatInstallerFunnel, route, anaConst.SrcStateTool) // Check if state tool already installed if !params.isUpdate && !params.force && stateToolInstalled && !targetingSameBranch { logging.Debug("Cancelling out because State Tool is already installed") out.Print(fmt.Sprintf("State Tool Package Manager is already installed at [NOTICE]%s[/RESET]. To reinstall use the [ACTIONABLE]--force[/RESET] flag.", installPath)) - an.Event(AnalyticsFunnelCat, "already-installed") + an.Event(anaConst.CatInstallerFunnel, "already-installed", anaConst.SrcStateTool) params.isUpdate = true return postInstallEvents(out, cfg, an, params) } @@ -295,7 +293,7 @@ func execute(out output.Outputer, cfg *config.Instance, an analytics.Dispatcher, // installOrUpdateFromLocalSource is invoked when we're performing an installation where the payload is already provided func installOrUpdateFromLocalSource(out output.Outputer, cfg *config.Instance, an analytics.Dispatcher, payloadPath string, params *Params) error { logging.Debug("Install from local source") - an.Event(AnalyticsFunnelCat, "local-source") + an.Event(anaConst.CatInstallerFunnel, "local-source", anaConst.SrcStateTool) if !params.isUpdate { // install.sh or install.ps1 downloaded this installer and is running it. out.Print(output.Title("Installing State Tool Package Manager")) @@ -324,12 +322,12 @@ func installOrUpdateFromLocalSource(out output.Outputer, cfg *config.Instance, a } // Run installer - an.Event(AnalyticsFunnelCat, "pre-installer") + an.Event(anaConst.CatInstallerFunnel, "pre-installer", anaConst.SrcStateTool) if err := installer.Install(); err != nil { out.Print("[ERROR]x Failed[/RESET]") return err } - an.Event(AnalyticsFunnelCat, "post-installer") + an.Event(anaConst.CatInstallerFunnel, "post-installer", anaConst.SrcStateTool) out.Print("[SUCCESS]✔ Done[/RESET]") if !params.isUpdate { @@ -342,7 +340,7 @@ func installOrUpdateFromLocalSource(out output.Outputer, cfg *config.Instance, a } func postInstallEvents(out output.Outputer, cfg *config.Instance, an analytics.Dispatcher, params *Params) error { - an.Event(AnalyticsFunnelCat, "post-install-events") + an.Event(anaConst.CatInstallerFunnel, "post-install-events", anaConst.SrcStateTool) installPath, err := resolveInstallPath(params.path) if err != nil { @@ -368,30 +366,30 @@ func postInstallEvents(out output.Outputer, cfg *config.Instance, an analytics.D switch { // Execute provided --command case params.command != "": - an.Event(AnalyticsFunnelCat, "forward-command") + an.Event(anaConst.CatInstallerFunnel, "forward-command", anaConst.SrcStateTool) out.Print(fmt.Sprintf("\nRunning `[ACTIONABLE]%s[/RESET]`\n", params.command)) cmd, args := exeutils.DecodeCmd(params.command) if _, _, err := exeutils.ExecuteAndPipeStd(cmd, args, envSlice(binPath)); err != nil { - an.EventWithLabel(AnalyticsFunnelCat, "forward-command-err", err.Error()) + an.EventWithLabel(anaConst.CatInstallerFunnel, "forward-command-err", anaConst.SrcStateTool, err.Error()) return errs.Silence(errs.Wrap(err, "Running provided command failed, error returned: %s", errs.JoinMessage(err))) } // Activate provided --activate Namespace case params.activate.IsValid(): - an.Event(AnalyticsFunnelCat, "forward-activate") + an.Event(anaConst.CatInstallerFunnel, "forward-activate", anaConst.SrcStateTool) out.Print(fmt.Sprintf("\nRunning `[ACTIONABLE]state activate %s[/RESET]`\n", params.activate.String())) if _, _, err := exeutils.ExecuteAndPipeStd(stateExe, []string{"activate", params.activate.String()}, envSlice(binPath)); err != nil { - an.EventWithLabel(AnalyticsFunnelCat, "forward-activate-err", err.Error()) + an.EventWithLabel(anaConst.CatInstallerFunnel, "forward-activate-err", anaConst.SrcStateTool, err.Error()) return errs.Silence(errs.Wrap(err, "Could not activate %s, error returned: %s", params.activate.String(), errs.JoinMessage(err))) } // Activate provided --activate-default Namespace case params.activateDefault.IsValid(): - an.Event(AnalyticsFunnelCat, "forward-activate-default") + an.Event(anaConst.CatInstallerFunnel, "forward-activate-default", anaConst.SrcStateTool) out.Print(fmt.Sprintf("\nRunning `[ACTIONABLE]state activate --default %s[/RESET]`\n", params.activateDefault.String())) if _, _, err := exeutils.ExecuteAndPipeStd(stateExe, []string{"activate", params.activateDefault.String(), "--default"}, envSlice(binPath)); err != nil { - an.EventWithLabel(AnalyticsFunnelCat, "forward-activate-default-err", err.Error()) + an.EventWithLabel(anaConst.CatInstallerFunnel, "forward-activate-default-err", anaConst.SrcStateTool, err.Error()) return errs.Silence(errs.Wrap(err, "Could not activate %s, error returned: %s", params.activateDefault.String(), errs.JoinMessage(err))) } case !params.isUpdate && terminal.IsTerminal(int(os.Stdin.Fd())) && os.Getenv(constants.InstallerNoSubshell) != "true" && os.Getenv("TERM") != "dumb": diff --git a/cmd/state-offline-installer/install.go b/cmd/state-offline-installer/install.go index 42d7d0da76..cd46faa035 100644 --- a/cmd/state-offline-installer/install.go +++ b/cmd/state-offline-installer/install.go @@ -92,9 +92,9 @@ func (r *runner) Run(params *Params) (rerr error) { return } if locale.IsInputError(rerr) { - r.analytics.EventWithLabel(ac.CatOfflineInstaller, ac.ActOfflineInstallerAbort, errs.JoinMessage(rerr), installerDimensions) + r.analytics.EventWithLabel(ac.CatOfflineInstaller, ac.ActOfflineInstallerAbort, ac.SrcOfflineInstaller, errs.JoinMessage(rerr), installerDimensions) } else { - r.analytics.EventWithLabel(ac.CatOfflineInstaller, ac.ActOfflineInstallerFailure, errs.JoinMessage(rerr), installerDimensions) + r.analytics.EventWithLabel(ac.CatOfflineInstaller, ac.ActOfflineInstallerFailure, ac.SrcOfflineInstaller, errs.JoinMessage(rerr), installerDimensions) } }() @@ -122,7 +122,7 @@ func (r *runner) Run(params *Params) (rerr error) { CommitID: &r.icfg.CommitID, Trigger: ptr.To(target.TriggerOfflineInstaller.String()), } - r.analytics.Event(ac.CatOfflineInstaller, "start", installerDimensions) + r.analytics.Event(ac.CatOfflineInstaller, "start", ac.SrcOfflineInstaller, installerDimensions) // Detect target path targetPath, err := r.getTargetPath(params.path) @@ -248,7 +248,7 @@ func (r *runner) Run(params *Params) (rerr error) { return errs.Wrap(err, "Could not configure environment") } - r.analytics.Event(ac.CatOfflineInstaller, ac.ActOfflineInstallerSuccess, installerDimensions) + r.analytics.Event(ac.CatOfflineInstaller, ac.ActOfflineInstallerSuccess, ac.SrcOfflineInstaller, installerDimensions) r.out.Print(fmt.Sprintf(`Installation complete. Your language runtime has been installed in [ACTIONABLE]%s[/RESET].`, targetPath)) diff --git a/cmd/state-offline-uninstaller/uninstall.go b/cmd/state-offline-uninstaller/uninstall.go index 08a86065fc..209891f9ff 100644 --- a/cmd/state-offline-uninstaller/uninstall.go +++ b/cmd/state-offline-uninstaller/uninstall.go @@ -79,9 +79,9 @@ func (r *runner) Run(params *Params) (rerr error) { return } if locale.IsInputError(rerr) { - r.analytics.EventWithLabel(ac.CatOfflineInstaller, ac.ActOfflineInstallerAbort, errs.JoinMessage(rerr), installerDimensions) + r.analytics.EventWithLabel(ac.CatOfflineInstaller, ac.ActOfflineInstallerAbort, ac.SrcOfflineInstaller, errs.JoinMessage(rerr), installerDimensions) } else { - r.analytics.EventWithLabel(ac.CatOfflineInstaller, ac.ActOfflineInstallerFailure, errs.JoinMessage(rerr), installerDimensions) + r.analytics.EventWithLabel(ac.CatOfflineInstaller, ac.ActOfflineInstallerFailure, ac.SrcOfflineInstaller, errs.JoinMessage(rerr), installerDimensions) } }() @@ -116,7 +116,7 @@ func (r *runner) Run(params *Params) (rerr error) { CommitID: &r.icfg.CommitID, Trigger: ptr.To(target.TriggerOfflineUninstaller.String()), } - r.analytics.Event(ac.CatOfflineInstaller, ac.ActOfflineInstallerStart, installerDimensions) + r.analytics.Event(ac.CatOfflineInstaller, ac.ActOfflineInstallerStart, ac.SrcOfflineInstaller, installerDimensions) r.out.Print("Removing environment configuration") err = r.removeEnvPaths(namespace) @@ -130,8 +130,8 @@ func (r *runner) Run(params *Params) (rerr error) { return errs.Wrap(err, "Error removing installation directory") } - r.analytics.Event(ac.CatOfflineInstaller, ac.ActOfflineInstallerSuccess, installerDimensions) - r.analytics.Event(ac.CatRuntimeUsage, ac.ActRuntimeDelete, installerDimensions) + r.analytics.Event(ac.CatOfflineInstaller, ac.ActOfflineInstallerSuccess, ac.SrcOfflineInstaller, installerDimensions) + r.analytics.Event(ac.CatRuntimeUsage, ac.ActRuntimeDelete, ac.SrcOfflineInstaller, installerDimensions) r.out.Print("Uninstall Complete") diff --git a/cmd/state-svc/internal/resolver/resolver.go b/cmd/state-svc/internal/resolver/resolver.go index b644a7a008..f027de602f 100644 --- a/cmd/state-svc/internal/resolver/resolver.go +++ b/cmd/state-svc/internal/resolver/resolver.go @@ -102,7 +102,7 @@ func (r *Resolver) Query() genserver.QueryResolver { return r } func (r *Resolver) Version(ctx context.Context) (*graph.Version, error) { defer func() { handlePanics(recover(), debug.Stack()) }() - r.an.EventWithLabel(anaConsts.CatStateSvc, "endpoint", "Version") + r.an.EventWithLabel(anaConsts.CatStateSvc, "endpoint", anaConsts.SrcStateTool, "Version") logging.Debug("Version resolver") return &graph.Version{ State: &graph.StateVersion{ @@ -118,7 +118,7 @@ func (r *Resolver) Version(ctx context.Context) (*graph.Version, error) { func (r *Resolver) AvailableUpdate(ctx context.Context) (*graph.AvailableUpdate, error) { defer func() { handlePanics(recover(), debug.Stack()) }() - r.an.EventWithLabel(anaConsts.CatStateSvc, "endpoint", "AvailableUpdate") + r.an.EventWithLabel(anaConsts.CatStateSvc, "endpoint", anaConsts.SrcStateTool, "AvailableUpdate") logging.Debug("AvailableUpdate resolver") defer logging.Debug("AvailableUpdate done") @@ -142,7 +142,7 @@ func (r *Resolver) AvailableUpdate(ctx context.Context) (*graph.AvailableUpdate, func (r *Resolver) Projects(ctx context.Context) ([]*graph.Project, error) { defer func() { handlePanics(recover(), debug.Stack()) }() - r.an.EventWithLabel(anaConsts.CatStateSvc, "endpoint", "Projects") + r.an.EventWithLabel(anaConsts.CatStateSvc, "endpoint", anaConsts.SrcStateTool, "Projects") logging.Debug("Projects resolver") var projects []*graph.Project localConfigProjects := projectfile.GetProjectMapping(r.cfg) @@ -159,10 +159,10 @@ func (r *Resolver) Projects(ctx context.Context) ([]*graph.Project, error) { return projects, nil } -func (r *Resolver) AnalyticsEvent(_ context.Context, category, action string, _label *string, dimensionsJson string) (*graph.AnalyticsEventResponse, error) { +func (r *Resolver) AnalyticsEvent(_ context.Context, category, action, source string, _label *string, dimensionsJson string) (*graph.AnalyticsEventResponse, error) { defer func() { handlePanics(recover(), debug.Stack()) }() - logging.Debug("Analytics event resolver: %s - %s", category, action) + logging.Debug("Analytics event resolver: %s - %s (%s)", category, action, source) label := "" if _label != nil { @@ -188,7 +188,7 @@ func (r *Resolver) AnalyticsEvent(_ context.Context, category, action string, _l return nil }) - r.anForClient.EventWithLabel(category, action, label, dims) + r.anForClient.EventWithLabel(category, action, source, label, dims) return &graph.AnalyticsEventResponse{Sent: true}, nil } diff --git a/cmd/state-svc/internal/rtwatcher/watcher.go b/cmd/state-svc/internal/rtwatcher/watcher.go index 60634c7040..fa22aaeaff 100644 --- a/cmd/state-svc/internal/rtwatcher/watcher.go +++ b/cmd/state-svc/internal/rtwatcher/watcher.go @@ -30,7 +30,7 @@ type Watcher struct { } type analytics interface { - Event(category string, action string, dim ...*dimensions.Values) + Event(category, action, source string, dim ...*dimensions.Values) } func New(cfg *config.Instance, an analytics) *Watcher { @@ -97,7 +97,7 @@ func (w *Watcher) check() { func (w *Watcher) RecordUsage(e entry) { logging.Debug("Recording usage of %s (%d)", e.Exec, e.PID) - w.an.Event(anaConst.CatRuntimeUsage, anaConst.ActRuntimeHeartbeat, e.Dims) + w.an.Event(anaConst.CatRuntimeUsage, anaConst.ActRuntimeHeartbeat, anaConst.SrcStateTool, e.Dims) } func (w *Watcher) Close() error { diff --git a/cmd/state-svc/internal/server/generated/generated.go b/cmd/state-svc/internal/server/generated/generated.go index c08a7f2084..104c805ca9 100644 --- a/cmd/state-svc/internal/server/generated/generated.go +++ b/cmd/state-svc/internal/server/generated/generated.go @@ -78,7 +78,7 @@ type ComplexityRoot struct { } Query struct { - AnalyticsEvent func(childComplexity int, category string, action string, label *string, dimensionsJSON string) int + AnalyticsEvent func(childComplexity int, category string, action string, source string, label *string, dimensionsJSON string) int AvailableUpdate func(childComplexity int) int CheckMessages func(childComplexity int, command string, flags []string) int CheckRuntimeUsage func(childComplexity int, organizationName string) int @@ -110,7 +110,7 @@ type QueryResolver interface { Version(ctx context.Context) (*graph.Version, error) AvailableUpdate(ctx context.Context) (*graph.AvailableUpdate, error) Projects(ctx context.Context) ([]*graph.Project, error) - AnalyticsEvent(ctx context.Context, category string, action string, label *string, dimensionsJSON string) (*graph.AnalyticsEventResponse, error) + AnalyticsEvent(ctx context.Context, category string, action string, source string, label *string, dimensionsJSON string) (*graph.AnalyticsEventResponse, error) ReportRuntimeUsage(ctx context.Context, pid int, exec string, dimensionsJSON string) (*graph.ReportRuntimeUsageResponse, error) CheckRuntimeUsage(ctx context.Context, organizationName string) (*graph.CheckRuntimeUsageResponse, error) CheckMessages(ctx context.Context, command string, flags []string) ([]*graph.MessageInfo, error) @@ -262,7 +262,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return 0, false } - return e.complexity.Query.AnalyticsEvent(childComplexity, args["category"].(string), args["action"].(string), args["label"].(*string), args["dimensionsJson"].(string)), true + return e.complexity.Query.AnalyticsEvent(childComplexity, args["category"].(string), args["action"].(string), args["source"].(string), args["label"].(*string), args["dimensionsJson"].(string)), true case "Query.availableUpdate": if e.complexity.Query.AvailableUpdate == nil { @@ -512,7 +512,7 @@ type Query { version: Version availableUpdate: AvailableUpdate projects: [Project]! - analyticsEvent(category: String!, action: String!, label: String, dimensionsJson: String!): AnalyticsEventResponse + analyticsEvent(category: String!, action: String!, source: String!, label: String, dimensionsJson: String!): AnalyticsEventResponse reportRuntimeUsage(pid: Int!, exec: String!, dimensionsJson: String!): ReportRuntimeUsageResponse checkRuntimeUsage(organizationName: String!): CheckRuntimeUsageResponse checkMessages(command: String!, flags: [String!]!): [MessageInfo!]! @@ -567,24 +567,33 @@ func (ec *executionContext) field_Query_analyticsEvent_args(ctx context.Context, } } args["action"] = arg1 - var arg2 *string + var arg2 string + if tmp, ok := rawArgs["source"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("source")) + arg2, err = ec.unmarshalNString2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["source"] = arg2 + var arg3 *string if tmp, ok := rawArgs["label"]; ok { ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("label")) - arg2, err = ec.unmarshalOString2ᚖstring(ctx, tmp) + arg3, err = ec.unmarshalOString2ᚖstring(ctx, tmp) if err != nil { return nil, err } } - args["label"] = arg2 - var arg3 string + args["label"] = arg3 + var arg4 string if tmp, ok := rawArgs["dimensionsJson"]; ok { ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("dimensionsJson")) - arg3, err = ec.unmarshalNString2string(ctx, tmp) + arg4, err = ec.unmarshalNString2string(ctx, tmp) if err != nil { return nil, err } } - args["dimensionsJson"] = arg3 + args["dimensionsJson"] = arg4 return args, nil } @@ -1620,7 +1629,7 @@ func (ec *executionContext) _Query_analyticsEvent(ctx context.Context, field gra }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().AnalyticsEvent(rctx, fc.Args["category"].(string), fc.Args["action"].(string), fc.Args["label"].(*string), fc.Args["dimensionsJson"].(string)) + return ec.resolvers.Query().AnalyticsEvent(rctx, fc.Args["category"].(string), fc.Args["action"].(string), fc.Args["source"].(string), fc.Args["label"].(*string), fc.Args["dimensionsJson"].(string)) }) if err != nil { ec.Error(ctx, err) diff --git a/cmd/state-svc/internal/server/server.go b/cmd/state-svc/internal/server/server.go index 9750b77711..f1048e1730 100644 --- a/cmd/state-svc/internal/server/server.go +++ b/cmd/state-svc/internal/server/server.go @@ -75,16 +75,16 @@ func (s *Server) Resolver() *resolver.Resolver { } func (s *Server) Start() error { - s.analytics.Event(constants.CatStateSvc, "start") + s.analytics.Event(constants.CatStateSvc, "start", constants.SrcStateTool) err := s.httpServer.Start(s.listener.Addr().String()) if err != nil { - s.analytics.Event(constants.CatStateSvc, "start-failure") + s.analytics.Event(constants.CatStateSvc, "start-failure", constants.SrcStateTool) } return err } func (s *Server) Shutdown() error { - s.analytics.Event(constants.CatStateSvc, "shutdown") + s.analytics.Event(constants.CatStateSvc, "shutdown", constants.SrcStateTool) logging.Debug("shutting down server") ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() diff --git a/cmd/state-svc/schema/schema.graphqls b/cmd/state-svc/schema/schema.graphqls index 0ab058c449..6af4224bbd 100644 --- a/cmd/state-svc/schema/schema.graphqls +++ b/cmd/state-svc/schema/schema.graphqls @@ -69,7 +69,7 @@ type Query { version: Version availableUpdate: AvailableUpdate projects: [Project]! - analyticsEvent(category: String!, action: String!, label: String, dimensionsJson: String!): AnalyticsEventResponse + analyticsEvent(category: String!, action: String!, source: String!, label: String, dimensionsJson: String!): AnalyticsEventResponse reportRuntimeUsage(pid: Int!, exec: String!, dimensionsJson: String!): ReportRuntimeUsageResponse checkRuntimeUsage(organizationName: String!): CheckRuntimeUsageResponse checkMessages(command: String!, flags: [String!]!): [MessageInfo!]! diff --git a/cmd/state/autoupdate.go b/cmd/state/autoupdate.go index 54ffc6c74e..0992bff9be 100644 --- a/cmd/state/autoupdate.go +++ b/cmd/state/autoupdate.go @@ -63,7 +63,7 @@ func autoUpdate(args []string, cfg *config.Instance, an analytics.Dispatcher, ou if !isEnabled(cfg) { logging.Debug("Not performing autoupdates because user turned off autoupdates.") - an.EventWithLabel(anaConst.CatUpdates, anaConst.ActShouldUpdate, anaConst.UpdateLabelDisabledConfig) + an.EventWithLabel(anaConst.CatUpdates, anaConst.ActShouldUpdate, anaConst.SrcStateTool, anaConst.UpdateLabelDisabledConfig) out.Notice(output.Title(locale.Tl("update_available_header", "Auto Update"))) out.Notice(locale.Tr("update_available", constants.Version, up.Version)) return false, nil @@ -77,20 +77,20 @@ func autoUpdate(args []string, cfg *config.Instance, an analytics.Dispatcher, ou err = up.InstallBlocking("") if err != nil { if os.IsPermission(err) { - an.EventWithLabel(anaConst.CatUpdates, anaConst.ActUpdateInstall, anaConst.UpdateLabelFailed, &dimensions.Values{ + an.EventWithLabel(anaConst.CatUpdates, anaConst.ActUpdateInstall, anaConst.SrcStateTool, anaConst.UpdateLabelFailed, &dimensions.Values{ TargetVersion: ptr.To(up.Version), Error: ptr.To("Could not update the state tool due to insufficient permissions."), }) return false, locale.WrapInputError(err, locale.Tl("auto_update_permission_err", "", constants.DocumentationURL, errs.JoinMessage(err))) } if errs.Matches(err, &updater.ErrorInProgress{}) { - an.EventWithLabel(anaConst.CatUpdates, anaConst.ActUpdateInstall, anaConst.UpdateLabelFailed, &dimensions.Values{ + an.EventWithLabel(anaConst.CatUpdates, anaConst.ActUpdateInstall, anaConst.SrcStateTool, anaConst.UpdateLabelFailed, &dimensions.Values{ TargetVersion: ptr.To(up.Version), Error: ptr.To(anaConst.UpdateErrorInProgress), }) return false, nil } - an.EventWithLabel(anaConst.CatUpdates, anaConst.ActUpdateInstall, anaConst.UpdateLabelFailed, &dimensions.Values{ + an.EventWithLabel(anaConst.CatUpdates, anaConst.ActUpdateInstall, anaConst.SrcStateTool, anaConst.UpdateLabelFailed, &dimensions.Values{ TargetVersion: ptr.To(up.Version), Error: ptr.To(anaConst.UpdateErrorInstallFailed), }) @@ -108,14 +108,14 @@ func autoUpdate(args []string, cfg *config.Instance, an analytics.Dispatcher, ou } else if errs.Matches(err, &ErrExecuteRelaunch{}) { msg = anaConst.UpdateErrorRelaunch } - an.EventWithLabel(anaConst.CatUpdates, anaConst.ActUpdateRelaunch, anaConst.UpdateLabelFailed, &dimensions.Values{ + an.EventWithLabel(anaConst.CatUpdates, anaConst.ActUpdateRelaunch, anaConst.SrcStateTool, anaConst.UpdateLabelFailed, &dimensions.Values{ TargetVersion: ptr.To(up.Version), Error: ptr.To(msg), }) return true, errs.Silence(errs.WrapExitCode(err, code)) } - an.EventWithLabel(anaConst.CatUpdates, anaConst.ActUpdateRelaunch, anaConst.UpdateLabelSuccess, &dimensions.Values{ + an.EventWithLabel(anaConst.CatUpdates, anaConst.ActUpdateRelaunch, anaConst.SrcStateTool, anaConst.UpdateLabelSuccess, &dimensions.Values{ TargetVersion: ptr.To(up.Version), }) return true, nil @@ -187,7 +187,7 @@ func shouldRunAutoUpdate(args []string, cfg *config.Instance, an analytics.Dispa label = anaConst.UpdateLabelLocked } - an.EventWithLabel(anaConst.CatUpdates, anaConst.ActShouldUpdate, label) + an.EventWithLabel(anaConst.CatUpdates, anaConst.ActShouldUpdate, anaConst.SrcStateTool, label) return shouldUpdate } diff --git a/cmd/state/internal/cmdtree/activate.go b/cmd/state/internal/cmdtree/activate.go index d675e300b4..0202124e30 100644 --- a/cmd/state/internal/cmdtree/activate.go +++ b/cmd/state/internal/cmdtree/activate.go @@ -66,19 +66,19 @@ func newActivateCommand(prime *primer.Values) *captain.Command { an := prime.Analytics() var serr interface{ Signal() os.Signal } if errors.As(err, &serr) { - an.Event(constants.CatActivationFlow, "user-interrupt-error") + an.Event(constants.CatActivationFlow, "user-interrupt-error", constants.SrcStateTool) } if locale.IsInputError(err) { // Failed due to user input - an.Event(constants.CatActivationFlow, "user-input-error") + an.Event(constants.CatActivationFlow, "user-input-error", constants.SrcStateTool) } else { var exitErr = &exec.ExitError{} if !errors.As(err, &exitErr) { // Failed due to an error we might need to address - an.Event(constants.CatActivationFlow, "error") + an.Event(constants.CatActivationFlow, "error", constants.SrcStateTool) } else { // Failed due to user subshell actions / events - an.Event(constants.CatActivationFlow, "user-exit-error") + an.Event(constants.CatActivationFlow, "user-exit-error", constants.SrcStateTool) } } } diff --git a/cmd/state/internal/cmdtree/tutorial.go b/cmd/state/internal/cmdtree/tutorial.go index af69f9a785..8c669867d9 100644 --- a/cmd/state/internal/cmdtree/tutorial.go +++ b/cmd/state/internal/cmdtree/tutorial.go @@ -53,9 +53,9 @@ func newTutorialProjectCommand(prime *primer.Values) *captain.Command { func(ccmd *captain.Command, args []string) error { err := runner.RunNewProject(params) if err != nil { - prime.Analytics().EventWithLabel(constants.CatTutorial, "error", errs.JoinMessage(err)) + prime.Analytics().EventWithLabel(constants.CatTutorial, "error", constants.SrcStateTool, errs.JoinMessage(err)) } else { - prime.Analytics().Event(constants.CatTutorial, "completed") + prime.Analytics().Event(constants.CatTutorial, "completed", constants.SrcStateTool) } return err }, diff --git a/go.sum b/go.sum index dcf45175a8..14d761555c 100644 --- a/go.sum +++ b/go.sum @@ -341,10 +341,6 @@ github.com/99designs/gqlgen v0.17.19 h1:lO3PBSOv5Mw8RPt0Nwg7ovJ9tNfjGDEnU48AYqLz github.com/99designs/gqlgen v0.17.19/go.mod h1:tXjo2/o1L/9A8S/4nLK7Arx/677H8hxlD/iSTRx0BtE= github.com/ActiveState/go-ogle-analytics v0.0.0-20170510030904-9b3f14901527 h1:lW+qgVXf/iAnSs8SgagWxh8d6nsbpmwyhmeg9/fp0Os= github.com/ActiveState/go-ogle-analytics v0.0.0-20170510030904-9b3f14901527/go.mod h1:/9SyzKLlJSuIa7WAsLUUhHqTK9+PtZD8cKF8G4SLpa0= -github.com/ActiveState/graphql v0.0.0-20180524141115-05b17f315749 h1:6Uj/oFUEJ7UcQ721u2Smti9IIy9lPHEho50buO4ZYXE= -github.com/ActiveState/graphql v0.0.0-20180524141115-05b17f315749/go.mod h1:NhUbNQ8UpfnC6nZvZ8oThqYSCE/G8FQp9JUrK9jXJs0= -github.com/ActiveState/graphql v0.0.0-20230718220857-da7d147af6b4 h1:f2n4heMWzHlHUqQZuV2hTPO5bU95WEQrT76qyfS6qtE= -github.com/ActiveState/graphql v0.0.0-20230718220857-da7d147af6b4/go.mod h1:NhUbNQ8UpfnC6nZvZ8oThqYSCE/G8FQp9JUrK9jXJs0= github.com/ActiveState/graphql v0.0.0-20230719154233-6949037a6e48 h1:UCx/ObpVRgC4fp2OlJM2iNdMMu+K87/aPxKrQ1WRLj4= github.com/ActiveState/graphql v0.0.0-20230719154233-6949037a6e48/go.mod h1:NhUbNQ8UpfnC6nZvZ8oThqYSCE/G8FQp9JUrK9jXJs0= github.com/ActiveState/termtest v0.7.2 h1:vNPMpI2AyZnLZzZn/CRpMZzIuVL0XhaTLA4qyghIGF8= diff --git a/internal/analytics/analytics.go b/internal/analytics/analytics.go index 4edc267590..6f2aaa3aca 100644 --- a/internal/analytics/analytics.go +++ b/internal/analytics/analytics.go @@ -8,8 +8,8 @@ import ( // Dispatcher describes a struct that can send analytics event in the background type Dispatcher interface { - Event(category string, action string, dim ...*dimensions.Values) - EventWithLabel(category string, action string, label string, dim ...*dimensions.Values) + Event(category, action, source string, dim ...*dimensions.Values) + EventWithLabel(category, action, source, label string, dim ...*dimensions.Values) Wait() Close() } diff --git a/internal/analytics/client/async/client.go b/internal/analytics/client/async/client.go index 9793a435c1..c590453c11 100644 --- a/internal/analytics/client/async/client.go +++ b/internal/analytics/client/async/client.go @@ -75,13 +75,13 @@ func New(svcModel *model.SvcModel, cfg *config.Instance, auth *authentication.Au } // Event logs an event to google analytics -func (a *Client) Event(category string, action string, dims ...*dimensions.Values) { - a.EventWithLabel(category, action, "", dims...) +func (a *Client) Event(category, action, source string, dims ...*dimensions.Values) { + a.EventWithLabel(category, action, source, "", dims...) } // EventWithLabel logs an event with a label to google analytics -func (a *Client) EventWithLabel(category string, action string, label string, dims ...*dimensions.Values) { - err := a.sendEvent(category, action, label, dims...) +func (a *Client) EventWithLabel(category, action, source, label string, dims ...*dimensions.Values) { + err := a.sendEvent(category, action, source, label, dims...) if err != nil { multilog.Error("Error during analytics.sendEvent: %v", errs.JoinMessage(err)) } @@ -98,7 +98,7 @@ func (a *Client) Wait() { a.eventWaitGroup.Wait() } -func (a *Client) sendEvent(category, action, label string, dims ...*dimensions.Values) error { +func (a *Client) sendEvent(category, action, source, label string, dims ...*dimensions.Values) error { if a.svcModel == nil { // this is only true on CI return nil } @@ -133,7 +133,7 @@ func (a *Client) sendEvent(category, action, label string, dims ...*dimensions.V defer func() { handlePanics(recover(), debug.Stack()) }() defer a.eventWaitGroup.Done() - if err := a.svcModel.AnalyticsEvent(context.Background(), category, action, label, string(dimMarshalled)); err != nil { + if err := a.svcModel.AnalyticsEvent(context.Background(), category, action, source, label, string(dimMarshalled)); err != nil { logging.Debug("Failed to report analytics event via state-svc: %s", errs.JoinMessage(err)) } }() diff --git a/internal/analytics/client/blackhole/client.go b/internal/analytics/client/blackhole/client.go index c36cf79d1b..a7ac5d4431 100644 --- a/internal/analytics/client/blackhole/client.go +++ b/internal/analytics/client/blackhole/client.go @@ -13,10 +13,10 @@ func New() *Client { return &Client{} } -func (c Client) Event(category string, action string, dim ...*dimensions.Values) { +func (c Client) Event(category, action, source string, dim ...*dimensions.Values) { } -func (c Client) EventWithLabel(category string, action string, label string, dim ...*dimensions.Values) { +func (c Client) EventWithLabel(category, action, source, label string, dim ...*dimensions.Values) { } func (c Client) Wait() { diff --git a/internal/analytics/client/sync/client.go b/internal/analytics/client/sync/client.go index 36d725fb14..6ffee1f480 100644 --- a/internal/analytics/client/sync/client.go +++ b/internal/analytics/client/sync/client.go @@ -32,7 +32,7 @@ import ( type Reporter interface { ID() string - Event(category, action, label string, dimensions *dimensions.Values) error + Event(category, action, source, label string, dimensions *dimensions.Values) error } // Client instances send analytics events to GA and S3 endpoints without delay. It is only supposed to be used inside the `state-svc`. All other processes should use the DefaultClient. @@ -150,13 +150,13 @@ func (a *Client) Wait() { } // Events returns a channel to feed eventData directly to the report loop -func (a *Client) report(category, action, label string, dimensions *dimensions.Values) { +func (a *Client) report(category, action, source, label string, dimensions *dimensions.Values) { if !a.sendReports { return } for _, reporter := range a.reporters { - if err := reporter.Event(category, action, label, dimensions); err != nil { + if err := reporter.Event(category, action, source, label, dimensions); err != nil { logging.Debug( "Reporter failed: %s, category: %s, action: %s, error: %s", reporter.ID(), category, action, errs.JoinMessage(err), @@ -165,8 +165,8 @@ func (a *Client) report(category, action, label string, dimensions *dimensions.V } } -func (a *Client) Event(category string, action string, dims ...*dimensions.Values) { - a.EventWithLabel(category, action, "", dims...) +func (a *Client) Event(category, action, source string, dims ...*dimensions.Values) { + a.EventWithLabel(category, action, source, "", dims...) } func mergeDimensions(target *dimensions.Values, dims ...*dimensions.Values) *dimensions.Values { @@ -180,7 +180,7 @@ func mergeDimensions(target *dimensions.Values, dims ...*dimensions.Values) *dim return actualDims } -func (a *Client) EventWithLabel(category string, action, label string, dims ...*dimensions.Values) { +func (a *Client) EventWithLabel(category, action, source, label string, dims ...*dimensions.Values) { if a.customDimensions == nil { if condition.InUnitTest() { return @@ -210,7 +210,7 @@ func (a *Client) EventWithLabel(category string, action, label string, dims ...* go func() { defer a.eventWaitGroup.Done() defer func() { handlePanics(recover(), debug.Stack()) }() - a.report(category, action, label, actualDims) + a.report(category, action, source, label, actualDims) }() } diff --git a/internal/analytics/client/sync/reporters/ga-state.go b/internal/analytics/client/sync/reporters/ga-state.go index 623b7470d8..afdec89b64 100644 --- a/internal/analytics/client/sync/reporters/ga-state.go +++ b/internal/analytics/client/sync/reporters/ga-state.go @@ -44,7 +44,7 @@ func (r *GaCLIReporter) AddOmitCategory(category string) { r.omit[category] = struct{}{} } -func (r *GaCLIReporter) Event(category, action, label string, d *dimensions.Values) error { +func (r *GaCLIReporter) Event(category, action, source, label string, d *dimensions.Values) error { if _, ok := r.omit[category]; ok { logging.Debug("Not sending event with category: %s to Google Analytics", category) return nil diff --git a/internal/analytics/client/sync/reporters/pixel.go b/internal/analytics/client/sync/reporters/pixel.go index 8a9fde09d7..d42588862b 100644 --- a/internal/analytics/client/sync/reporters/pixel.go +++ b/internal/analytics/client/sync/reporters/pixel.go @@ -29,7 +29,7 @@ func (r *PixelReporter) ID() string { return "PixelReporter" } -func (r *PixelReporter) Event(category, action, label string, d *dimensions.Values) error { +func (r *PixelReporter) Event(category, action, source, label string, d *dimensions.Values) error { pixelURL, err := url.Parse(r.url) if err != nil { return errs.Wrap(err, "Invalid pixel URL: %s", r.url) @@ -38,6 +38,7 @@ func (r *PixelReporter) Event(category, action, label string, d *dimensions.Valu query := &url.Values{} query.Add("x-category", category) query.Add("x-action", action) + query.Add("x-source", source) query.Add("x-label", label) for num, value := range legacyDimensionMap(d) { diff --git a/internal/analytics/client/sync/reporters/test.go b/internal/analytics/client/sync/reporters/test.go index b8c60dfe08..060cf6051f 100644 --- a/internal/analytics/client/sync/reporters/test.go +++ b/internal/analytics/client/sync/reporters/test.go @@ -36,12 +36,13 @@ func (r *TestReporter) ID() string { type TestLogEntry struct { Category string Action string + Source string Label string Dimensions *dimensions.Values } -func (r *TestReporter) Event(category, action, label string, d *dimensions.Values) error { - b, err := json.Marshal(TestLogEntry{category, action, label, d}) +func (r *TestReporter) Event(category, action, source, label string, d *dimensions.Values) error { + b, err := json.Marshal(TestLogEntry{category, action, souce, label, d}) if err != nil { return errs.Wrap(err, "Could not marshal test log entry") } diff --git a/internal/analytics/constants/constants.go b/internal/analytics/constants/constants.go index b2bf3dfa4f..60202bf803 100644 --- a/internal/analytics/constants/constants.go +++ b/internal/analytics/constants/constants.go @@ -25,6 +25,21 @@ const CatConfig = "config" // CatUpdate is the event category used for all update events const CatUpdates = "updates" +// CatInstaller is the event category used for installer events. +const CatInstaller = "installer" + +// CatInstallerFunnel is the event category used for installer funnel events. +const CatInstallerFunnel = "installer-funnel" + +// SrcStateTool is the event source for State Tool. +const SrcStateTool = "State Tool" + +// SrcOfflineInstaller is the event source for offline installers. +const SrcOfflineInstaller = "Offline Installer" + +// SrcExecutor is the event source for executors. +const SrcExecutor = "Executor" + // ActRuntimeHeartbeat is the event action sent when a runtime is in use const ActRuntimeHeartbeat = "heartbeat" diff --git a/internal/captain/command.go b/internal/captain/command.go index 37e1cd4b21..9708b9da3e 100644 --- a/internal/captain/command.go +++ b/internal/captain/command.go @@ -634,10 +634,10 @@ func (c *Command) cobraExecHandler(cobraCmd *cobra.Command, args []string) error label = append(label, name) }) - c.analytics.EventWithLabel(anaConsts.CatRunCmd, appEventPrefix+subCommandString, strings.Join(label, " ")) + c.analytics.EventWithLabel(anaConsts.CatRunCmd, appEventPrefix+subCommandString, anaConsts.SrcStateTool, strings.Join(label, " ")) if shim, got := os.LookupEnv(constants.ShimEnvVarName); got { - c.analytics.Event(anaConsts.CatShim, shim) + c.analytics.Event(anaConsts.CatShim, shim, anaConsts.SrcStateTool) } } @@ -708,16 +708,16 @@ func (c *Command) cobraExecHandler(cobraCmd *cobra.Command, args []string) error var serr interface{ Signal() os.Signal } if errors.As(err, &serr) { if c.analytics != nil { - c.analytics.EventWithLabel(anaConsts.CatCommandExit, appEventPrefix+subCommandString, "interrupt") + c.analytics.EventWithLabel(anaConsts.CatCommandExit, appEventPrefix+subCommandString, anaConsts.SrcStateTool, "interrupt") } err = locale.WrapInputError(err, "user_interrupt", "User interrupted the State Tool process.") } else { if c.analytics != nil { if err != nil && (subCommandString == "install" || subCommandString == "activate") { // This is a temporary hack; proper implementation: https://activestatef.atlassian.net/browse/DX-495 - c.analytics.EventWithLabel(anaConsts.CatCommandError, appEventPrefix+subCommandString, errs.JoinMessage(err)) + c.analytics.EventWithLabel(anaConsts.CatCommandError, appEventPrefix+subCommandString, anaConsts.SrcStateTool, errs.JoinMessage(err)) } - c.analytics.EventWithLabel(anaConsts.CatCommandExit, appEventPrefix+subCommandString, strconv.Itoa(exitCode)) + c.analytics.EventWithLabel(anaConsts.CatCommandExit, appEventPrefix+subCommandString, anaConsts.SrcStateTool, strconv.Itoa(exitCode)) } } diff --git a/internal/graph/generated.go b/internal/graph/generated.go index b7102e0eff..62610a04aa 100644 --- a/internal/graph/generated.go +++ b/internal/graph/generated.go @@ -38,15 +38,6 @@ type MessageInfo struct { Placement MessagePlacementType `json:"placement"` } -/* -[ - { - "ID": "simple", - "Message": "This is a [NOTICE]simple[/RESET] message\nwith a line break", - } -] -*/ - type Project struct { Namespace string `json:"namespace"` Locations []string `json:"locations"` diff --git a/internal/prompt/prompt.go b/internal/prompt/prompt.go index ad5044acc8..9e3759dc79 100644 --- a/internal/prompt/prompt.go +++ b/internal/prompt/prompt.go @@ -15,7 +15,7 @@ import ( ) type EventDispatcher interface { - EventWithLabel(category string, action string, label string, dim ...*dimensions.Values) + EventWithLabel(category, action, source string, label string, dim ...*dimensions.Values) } // Prompter is the interface used to run our prompt from, useful for mocking in tests @@ -169,7 +169,7 @@ func (p *Prompt) Confirm(title, message string, defaultChoice *bool) (bool, erro p.out.Notice(output.Emphasize(title)) } - p.analytics.EventWithLabel(constants.CatPrompt, title, "present") + p.analytics.EventWithLabel(constants.CatPrompt, title, constants.SrcStateTool, "present") var defChoice bool if defaultChoice != nil { @@ -183,11 +183,11 @@ func (p *Prompt) Confirm(title, message string, defaultChoice *bool) (bool, erro }}, &resp, nil) if err != nil { if err == terminal.InterruptErr { - p.analytics.EventWithLabel(constants.CatPrompt, title, "interrupt") + p.analytics.EventWithLabel(constants.CatPrompt, title, constants.SrcStateTool, "interrupt") } return false, locale.NewInputError(err.Error()) } - p.analytics.EventWithLabel(constants.CatPrompt, title, translateConfirm(resp)) + p.analytics.EventWithLabel(constants.CatPrompt, title, constants.SrcStateTool, translateConfirm(resp)) return resp, nil } diff --git a/internal/runbits/activation/activation.go b/internal/runbits/activation/activation.go index d6d23740d8..da23f7b4d7 100644 --- a/internal/runbits/activation/activation.go +++ b/internal/runbits/activation/activation.go @@ -80,7 +80,7 @@ func ActivateAndWait( } defer fe.Close() - an.Event(anaConst.CatActivationFlow, "before-subshell") + an.Event(anaConst.CatActivationFlow, "before-subshell", anaConst.SrcStateTool) err = <-ss.Errors() if err != nil { diff --git a/internal/runbits/requirements/requirements.go b/internal/runbits/requirements/requirements.go index c3d7d81ac7..2a0f4b82f3 100644 --- a/internal/runbits/requirements/requirements.go +++ b/internal/runbits/requirements/requirements.go @@ -204,7 +204,7 @@ func (r *RequirementOperation) ExecuteRequirementOperation(requirementName, requ } r.Analytics.EventWithLabel( - anaConsts.CatPackageOp, fmt.Sprintf("%s-%s", operation, langName), requirementName, + anaConsts.CatPackageOp, fmt.Sprintf("%s-%s", operation, langName), anaConsts.SrcStateTool, requirementName, ) if !hasParentCommit { diff --git a/internal/runners/activate/activate.go b/internal/runners/activate/activate.go index 14fd6ba218..7366816080 100644 --- a/internal/runners/activate/activate.go +++ b/internal/runners/activate/activate.go @@ -151,7 +151,7 @@ func (r *Activate) Run(params *ActivateParams) error { } // Have to call this once the project has been set - r.analytics.Event(anaConsts.CatActivationFlow, "start") + r.analytics.Event(anaConsts.CatActivationFlow, "start", anaConsts.SrcStateTool) proj.Source().Persist() diff --git a/internal/runners/config/set.go b/internal/runners/config/set.go index dca1705333..8b78072531 100644 --- a/internal/runners/config/set.go +++ b/internal/runners/config/set.go @@ -106,5 +106,5 @@ func (s *Set) sendEvent(key string, value string, option configMediator.Option) } } - s.analytics.EventWithLabel(constants.CatConfig, action, key) + s.analytics.EventWithLabel(constants.CatConfig, action, constants.SrcStateTool, key) } diff --git a/internal/runners/deploy/uninstall/uninstall.go b/internal/runners/deploy/uninstall/uninstall.go index 2ffde0e656..56f18b693b 100644 --- a/internal/runners/deploy/uninstall/uninstall.go +++ b/internal/runners/deploy/uninstall/uninstall.go @@ -99,7 +99,7 @@ func (u *Uninstall) Run(params *Params) error { return locale.WrapError(err, "err_deploy_uninstall", "Unable to remove deployed runtime at '{{.V0}}'", path) } - u.analytics.Event(constants.CatRuntimeUsage, constants.ActRuntimeDelete, &dimensions.Values{ + u.analytics.Event(constants.CatRuntimeUsage, constants.ActRuntimeDelete, constants.SrcStateTool, &dimensions.Values{ Trigger: ptr.To(target.TriggerDeploy.String()), CommitID: ptr.To(commitID), ProjectNameSpace: ptr.To(namespace), diff --git a/internal/runners/ppm/convert.go b/internal/runners/ppm/convert.go index 561130d135..1220dda201 100644 --- a/internal/runners/ppm/convert.go +++ b/internal/runners/ppm/convert.go @@ -53,24 +53,24 @@ func (cf *ConversionFlow) StartIfNecessary() (bool, error) { return false, nil } - cf.analytics.Event(anaConsts.CatPpmConversion, "run") + cf.analytics.Event(anaConsts.CatPpmConversion, "run", anaConsts.SrcStateTool) r, err := cf.runSurvey() if err != nil { - cf.analytics.EventWithLabel(anaConsts.CatPpmConversion, "error", errs.JoinMessage(err)) + cf.analytics.EventWithLabel(anaConsts.CatPpmConversion, "error", anaConsts.SrcStateTool, errs.JoinMessage(err)) return true, locale.WrapError(err, "ppm_conversion_survey_error", "Conversion flow failed.") } if r != accepted { - cf.analytics.EventWithLabel(anaConsts.CatPpmConversion, "completed", r.String()) + cf.analytics.EventWithLabel(anaConsts.CatPpmConversion, "completed", anaConsts.SrcStateTool, r.String()) return true, locale.NewInputError("ppm_conversion_rejected", "Virtual environment creation cancelled.") } err = cf.createVirtualEnv() if err != nil { - cf.analytics.EventWithLabel(anaConsts.CatPpmConversion, "error", errs.JoinMessage(err)) + cf.analytics.EventWithLabel(anaConsts.CatPpmConversion, "error", anaConsts.SrcStateTool, errs.JoinMessage(err)) return true, locale.WrapError(err, "ppm_conversion_venv_error", "Failed to create a project.") } - cf.analytics.EventWithLabel(anaConsts.CatPpmConversion, "completed", r.String()) + cf.analytics.EventWithLabel(anaConsts.CatPpmConversion, "completed", anaConsts.SrcStateTool, r.String()) return true, nil } @@ -103,7 +103,7 @@ func (cf *ConversionFlow) runSurvey() (conversionResult, error) { choices[0]: "create-virtual-env-1", choices[1]: "asked-why", } - cf.analytics.EventWithLabel(anaConsts.CatPpmConversion, "selection", eventChoices[choice]) + cf.analytics.EventWithLabel(anaConsts.CatPpmConversion, "selection", anaConsts.SrcStateTool, eventChoices[choice]) if choice == choices[0] { return accepted, nil @@ -141,7 +141,7 @@ func (cf *ConversionFlow) explainVirtualEnv() (conversionResult, error) { convertAnswerCreate: "create-virtual-env-2", no: "still-wants-ppm", } - cf.analytics.EventWithLabel(anaConsts.CatPpmConversion, "selection", eventChoices[choice]) + cf.analytics.EventWithLabel(anaConsts.CatPpmConversion, "selection", anaConsts.SrcStateTool, eventChoices[choice]) switch choice { case convertAnswerCreate: @@ -179,7 +179,7 @@ func (cf *ConversionFlow) explainAskFeedback() (conversionResult, error) { ok: "create-virtual-env-3", exit: "exit", } - cf.analytics.EventWithLabel(anaConsts.CatPpmConversion, "selection", eventChoices[choice]) + cf.analytics.EventWithLabel(anaConsts.CatPpmConversion, "selection", anaConsts.SrcStateTool, eventChoices[choice]) if choice == ok { return accepted, nil diff --git a/internal/runners/ppm/shim.go b/internal/runners/ppm/shim.go index 6d375cbcce..8164220868 100644 --- a/internal/runners/ppm/shim.go +++ b/internal/runners/ppm/shim.go @@ -81,9 +81,9 @@ func (s *Shim) RunList(converted bool, args ...string) error { func (s *Shim) shim(intercepted, replaced string, args ...string) error { err := s.executeShim(intercepted, replaced, args...) if err != nil { - s.analytics.EventWithLabel(constants.CatPPMShimCmd, intercepted, fmt.Sprintf("error: %v", errs.JoinMessage(err))) + s.analytics.EventWithLabel(constants.CatPPMShimCmd, intercepted, constants.SrcStateTool, fmt.Sprintf("error: %v", errs.JoinMessage(err))) } else { - s.analytics.EventWithLabel(constants.CatPPMShimCmd, intercepted, "success") + s.analytics.EventWithLabel(constants.CatPPMShimCmd, intercepted, constants.SrcStateTool, "success") } return err } @@ -91,7 +91,7 @@ func (s *Shim) shim(intercepted, replaced string, args ...string) error { func (s *Shim) executeShim(intercepted, replaced string, args ...string) error { if s.project == nil { // TODO: Replace this function call when conversion flow is complete - s.analytics.Event(constants.CatPPMShimCmd, "tutorial") + s.analytics.Event(constants.CatPPMShimCmd, "tutorial", constants.SrcStateTool) return tutorial() } diff --git a/internal/runners/tutorial/tutorial.go b/internal/runners/tutorial/tutorial.go index 2bc961ed35..867d273f79 100644 --- a/internal/runners/tutorial/tutorial.go +++ b/internal/runners/tutorial/tutorial.go @@ -12,8 +12,8 @@ import ( "github.com/ActiveState/cli/internal/fileutils" "github.com/ActiveState/cli/internal/language" "github.com/ActiveState/cli/internal/locale" - "github.com/ActiveState/cli/internal/output" "github.com/ActiveState/cli/internal/osutils/user" + "github.com/ActiveState/cli/internal/output" "github.com/ActiveState/cli/internal/primer" "github.com/ActiveState/cli/internal/prompt" "github.com/ActiveState/cli/internal/runbits" @@ -45,7 +45,7 @@ type NewProjectParams struct { } func (t *Tutorial) RunNewProject(params NewProjectParams) error { - t.analytics.EventWithLabel(anaConsts.CatTutorial, "run", fmt.Sprintf("skipIntro=%v,language=%v", params.SkipIntro, params.Language.String())) + t.analytics.EventWithLabel(anaConsts.CatTutorial, "run", anaConsts.SrcStateTool, fmt.Sprintf("skipIntro=%v,language=%v", params.SkipIntro, params.Language.String())) // Print intro if !params.SkipIntro { @@ -75,7 +75,7 @@ func (t *Tutorial) RunNewProject(params NewProjectParams) error { if lang == language.Unknown || lang == language.Unset { return locale.NewError("err_tutorial_language_unknown", "Invalid language selected: {{.V0}}.", choice) } - t.analytics.EventWithLabel(anaConsts.CatTutorial, "choose-language", lang.String()) + t.analytics.EventWithLabel(anaConsts.CatTutorial, "choose-language", anaConsts.SrcStateTool, lang.String()) } // Prompt for project name @@ -125,7 +125,7 @@ func (t *Tutorial) RunNewProject(params NewProjectParams) error { // authFlow is invoked when the user is not authenticated, it will prompt for sign in or sign up func (t *Tutorial) authFlow() error { - t.analytics.Event(anaConsts.CatTutorial, "authentication-flow") + t.analytics.Event(anaConsts.CatTutorial, "authentication-flow", anaConsts.SrcStateTool) // Sign in / Sign up choices signIn := locale.Tl("tutorial_signin", "Sign In") @@ -147,17 +147,17 @@ func (t *Tutorial) authFlow() error { // Evaluate user selection switch choice { case signIn: - t.analytics.EventWithLabel(anaConsts.CatTutorial, "authentication-action", "sign-in") + t.analytics.EventWithLabel(anaConsts.CatTutorial, "authentication-action", anaConsts.SrcStateTool, "sign-in") if err := runbits.Invoke(t.outputer, "auth"); err != nil { return locale.WrapInputError(err, "err_tutorial_signin", "Sign in failed. You could try manually signing in by running `state auth`.") } case signUpCLI: - t.analytics.EventWithLabel(anaConsts.CatTutorial, "authentication-action", "sign-up") + t.analytics.EventWithLabel(anaConsts.CatTutorial, "authentication-action", anaConsts.SrcStateTool, "sign-up") if err := runbits.Invoke(t.outputer, "auth", "signup"); err != nil { return locale.WrapInputError(err, "err_tutorial_signup", "Sign up failed. You could try manually signing up by running `state auth signup`.") } case signUpBrowser: - t.analytics.EventWithLabel(anaConsts.CatTutorial, "authentication-action", "sign-up-browser") + t.analytics.EventWithLabel(anaConsts.CatTutorial, "authentication-action", anaConsts.SrcStateTool, "sign-up-browser") err := open.Run(constants.PlatformSignupURL) if err != nil { return locale.WrapInputError(err, "err_tutorial_browser", "Could not open browser, please manually navigate to {{.V0}}.", constants.PlatformSignupURL) @@ -172,7 +172,7 @@ func (t *Tutorial) authFlow() error { return locale.WrapError(err, "err_tutorial_auth", "Could not authenticate after invoking `state auth ..`.") } - t.analytics.Event(anaConsts.CatTutorial, "authentication-flow-complete") + t.analytics.Event(anaConsts.CatTutorial, "authentication-flow-complete", anaConsts.SrcStateTool) return nil } diff --git a/internal/svcctl/comm.go b/internal/svcctl/comm.go index eda636e675..ccdd848253 100644 --- a/internal/svcctl/comm.go +++ b/internal/svcctl/comm.go @@ -78,7 +78,7 @@ type Resolver interface { } type AnalyticsReporter interface { - Event(category string, action string, dims ...*dimensions.Values) + Event(category, action, source string, dims ...*dimensions.Values) } func HeartbeatHandler(cfg *config.Instance, resolver Resolver, analyticsReporter AnalyticsReporter) ipc.RequestHandler { @@ -138,7 +138,7 @@ func HeartbeatHandler(cfg *config.Instance, resolver Resolver, analyticsReporter } logging.Debug("Firing runtime usage events for %s", metaData.Namespace) - analyticsReporter.Event(constants.CatRuntimeUsage, constants.ActRuntimeAttempt, dims) + analyticsReporter.Event(constants.CatRuntimeUsage, constants.ActRuntimeAttempt, constants.SrcStateTool, dims) _, err = resolver.ReportRuntimeUsage(context.Background(), pidNum, hb.ExecPath, dimsJSON) if err != nil { multilog.Critical("Heartbeat Failure: Failed to report runtime usage in heartbeat handler: %s", errs.JoinMessage(err)) diff --git a/internal/updater/checker.go b/internal/updater/checker.go index 1e2e8de265..b6c3031659 100644 --- a/internal/updater/checker.go +++ b/internal/updater/checker.go @@ -143,7 +143,7 @@ func (u *Checker) GetUpdateInfo(desiredChannel, desiredVersion string) (*Availab err = errs.Wrap(err, "Could not fetch update info from %s", infoURL) } - u.an.EventWithLabel(anaConst.CatUpdates, anaConst.ActUpdateCheck, label, &dimensions.Values{ + u.an.EventWithLabel(anaConst.CatUpdates, anaConst.ActUpdateCheck, anaConst.SrcStateTool, label, &dimensions.Values{ Version: ptr.To(desiredVersion), Error: ptr.To(msg), }) @@ -159,6 +159,6 @@ func (u *Checker) GetUpdateInfo(desiredChannel, desiredVersion string) (*Availab info.url = u.fileURL + "/" + info.Path - u.an.EventWithLabel(anaConst.CatUpdates, anaConst.ActUpdateCheck, anaConst.UpdateLabelAvailable, &dimensions.Values{Version: ptr.To(info.Version)}) + u.an.EventWithLabel(anaConst.CatUpdates, anaConst.ActUpdateCheck, anaConst.SrcStateTool, anaConst.UpdateLabelAvailable, &dimensions.Values{Version: ptr.To(info.Version)}) return info, nil } diff --git a/internal/updater/fetcher.go b/internal/updater/fetcher.go index 747d75d976..af8d5c41d6 100644 --- a/internal/updater/fetcher.go +++ b/internal/updater/fetcher.go @@ -70,7 +70,7 @@ func (f *Fetcher) Fetch(update *AvailableUpdate, targetDir string) error { } func (f *Fetcher) analyticsEvent(version, msg string) { - f.an.EventWithLabel(anaConst.CatUpdates, anaConst.ActUpdateDownload, anaConst.UpdateLabelFailed, &dimensions.Values{ + f.an.EventWithLabel(anaConst.CatUpdates, anaConst.ActUpdateDownload, anaConst.SrcStateTool, anaConst.UpdateLabelFailed, &dimensions.Values{ TargetVersion: ptr.To(version), Error: ptr.To(msg), }) diff --git a/internal/updater/updater.go b/internal/updater/updater.go index d72bcb7881..d656dd1ebc 100644 --- a/internal/updater/updater.go +++ b/internal/updater/updater.go @@ -207,5 +207,5 @@ func (u *AvailableUpdate) analyticsEvent(action, label, version, msg string) { dims.Error = ptr.To(msg) } - u.an.EventWithLabel(anaConst.CatUpdates, anaConst.ActUpdateDownload, label, dims) + u.an.EventWithLabel(anaConst.CatUpdates, anaConst.ActUpdateDownload, anaConst.SrcStateTool, label, dims) } diff --git a/pkg/cmdlets/errors/errors.go b/pkg/cmdlets/errors/errors.go index a47b2747c0..0c0af27b5f 100644 --- a/pkg/cmdlets/errors/errors.go +++ b/pkg/cmdlets/errors/errors.go @@ -153,7 +153,7 @@ func ReportError(err error, cmd *captain.Command, an analytics.Dispatcher) { } } - an.EventWithLabel(anaConst.CatDebug, action, strings.Join(label, " "), &dimensions.Values{ + an.EventWithLabel(anaConst.CatDebug, action, anaConst.SrcStateTool, strings.Join(label, " "), &dimensions.Values{ Error: ptr.To(errorMsg), }) diff --git a/pkg/cmdlets/git/git.go b/pkg/cmdlets/git/git.go index f0e496685a..70c7e0e75b 100644 --- a/pkg/cmdlets/git/git.go +++ b/pkg/cmdlets/git/git.go @@ -122,7 +122,7 @@ func EnsureCorrectProject(owner, name, projectFilePath, repoURL string, out outp if err != nil { return locale.WrapError(err, "err_git_update_mismatch", "Could not update projectfile namespace") } - an.Event(anaConsts.CatMisc, "git-project-mismatch") + an.Event(anaConsts.CatMisc, "git-project-mismatch", anaConsts.SrcStateTool) } return nil diff --git a/pkg/platform/api/svc/request/analytics.go b/pkg/platform/api/svc/request/analytics.go index 386319d12a..713ebd2b87 100644 --- a/pkg/platform/api/svc/request/analytics.go +++ b/pkg/platform/api/svc/request/analytics.go @@ -1,26 +1,28 @@ package request type AnalyticsEvent struct { - category string - action string - label string - dimensionsJson string - outputType string - userID string + category string + action string + source string + label string + dimensionsJson string + outputType string + userID string } -func NewAnalyticsEvent(category, action, label, dimensionsJson string) *AnalyticsEvent { +func NewAnalyticsEvent(category, action, source, label, dimensionsJson string) *AnalyticsEvent { return &AnalyticsEvent{ - category: category, - action: action, - label: label, - dimensionsJson: dimensionsJson, + category: category, + action: action, + source: source, + label: label, + dimensionsJson: dimensionsJson, } } func (e *AnalyticsEvent) Query() string { - return `query($category: String!, $action: String!, $label: String, $dimensionsJson: String!) { - analyticsEvent(category: $category, action: $action, label: $label, dimensionsJson: $dimensionsJson) { + return `query($category: String!, $action: String!, $source: String!, $label: String, $dimensionsJson: String!) { + analyticsEvent(category: $category, action: $action, source: $source, label: $label, dimensionsJson: $dimensionsJson) { sent } }` @@ -28,9 +30,10 @@ func (e *AnalyticsEvent) Query() string { func (e *AnalyticsEvent) Vars() map[string]interface{} { return map[string]interface{}{ - "category": e.category, - "action": e.action, - "label": e.label, + "category": e.category, + "action": e.action, + "source": e.source, + "label": e.label, "dimensionsJson": e.dimensionsJson, } } diff --git a/pkg/platform/model/svc.go b/pkg/platform/model/svc.go index b1de885514..4a0f3b2bfe 100644 --- a/pkg/platform/model/svc.go +++ b/pkg/platform/model/svc.go @@ -97,10 +97,10 @@ func (m *SvcModel) Ping() error { return err } -func (m *SvcModel) AnalyticsEvent(ctx context.Context, category, action, label string, dimJson string) error { +func (m *SvcModel) AnalyticsEvent(ctx context.Context, category, action, source, label string, dimJson string) error { defer profile.Measure("svc:analyticsEvent", time.Now()) - r := request.NewAnalyticsEvent(category, action, label, dimJson) + r := request.NewAnalyticsEvent(category, action, source, label, dimJson) u := graph.AnalyticsEventResponse{} if err := m.request(ctx, r, &u); err != nil { return errs.Wrap(err, "Error sending analytics event via state-svc") diff --git a/pkg/platform/runtime/runtime.go b/pkg/platform/runtime/runtime.go index b5eb63d17d..aedc114736 100644 --- a/pkg/platform/runtime/runtime.go +++ b/pkg/platform/runtime/runtime.go @@ -76,7 +76,7 @@ func New(target setup.Targeter, an analytics.Dispatcher, svcm *model.SvcModel) ( return &Runtime{disabled: true, target: target}, nil } recordAttempt(an, target) - an.Event(anaConsts.CatRuntime, anaConsts.ActRuntimeStart, &dimensions.Values{ + an.Event(anaConsts.CatRuntime, anaConsts.ActRuntimeStart, anaConsts.SrcStateTool, &dimensions.Values{ Trigger: ptr.To(target.Trigger().String()), Headless: ptr.To(strconv.FormatBool(target.Headless())), CommitID: ptr.To(target.CommitUUID().String()), @@ -86,7 +86,7 @@ func New(target setup.Targeter, an analytics.Dispatcher, svcm *model.SvcModel) ( r, err := newRuntime(target, an, svcm) if err == nil { - an.Event(anaConsts.CatRuntime, anaConsts.ActRuntimeCache, &dimensions.Values{ + an.Event(anaConsts.CatRuntime, anaConsts.ActRuntimeCache, anaConsts.SrcStateTool, &dimensions.Values{ CommitID: ptr.To(target.CommitUUID().String()), }) } @@ -225,7 +225,7 @@ func (r *Runtime) recordCompletion(err error) { message = errs.JoinMessage(err) } - r.analytics.Event(anaConsts.CatRuntime, action, &dimensions.Values{ + r.analytics.Event(anaConsts.CatRuntime, action, anaConsts.SrcStateTool, &dimensions.Values{ CommitID: ptr.To(r.target.CommitUUID().String()), // Note: ProjectID is set by state-svc since ProjectNameSpace is specified. ProjectNameSpace: ptr.To(ns.String()), @@ -257,7 +257,7 @@ func recordAttempt(an analytics.Dispatcher, target setup.Targeter) { return } - an.Event(anaConsts.CatRuntimeUsage, anaConsts.ActRuntimeAttempt, usageDims(target)) + an.Event(anaConsts.CatRuntimeUsage, anaConsts.ActRuntimeAttempt, anaConsts.SrcStateTool, usageDims(target)) } func usageDims(target setup.Targeter) *dimensions.Values { diff --git a/pkg/platform/runtime/setup/setup.go b/pkg/platform/runtime/setup/setup.go index 51049739a2..cca76c693f 100644 --- a/pkg/platform/runtime/setup/setup.go +++ b/pkg/platform/runtime/setup/setup.go @@ -433,7 +433,7 @@ func (s *Setup) fetchAndInstallArtifactsFromBuildPlan(installFunc artifactInstal // send analytics build event, if a new runtime has to be built in the cloud if buildResult.BuildStatus == bpModel.Started { - s.analytics.Event(anaConsts.CatRuntime, anaConsts.ActRuntimeBuild, dimensions) + s.analytics.Event(anaConsts.CatRuntime, anaConsts.ActRuntimeBuild, anaConsts.SrcStateTool, dimensions) } changedArtifacts, err := buildplan.NewBaseArtifactChangesetByBuildPlan(buildResult.Build, false) @@ -542,7 +542,7 @@ func (s *Setup) fetchAndInstallArtifactsFromBuildPlan(installFunc artifactInstal // only send the download analytics event, if we have to install artifacts that are not yet installed if len(artifactsToInstall) > 0 { // if we get here, we dowload artifacts - s.analytics.Event(anaConsts.CatRuntime, anaConsts.ActRuntimeDownload, dimensions) + s.analytics.Event(anaConsts.CatRuntime, anaConsts.ActRuntimeDownload, anaConsts.SrcStateTool, dimensions) } err = s.installArtifactsFromBuild(buildResult, runtimeArtifacts, artifact.ArtifactIDsToMap(artifactsToInstall), downloadablePrebuiltResults, setup, installFunc, logFilePath) diff --git a/test/integration/analytics_int_test.go b/test/integration/analytics_int_test.go index 8bd885f5c8..fc463805fd 100644 --- a/test/integration/analytics_int_test.go +++ b/test/integration/analytics_int_test.go @@ -179,7 +179,7 @@ func (suite *AnalyticsIntegrationTestSuite) TestActivateEvents() { func countEvents(events []reporters.TestLogEntry, category, action string) int { filteredEvents := funk.Filter(events, func(e reporters.TestLogEntry) bool { - return e.Category == category && e.Action == action + return e.Category == category && e.Action == action && e.Source == anaConst.SrcStateTool }).([]reporters.TestLogEntry) return len(filteredEvents) } diff --git a/test/integration/performance_svc_int_test.go b/test/integration/performance_svc_int_test.go index 71b3d18df4..1ca2a467ff 100644 --- a/test/integration/performance_svc_int_test.go +++ b/test/integration/performance_svc_int_test.go @@ -75,7 +75,7 @@ func (suite *PerformanceIntegrationTestSuite) TestSvcPerformance() { suite.Run("Query Analytics", func() { t := time.Now() - err := svcmodel.AnalyticsEvent(context.Background(), "performance-test", "performance-test", "performance-test", "{}") + err := svcmodel.AnalyticsEvent(context.Background(), "performance-test", "performance-test", "performance-test", "performance-test", "{}") suite.Require().NoError(err, ts.DebugMessage(fmt.Sprintf("Error: %s\nLog Tail:\n%s", errs.JoinMessage(err), logging.ReadTail()))) duration := time.Since(t)