diff --git a/src/Constants.ts b/src/Constants.ts index 576fab3ca..da75f4ead 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -11,3 +11,9 @@ export const CliTelemetryEvents = { ENGINE_SELECTION: 'engine_selection', ENGINE_EXECUTION: 'engine_execution' } + +export const CliCommands = { + RUN: 'run', + RULES: 'rules', + CONFIG: 'config' +} diff --git a/src/commands/code-analyzer/config.ts b/src/commands/code-analyzer/config.ts index d2bf01865..0a4c3d788 100644 --- a/src/commands/code-analyzer/config.ts +++ b/src/commands/code-analyzer/config.ts @@ -9,6 +9,7 @@ import {BundleName, getMessage, getMessages} from '../../lib/messages.js'; import {LogEventDisplayer} from '../../lib/listeners/LogEventListener.js'; import {RuleSelectionProgressSpinner} from '../../lib/listeners/ProgressEventListener.js'; import {Displayable, UxDisplay} from '../../lib/Display.js'; +import {SfCliTelemetryEmitter} from '../../lib/Telemetry.js'; export default class ConfigCommand extends SfCommand implements Displayable { // We don't need the `--json` output for this command. @@ -76,6 +77,7 @@ export default class ConfigCommand extends SfCommand implements Displayabl const dependencies: ConfigDependencies = { configFactory: new CodeAnalyzerConfigFactoryImpl(), pluginsFactory: new EnginePluginsFactoryImpl(), + telemetryEmitter: new SfCliTelemetryEmitter(), logEventListeners: [new LogEventDisplayer(uxDisplay)], progressEventListeners: [new RuleSelectionProgressSpinner(uxDisplay)], actionSummaryViewer: new ConfigActionSummaryViewer(uxDisplay), diff --git a/src/lib/actions/ConfigAction.ts b/src/lib/actions/ConfigAction.ts index afd5e95f9..72298d485 100644 --- a/src/lib/actions/ConfigAction.ts +++ b/src/lib/actions/ConfigAction.ts @@ -10,12 +10,16 @@ import {LogEventListener, LogEventLogger} from '../listeners/LogEventListener.js import {ProgressEventListener} from '../listeners/ProgressEventListener.js'; import {ConfigActionSummaryViewer} from '../viewers/ActionSummaryViewer.js'; import {AnnotatedConfigModel, ConfigModel} from '../models/ConfigModel.js'; +import {TelemetryEmitter} from '../Telemetry.js'; +import {TelemetryEventListener} from '../listeners/TelemetryEventListener.js'; +import * as Constants from '../../Constants.js'; export type ConfigDependencies = { configFactory: CodeAnalyzerConfigFactory; pluginsFactory: EnginePluginsFactory; logEventListeners: LogEventListener[]; progressEventListeners: ProgressEventListener[]; + telemetryEmitter: TelemetryEmitter; writer?: ConfigWriter; actionSummaryViewer: ConfigActionSummaryViewer; viewer: ConfigViewer; @@ -54,6 +58,8 @@ export class ConfigAction { // LogEventListeners should start listening as soon as the User Core is instantiated, since it can start emitting // relevant events basically immediately. this.dependencies.logEventListeners.forEach(listener => listener.listen(userCore)); + const telemetryListener: TelemetryEventListener = new TelemetryEventListener(this.dependencies.telemetryEmitter); + telemetryListener.listen(userCore); const enginePlugins: EnginePlugin[] = this.dependencies.pluginsFactory.create(); const enginePluginModules: string[] = userConfig.getCustomEnginePluginModules(); @@ -128,7 +134,10 @@ export class ConfigAction { // elements or file handlers that must be gracefully ended. this.dependencies.progressEventListeners.forEach(listener => listener.stopListening()); this.dependencies.logEventListeners.forEach(listener => listener.stopListening()); + telemetryListener.stopListening(); + // ==== EMIT TELEMETRY ========================================================================================== + this.emitEngineTelemetry(enginePlugins, userCore, userRules); // ==== CREATE AND WRITE CONFIG YAML =========================================================================== @@ -154,6 +163,23 @@ export class ConfigAction { return Promise.resolve(); } + private emitEngineTelemetry(enginePlugins: EnginePlugin[], core: CodeAnalyzer, ruleSelection: {getEngineNames(): string[], getRulesFor(engine: string): unknown[]}): void { + const coreEngineNames: string[] = enginePlugins.flatMap(enginePlugin => enginePlugin.getAvailableEngineNames()); + const selectedEngineNames: Set = new Set(ruleSelection.getEngineNames()); + + for (const coreEngineName of coreEngineNames) { + if (!selectedEngineNames.has(coreEngineName)) { + continue; + } + this.dependencies.telemetryEmitter.emitTelemetry(Constants.TelemetrySource, Constants.TelemetryEventName, { + sfcaEvent: Constants.CliTelemetryEvents.ENGINE_SELECTION, + command: Constants.CliCommands.CONFIG, + engine: coreEngineName, + ruleCount: ruleSelection.getRulesFor(coreEngineName).length + }); + } + } + public static createAction(dependencies: ConfigDependencies): ConfigAction { return new ConfigAction(dependencies) } diff --git a/src/lib/actions/RulesAction.ts b/src/lib/actions/RulesAction.ts index 5a0a3c8e0..417e579a8 100644 --- a/src/lib/actions/RulesAction.ts +++ b/src/lib/actions/RulesAction.ts @@ -96,6 +96,7 @@ export class RulesAction { } this.dependencies.telemetryEmitter.emitTelemetry(Constants.TelemetrySource, Constants.TelemetryEventName, { sfcaEvent: Constants.CliTelemetryEvents.ENGINE_SELECTION, + command: Constants.CliCommands.RULES, engine: coreEngineName, ruleCount: ruleSelection.getRulesFor(coreEngineName).length }); diff --git a/src/lib/actions/RunAction.ts b/src/lib/actions/RunAction.ts index 5e2cd90e2..6b5e89abb 100644 --- a/src/lib/actions/RunAction.ts +++ b/src/lib/actions/RunAction.ts @@ -117,12 +117,14 @@ export class RunAction { } this.dependencies.telemetryEmitter.emitTelemetry(Constants.TelemetrySource, Constants.TelemetryEventName, { sfcaEvent: Constants.CliTelemetryEvents.ENGINE_SELECTION, + command: Constants.CliCommands.RUN, engine: coreEngineName, ruleCount: ruleSelection.getRulesFor(coreEngineName).length }); this.dependencies.telemetryEmitter.emitTelemetry(Constants.TelemetrySource, Constants.TelemetryEventName, { sfcaEvent: Constants.CliTelemetryEvents.ENGINE_EXECUTION, + command: Constants.CliCommands.RUN, engine: coreEngineName, violationCount: results.getEngineRunResults(coreEngineName).getViolationCount() }); diff --git a/test/lib/actions/ConfigAction.test.ts b/test/lib/actions/ConfigAction.test.ts index 03bac59e2..47b929bfa 100644 --- a/test/lib/actions/ConfigAction.test.ts +++ b/test/lib/actions/ConfigAction.test.ts @@ -15,6 +15,7 @@ import {SpyConfigWriter} from '../../stubs/SpyConfigWriter.js'; import {SpyConfigViewer} from '../../stubs/SpyConfigViewer.js'; import {DisplayEvent, DisplayEventType, SpyDisplay} from '../../stubs/SpyDisplay.js'; import {LogEventDisplayer} from '../../../src/lib/listeners/LogEventListener.js'; +import {SpyTelemetryEmitter} from '../../stubs/SpyTelemetryEmitter.js'; const PATH_TO_FIXTURES = path.join(import.meta.dirname, '..', '..', 'fixtures'); @@ -38,7 +39,8 @@ describe('ConfigAction tests', () => { viewer: new ConfigStyledYamlViewer(spyDisplay), configFactory: new StubCodeAnalyzerConfigFactory(), actionSummaryViewer: new ConfigActionSummaryViewer(spyDisplay), - pluginsFactory: new StubEnginePluginFactory() + pluginsFactory: new StubEnginePluginFactory(), + telemetryEmitter: new SpyTelemetryEmitter() }; }); @@ -177,7 +179,8 @@ describe('ConfigAction tests', () => { viewer: new ConfigStyledYamlViewer(spyDisplay), configFactory: stubConfigFactory, actionSummaryViewer: new ConfigActionSummaryViewer(spyDisplay), - pluginsFactory: new StubEnginePluginFactory() + pluginsFactory: new StubEnginePluginFactory(), + telemetryEmitter: new SpyTelemetryEmitter() }; }); @@ -447,7 +450,8 @@ describe('ConfigAction tests', () => { viewer: new ConfigStyledYamlViewer(spyDisplay), configFactory: configFactory, actionSummaryViewer: new ConfigActionSummaryViewer(spyDisplay), - pluginsFactory: new StubEnginePluginFactory() + pluginsFactory: new StubEnginePluginFactory(), + telemetryEmitter: new SpyTelemetryEmitter() }; }); @@ -531,7 +535,8 @@ describe('ConfigAction tests', () => { viewer: new ConfigStyledYamlViewer(spyDisplay), configFactory: new StubCodeAnalyzerConfigFactory(), actionSummaryViewer: new ConfigActionSummaryViewer(spyDisplay), - pluginsFactory: new StubEnginePluginFactory() + pluginsFactory: new StubEnginePluginFactory(), + telemetryEmitter: new SpyTelemetryEmitter() }; }); @@ -687,7 +692,8 @@ describe('ConfigAction tests', () => { viewer: new ConfigStyledYamlViewer(spyDisplay), configFactory: new StubCodeAnalyzerConfigFactory(), actionSummaryViewer: new ConfigActionSummaryViewer(spyDisplay), - pluginsFactory: new WorkspaceAwareEnginePluginFactory() + pluginsFactory: new WorkspaceAwareEnginePluginFactory(), + telemetryEmitter: new SpyTelemetryEmitter() }; // ==== TESTED BEHAVIOR ==== @@ -708,7 +714,8 @@ describe('ConfigAction tests', () => { viewer: new ConfigStyledYamlViewer(spyDisplay), configFactory: new StubCodeAnalyzerConfigFactory(), actionSummaryViewer: new ConfigActionSummaryViewer(spyDisplay), - pluginsFactory: new StubEnginePluginFactory() + pluginsFactory: new StubEnginePluginFactory(), + telemetryEmitter: new SpyTelemetryEmitter() }; }); @@ -756,7 +763,8 @@ describe('ConfigAction tests', () => { viewer: new ConfigStyledYamlViewer(spyDisplay), configFactory: new StubCodeAnalyzerConfigFactory(), actionSummaryViewer: new ConfigActionSummaryViewer(spyDisplay), - pluginsFactory: new StubEnginePluginFactory() + pluginsFactory: new StubEnginePluginFactory(), + telemetryEmitter: new SpyTelemetryEmitter() } }); diff --git a/test/lib/actions/RulesAction.test.ts b/test/lib/actions/RulesAction.test.ts index 1d4021c2b..5714fccf2 100644 --- a/test/lib/actions/RulesAction.test.ts +++ b/test/lib/actions/RulesAction.test.ts @@ -271,18 +271,20 @@ describe('RulesAction tests', () => { expect(ruleSelectionTelemEvents).toHaveLength(2); expect(ruleSelectionTelemEvents[0]).toEqual({ "data": { + "sfcaEvent": "engine_selection", + "command": "rules", "engine": "stubEngine1", - "ruleCount": 5, - "sfcaEvent": "engine_selection" + "ruleCount": 5 }, "eventName": "plugin-code-analyzer", "source": "CLI" // NOTE: We might move these events to Core in the future instead of the CLI }); expect(ruleSelectionTelemEvents[1]).toEqual({ "data": { + "sfcaEvent": "engine_selection", + "command": "rules", "engine": "stubEngine2", - "ruleCount": 3, - "sfcaEvent": "engine_selection" + "ruleCount": 3 }, "eventName": "plugin-code-analyzer", "source": "CLI" // NOTE: We might move these events to Core in the future instead of the CLI diff --git a/test/lib/actions/RunAction.test.ts b/test/lib/actions/RunAction.test.ts index 09a5d8de1..644a19c8e 100644 --- a/test/lib/actions/RunAction.test.ts +++ b/test/lib/actions/RunAction.test.ts @@ -399,11 +399,13 @@ describe('RunAction tests', () => { expect(spyTelemetryEmitter.getCapturedTelemetry()[2].eventName).toEqual('plugin-code-analyzer'); expect(spyTelemetryEmitter.getCapturedTelemetry()[2].source).toEqual('CLI'); expect(spyTelemetryEmitter.getCapturedTelemetry()[2].data.sfcaEvent).toEqual('engine_selection'); + expect(spyTelemetryEmitter.getCapturedTelemetry()[2].data.command).toEqual('run'); expect(spyTelemetryEmitter.getCapturedTelemetry()[2].data.ruleCount).toEqual(5); expect(spyTelemetryEmitter.getCapturedTelemetry()[3].eventName).toEqual('plugin-code-analyzer'); expect(spyTelemetryEmitter.getCapturedTelemetry()[3].source).toEqual('CLI'); expect(spyTelemetryEmitter.getCapturedTelemetry()[3].data.sfcaEvent).toEqual('engine_execution'); + expect(spyTelemetryEmitter.getCapturedTelemetry()[3].data.command).toEqual('run'); expect(spyTelemetryEmitter.getCapturedTelemetry()[3].data.violationCount).toEqual(0); }); })