Skip to content

Commit 21433d3

Browse files
authored
Merge pull request #1864 from forcedotcom/m2d/v5.3.0
Main2Dev @W-19015105@ Merging main to dev after v5.3.0
2 parents 3aa0039 + 3e3e558 commit 21433d3

File tree

5 files changed

+101
-37
lines changed

5 files changed

+101
-37
lines changed

package-lock.json

Lines changed: 10 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
{
22
"name": "@salesforce/plugin-code-analyzer",
33
"description": "Salesforce Code Analyzer is a unified tool to help Salesforce developers analyze their source code for security vulnerabilities, performance issues, best practices, and more.",
4-
"version": "5.2.2",
4+
"version": "5.3.0",
55
"author": "Salesforce Code Analyzer Team",
66
"bugs": "https://github.com/forcedotcom/code-analyzer/issues",
77
"dependencies": {
88
"@oclif/core": "3.27.0",
99
"@salesforce/code-analyzer-core": "0.31.0",
1010
"@salesforce/code-analyzer-engine-api": "0.26.0",
11-
"@salesforce/code-analyzer-eslint-engine": "0.27.0",
11+
"@salesforce/code-analyzer-eslint-engine": "0.28.0",
1212
"@salesforce/code-analyzer-flow-engine": "0.24.0",
1313
"@salesforce/code-analyzer-pmd-engine": "0.28.0",
1414
"@salesforce/code-analyzer-regex-engine": "0.24.0",

src/commands/code-analyzer/rules.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {Displayable, UxDisplay} from '../../lib/Display';
1010
import {LogEventDisplayer} from '../../lib/listeners/LogEventListener';
1111
import {RuleSelectionProgressSpinner} from '../../lib/listeners/ProgressEventListener';
1212
import {CompositeRulesWriter} from '../../lib/writers/RulesWriter';
13+
import { SfCliTelemetryEmitter } from '../../lib/Telemetry';
1314

1415
export default class RulesCommand extends SfCommand<void> implements Displayable {
1516
// We don't need the `--json` output for this command.
@@ -86,6 +87,7 @@ export default class RulesCommand extends SfCommand<void> implements Displayable
8687
pluginsFactory: new EnginePluginsFactoryImpl(),
8788
logEventListeners: [new LogEventDisplayer(uxDisplay)],
8889
progressListeners: [new RuleSelectionProgressSpinner(uxDisplay)],
90+
telemetryEmitter: new SfCliTelemetryEmitter(),
8991
actionSummaryViewer: new RulesActionSummaryViewer(uxDisplay),
9092
viewer: this.createRulesViewer(view, outputFiles, uxDisplay),
9193
writer: CompositeRulesWriter.fromFiles(outputFiles)

src/lib/actions/RulesAction.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,16 @@ import { RulesActionSummaryViewer } from '../viewers/ActionSummaryViewer';
88
import { RuleViewer } from '../viewers/RuleViewer';
99
import { LogFileWriter } from '../writers/LogWriter';
1010
import { RulesWriter } from '../writers/RulesWriter';
11+
import { TelemetryEmitter } from '../Telemetry';
12+
import { TelemetryEventListener } from '../listeners/TelemetryEventListener';
13+
import * as Constants from '../../Constants';
1114

1215
export type RulesDependencies = {
1316
configFactory: CodeAnalyzerConfigFactory;
1417
pluginsFactory: EnginePluginsFactory;
1518
logEventListeners: LogEventListener[];
1619
progressListeners: ProgressEventListener[];
20+
telemetryEmitter: TelemetryEmitter;
1721
actionSummaryViewer: RulesActionSummaryViewer,
1822
viewer: RuleViewer;
1923
writer: RulesWriter;
@@ -45,6 +49,8 @@ export class RulesAction {
4549
// LogEventListeners should start listening as soon as the Core is instantiated, since Core can start emitting
4650
// events they listen for basically immediately.
4751
this.dependencies.logEventListeners.forEach(listener => listener.listen(core));
52+
const telemetryListener: TelemetryEventListener = new TelemetryEventListener(this.dependencies.telemetryEmitter);
53+
telemetryListener.listen(core);
4854
const enginePlugins = this.dependencies.pluginsFactory.create();
4955
const enginePluginModules = config.getCustomEnginePluginModules();
5056
const addEnginePromises: Promise<void>[] = [
@@ -60,10 +66,12 @@ export class RulesAction {
6066
// that's when progress events can start being emitted.
6167
this.dependencies.progressListeners.forEach(listener => listener.listen(core));
6268
const ruleSelection: RuleSelection = await core.selectRules(input["rule-selector"], selectOptions);
69+
this.emitEngineTelemetry(ruleSelection, enginePlugins.flatMap(p => p.getAvailableEngineNames()));
6370
// After Core is done running, the listeners need to be told to stop, since some of them have persistent UI elements
6471
// or file handlers that must be gracefully ended.
6572
this.dependencies.progressListeners.forEach(listener => listener.stopListening());
6673
this.dependencies.logEventListeners.forEach(listener => listener.stopListening());
74+
telemetryListener.stopListening();
6775
const rules: Rule[] = core.getEngineNames().flatMap(name => ruleSelection.getRulesFor(name));
6876

6977
this.dependencies.writer.write(ruleSelection)
@@ -79,4 +87,18 @@ export class RulesAction {
7987
public static createAction(dependencies: RulesDependencies): RulesAction {
8088
return new RulesAction(dependencies);
8189
}
90+
91+
private emitEngineTelemetry(ruleSelection: RuleSelection, coreEngineNames: string[]): void {
92+
const selectedEngineNames: Set<string> = new Set(ruleSelection.getEngineNames());
93+
for (const coreEngineName of coreEngineNames) {
94+
if (!selectedEngineNames.has(coreEngineName)) {
95+
continue;
96+
}
97+
this.dependencies.telemetryEmitter.emitTelemetry(Constants.TelemetrySource, Constants.TelemetryEventName, {
98+
sfcaEvent: Constants.CliTelemetryEvents.ENGINE_SELECTION,
99+
engine: coreEngineName,
100+
ruleCount: ruleSelection.getRulesFor(coreEngineName).length
101+
});
102+
}
103+
}
82104
}

test/lib/actions/RulesAction.test.ts

Lines changed: 65 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@ import { SpyRuleViewer } from '../../stubs/SpyRuleViewer';
88
import { SpyRuleWriter } from '../../stubs/SpyRuleWriter';
99
import { StubDefaultConfigFactory } from '../../stubs/StubCodeAnalyzerConfigFactories';
1010
import * as StubEnginePluginFactories from '../../stubs/StubEnginePluginsFactories';
11+
import { CapturedTelemetryEmission, SpyTelemetryEmitter } from '../../stubs/SpyTelemetryEmitter';
1112

1213
const PATH_TO_GOLDFILES = path.join(__dirname, '..', '..', 'fixtures', 'comparison-files', 'lib', 'actions', 'RulesAction.test.ts');
1314

1415
describe('RulesAction tests', () => {
1516
let viewer: SpyRuleViewer;
1617
let writer: SpyRuleWriter;
1718
let spyDisplay: SpyDisplay;
19+
let spyTelemetryEmitter: SpyTelemetryEmitter;
1820
let actionSummaryViewer: RulesActionSummaryViewer;
1921
let defaultDependencies: RulesDependencies;
2022

@@ -23,11 +25,13 @@ describe('RulesAction tests', () => {
2325
writer = new SpyRuleWriter();
2426
spyDisplay = new SpyDisplay();
2527
actionSummaryViewer = new RulesActionSummaryViewer(spyDisplay);
28+
spyTelemetryEmitter = new SpyTelemetryEmitter();
2629
defaultDependencies = {
2730
configFactory: new StubDefaultConfigFactory(),
2831
pluginsFactory: new StubEnginePluginFactories.StubEnginePluginsFactory_withFunctionalStubEngine(),
2932
logEventListeners: [],
3033
progressListeners: [],
34+
telemetryEmitter: spyTelemetryEmitter,
3135
actionSummaryViewer,
3236
viewer,
3337
writer
@@ -123,14 +127,8 @@ describe('RulesAction tests', () => {
123127
}
124128
])('When $case, only the relevant rules are returned', async ({workspace, target}) => {
125129
const dependencies: RulesDependencies = {
126-
configFactory: new StubDefaultConfigFactory(),
127-
// The engine we're using here will synthesize one rule per target.
130+
...defaultDependencies,
128131
pluginsFactory: new StubEnginePluginFactories.StubEnginePluginsFactory_withTargetDependentStubEngine(),
129-
logEventListeners: [],
130-
progressListeners: [],
131-
actionSummaryViewer,
132-
viewer,
133-
writer
134132
};
135133
const action = RulesAction.createAction(dependencies);
136134
const input: RulesInput = {
@@ -161,13 +159,8 @@ describe('RulesAction tests', () => {
161159
*/
162160
it('When no engines are registered, empty results are displayed', async () => {
163161
const dependencies: RulesDependencies = {
164-
configFactory: new StubDefaultConfigFactory(),
162+
...defaultDependencies,
165163
pluginsFactory: new StubEnginePluginFactories.StubEnginePluginsFactory_withNoPlugins(),
166-
logEventListeners: [],
167-
progressListeners: [],
168-
actionSummaryViewer,
169-
viewer,
170-
writer
171164
};
172165
const action = RulesAction.createAction(dependencies);
173166
const input: RulesInput = {
@@ -183,13 +176,8 @@ describe('RulesAction tests', () => {
183176

184177
it('Throws an error when an engine throws an error', async () => {
185178
const dependencies: RulesDependencies = {
186-
configFactory: new StubDefaultConfigFactory(),
179+
...defaultDependencies,
187180
pluginsFactory: new StubEnginePluginFactories.StubEnginePluginsFactory_withThrowingStubPlugin(),
188-
logEventListeners: [],
189-
progressListeners: [],
190-
actionSummaryViewer,
191-
viewer,
192-
writer
193181
};
194182
const action = RulesAction.createAction(dependencies);
195183
const input: RulesInput = {
@@ -214,13 +202,9 @@ describe('RulesAction tests', () => {
214202
])('When $quantifier rules are returned, $expectation', async ({selector, goldfile}) => {
215203
const goldfilePath: string = path.join(PATH_TO_GOLDFILES, 'action-summaries', goldfile);
216204
const dependencies: RulesDependencies = {
217-
configFactory: new StubDefaultConfigFactory(),
205+
...defaultDependencies,
218206
pluginsFactory: new StubEnginePluginFactories.StubEnginePluginsFactory_withFunctionalStubEngine(),
219-
logEventListeners: [],
220-
progressListeners: [],
221-
actionSummaryViewer,
222-
viewer,
223-
writer
207+
viewer
224208
};
225209
const action = RulesAction.createAction(dependencies);
226210
const input: RulesInput = {
@@ -266,6 +250,62 @@ describe('RulesAction tests', () => {
266250
expect(displayedLogEvents).toContain(goldfileContents);
267251
});
268252
});
253+
254+
describe('Telemetry Emission', () => {
255+
it('When a telemetry emitter is provided, it is used', async () => {
256+
257+
defaultDependencies.pluginsFactory = new StubEnginePluginFactories.StubEnginePluginsFactory_withFunctionalStubEngine();
258+
const action = RulesAction.createAction(defaultDependencies);
259+
// Create the input.
260+
const input: RulesInput = {
261+
'rule-selector': ['all']
262+
};
263+
// ==== TESTED BEHAVIOR ====
264+
await action.execute(input);
265+
266+
// ==== ASSERTIONS ====
267+
const allTelemEvents: CapturedTelemetryEmission[] = spyTelemetryEmitter.getCapturedTelemetry();
268+
const ruleSelectionTelemEvents: CapturedTelemetryEmission[] = allTelemEvents.filter(
269+
e => e.data.sfcaEvent === 'engine_selection');
270+
271+
expect(ruleSelectionTelemEvents).toHaveLength(2);
272+
expect(ruleSelectionTelemEvents[0]).toEqual({
273+
"data": {
274+
"engine": "stubEngine1",
275+
"ruleCount": 5,
276+
"sfcaEvent": "engine_selection"
277+
},
278+
"eventName": "plugin-code-analyzer",
279+
"source": "CLI" // NOTE: We might move these events to Core in the future instead of the CLI
280+
});
281+
expect(ruleSelectionTelemEvents[1]).toEqual({
282+
"data": {
283+
"engine": "stubEngine2",
284+
"ruleCount": 3,
285+
"sfcaEvent": "engine_selection"
286+
},
287+
"eventName": "plugin-code-analyzer",
288+
"source": "CLI" // NOTE: We might move these events to Core in the future instead of the CLI
289+
});
290+
291+
const engineSpecificTelemEvents: CapturedTelemetryEmission[] = allTelemEvents.filter(
292+
e => e.data.sfcaEvent === 'engine1DescribeTelemetry');
293+
expect(engineSpecificTelemEvents).toHaveLength(1);
294+
const customEvent: CapturedTelemetryEmission = engineSpecificTelemEvents[0];
295+
customEvent.data["timestamp"] = 0; // fix for deterministic testing
296+
customEvent.data["uuid"] = "someUUID"; // fix for deterministic testing
297+
expect(customEvent).toEqual({
298+
"data": {
299+
"someArg": true, // argument set by engine
300+
"sfcaEvent": "engine1DescribeTelemetry",
301+
"timestamp": 0,
302+
"uuid": "someUUID"
303+
},
304+
"eventName": "plugin-code-analyzer",
305+
"source": "stubEngine1"
306+
});
307+
});
308+
})
269309
});
270310

271311
// TODO: Whenever we decide to document the custom_engine_plugin_modules flag in our configuration file, then we'll want

0 commit comments

Comments
 (0)