Skip to content

Conversation

@jfeingold35
Copy link
Contributor

No description provided.


private emitEngineExecutionTelemetry(ruleSelection: RuleSelection, results: RunResults, coreEngineNames: string[]): Promise<void> {
const selectedEngineNames: Set<string> = new Set(ruleSelection.getEngineNames());
const executedEngineNames: Set<string> = new Set(results.getEngineNames());
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turns out that some of the tests started failing when I assumed that the ruleSelection and results would have the same .getEngineNames() results, so I added the separate boolean for execution.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

? How could this ever be the case? Are you sure the tests are not incorrect?
Is it because the results.getEngineNames return the uninstantiated engines as well but the ruleSelection doesn't?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not entirely sure. I'll look further into it.

pluginsFactory: EnginePluginsFactory;
logEventListeners: LogEventListener[];
progressListeners: ProgressEventListener[];
telemetryEmitter?: TelemetryEmitter;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I deviated from the pattern I've been using with the Listeners.

// LogEventListeners should start listening as soon as the Core is instantiated, since Core can start emitting
// events they listen for basically immediately.
this.dependencies.logEventListeners.forEach(listener => listener.listen(core));
const telemetryListener: TelemetryEventListener = new TelemetryEventListener(this.dependencies.telemetryEmitter ?? new NoOpTelemetryEmitter());
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We only instantiate one listener, and we'll use either the one we were given or a NoOp one. This should help avoid some messy nullchecks.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this line. When would telemetryEmitter ever be null?

return Promise.resolve();
});
receivedTelemetryEmissions = [];
jest.spyOn(SfCliTelemetryEmitter.prototype, 'emitTelemetry').mockImplementation((source, eventName, data) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this isn't the preferred way of doing this sort of test, but it was the best option available.

@jfeingold35 jfeingold35 changed the title @W-17915999@ Added telemetry collection for run action NEW @W-17915999@ Added telemetry collection for run action Apr 23, 2025
@jfeingold35 jfeingold35 marked this pull request as ready for review April 23, 2025 20:59
pluginsFactory: EnginePluginsFactory;
logEventListeners: LogEventListener[];
progressListeners: ProgressEventListener[];
telemetryEmitter?: TelemetryEmitter;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When would you ever have this undefined?

// LogEventListeners should start listening as soon as the Core is instantiated, since Core can start emitting
// events they listen for basically immediately.
this.dependencies.logEventListeners.forEach(listener => listener.listen(core));
const telemetryListener: TelemetryEventListener = new TelemetryEventListener(this.dependencies.telemetryEmitter ?? new NoOpTelemetryEmitter());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this line. When would telemetryEmitter ever be null?

Comment on lines 107 to 108
engineTelemetryObject[`${coreEngineName}_selected`] = selected;
engineTelemetryObject[`${coreEngineName}_executed`] = executed;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not understand this.

Is it that the results.getEngineNames is returning the uninstantiated engines as well?

Comment on lines +106 to +108
if (!selectedEngineNames.has(coreEngineName)) {
continue;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you really need to pass in coreEngineNames. Let's just loop over selectedEngineNames. Then back on line 81... we don't need to get the engine names from the plugins.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have to use coreEngineNames in order to limit ourselves to the plugins that we ourselves ship. Otherwise, we're collecting telemetry on possibly-user-created engines, which is apparently a no-no.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh you are saying that enginePlugins which you get from the this.dependencies.pluginsFactory.create(); doesn't include the dynamic engines. OK makes sense.

Comment on lines 121 to 128
const telemetryPromises: Promise<void>[] = [];
for (const selectionTelemetry of selectionTelemetryEvents) {
telemetryPromises.push(this.dependencies.telemetryEmitter.emitTelemetry('RunAction', Constants.TelemetryEventName, selectionTelemetry));
}
for (const executionTelemetry of executionTelemetryEvents) {
telemetryPromises.push(this.dependencies.telemetryEmitter.emitTelemetry('RunAction', Constants.TelemetryEventName, executionTelemetry));
}
return telemetryPromises;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you go over into lib/Telemetry.ts
and make

export interface TelemetryEmitter {
	emitTelemetry(source: string, eventName: string, data: TelemetryData): Promise<void>;
}

export class SfCliTelemetryEmitter implements TelemetryEmitter {
	// istanbul ignore next - No sense in covering SF-CLI core code.
	public emitTelemetry(source: string, eventName: string, data: TelemetryData): Promise<void> {
		return Lifecycle.getInstance().emitTelemetry({
			...data,
			source,
			eventName
		});
	}
}

instead be the following (since we won't be awaiting these anyway):

export interface TelemetryEmitter {
	emitTelemetry(source: string, eventName: string, data: TelemetryData): void;
}

export class SfCliTelemetryEmitter implements TelemetryEmitter {
	// istanbul ignore next - No sense in covering SF-CLI core code.
	public emitTelemetry(source: string, eventName: string, data: TelemetryData): void {
		void Lifecycle.getInstance().emitTelemetry({
			...data,
			source,
			eventName
		});
	}
}

then you won't need to create on line 103 and 104 these arrays, to then fill them, to then create promise arrays, to then push them, etc.

Instead you should be able to just immediately call the emitTelemetry('RunAction', ...) on lines 109 and 115.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's reasonable.

@jfeingold35 jfeingold35 merged commit 5618e09 into dev Apr 24, 2025
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants