Skip to content

Commit 3bfd385

Browse files
authored
add quick fix telemetry (microsoft#163323)
1 parent 75cdec2 commit 3bfd385

File tree

3 files changed

+75
-11
lines changed

3 files changed

+75
-11
lines changed

src/vs/workbench/contrib/terminal/browser/terminal.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -954,6 +954,7 @@ export interface ITerminalInstance {
954954
}
955955

956956
export interface ITerminalQuickFixOptions {
957+
id: string;
957958
commandLineMatcher: string | RegExp;
958959
outputMatcher?: ITerminalOutputMatcher;
959960
getQuickFixes: TerminalQuickFixCallback;

src/vs/workbench/contrib/terminal/browser/terminalQuickFixBuiltinActions.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export const GitCreatePrOutputRegex = /remote:\s*(https:\/\/github\.com\/.+\/.+\
2121

2222
export function gitSimilarCommand(): ITerminalQuickFixOptions {
2323
return {
24+
id: 'Git Similar',
2425
commandLineMatcher: GitCommandLineRegex,
2526
outputMatcher: {
2627
lineMatcher: GitSimilarOutputRegex,
@@ -51,6 +52,7 @@ export function gitSimilarCommand(): ITerminalQuickFixOptions {
5152
}
5253
export function gitTwoDashes(): ITerminalQuickFixOptions {
5354
return {
55+
id: 'Git Two Dashes',
5456
commandLineMatcher: GitCommandLineRegex,
5557
outputMatcher: {
5658
lineMatcher: GitTwoDashesRegex,
@@ -74,6 +76,7 @@ export function gitTwoDashes(): ITerminalQuickFixOptions {
7476
}
7577
export function freePort(terminalInstance?: Partial<ITerminalInstance>): ITerminalQuickFixOptions {
7678
return {
79+
id: 'Free Port',
7780
commandLineMatcher: AnyCommandLineRegex,
7881
outputMatcher: {
7982
lineMatcher: FreePortOutputRegex,
@@ -101,6 +104,7 @@ export function freePort(terminalInstance?: Partial<ITerminalInstance>): ITermin
101104
}
102105
export function gitPushSetUpstream(): ITerminalQuickFixOptions {
103106
return {
107+
id: 'Git Push Set Upstream',
104108
commandLineMatcher: GitPushCommandLineRegex,
105109
outputMatcher: {
106110
lineMatcher: GitPushOutputRegex,
@@ -125,6 +129,7 @@ export function gitPushSetUpstream(): ITerminalQuickFixOptions {
125129

126130
export function gitCreatePr(): ITerminalQuickFixOptions {
127131
return {
132+
id: 'Git Create Pr',
128133
commandLineMatcher: GitPushCommandLineRegex,
129134
outputMatcher: {
130135
lineMatcher: GitCreatePrOutputRegex,

src/vs/workbench/contrib/terminal/browser/xterm/quickFixAddon.ts

Lines changed: 69 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,21 @@ import { IDecoration, Terminal } from 'xterm';
2626
// Importing types is safe in any layer
2727
// eslint-disable-next-line local/code-import-patterns
2828
import type { ITerminalAddon } from 'xterm-headless';
29-
30-
29+
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
30+
import { ILogService } from 'vs/platform/log/common/log';
31+
const quickFixTelemetryTitle = 'terminal/quick-fix';
32+
type QuickFixResultTelemetryEvent = {
33+
id: string;
34+
fixesShown: boolean;
35+
ranQuickFixCommand?: boolean;
36+
};
37+
type QuickFixClassification = {
38+
owner: 'meganrogge';
39+
id: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The quick fix ID' };
40+
fixesShown: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the fixes were shown by the user' };
41+
ranQuickFixCommand?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'If the command that was executed matched a quick fix suggested one. Undefined if no command is expected.' };
42+
comment: 'Terminal quick fixes';
43+
};
3144
const quickFixSelectors = [DecorationSelector.QuickFix, DecorationSelector.LightBulb, DecorationSelector.Codicon, DecorationSelector.CommandDecoration, DecorationSelector.XtermDecoration];
3245
export interface ITerminalQuickFix {
3346
showMenu(): void;
@@ -56,13 +69,18 @@ export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon,
5669

5770
private readonly _terminalDecorationHoverService: TerminalDecorationHoverManager;
5871

72+
private _fixesShown: boolean = false;
73+
private _expectedCommands: string[] | undefined;
74+
5975
constructor(private readonly _capabilities: ITerminalCapabilityStore,
6076
@IContextMenuService private readonly _contextMenuService: IContextMenuService,
6177
@IConfigurationService private readonly _configurationService: IConfigurationService,
6278
@IInstantiationService instantiationService: IInstantiationService,
6379
@IAudioCueService private readonly _audioCueService: IAudioCueService,
6480
@IKeybindingService private readonly _keybindingService: IKeybindingService,
65-
@IOpenerService private readonly _openerService: IOpenerService
81+
@IOpenerService private readonly _openerService: IOpenerService,
82+
@ITelemetryService private readonly _telemetryService: ITelemetryService,
83+
@ILogService private readonly _logService: ILogService
6684
) {
6785
super();
6886
const commandDetectionCapability = this._capabilities.get(TerminalCapability.CommandDetection);
@@ -83,6 +101,7 @@ export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon,
83101
}
84102

85103
showMenu(): void {
104+
this._fixesShown = true;
86105
this._decoration?.element?.click();
87106
}
88107

@@ -99,7 +118,25 @@ export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon,
99118
if (!terminal || !commandDetection) {
100119
return;
101120
}
102-
this._register(commandDetection.onCommandFinished(command => this._resolveQuickFixes(command)));
121+
this._register(commandDetection.onCommandFinished(command => {
122+
if (this._expectedCommands) {
123+
const ranQuickFixCommand = this._expectedCommands.includes(command.command);
124+
this._logService.debug(quickFixTelemetryTitle, {
125+
id: this._expectedCommands.join(' '),
126+
fixesShown: this._fixesShown,
127+
ranQuickFixCommand
128+
});
129+
this._telemetryService?.publicLog2<QuickFixResultTelemetryEvent, QuickFixClassification>(quickFixTelemetryTitle, {
130+
id: this._expectedCommands.join(' '),
131+
fixesShown: this._fixesShown,
132+
ranQuickFixCommand
133+
});
134+
this._expectedCommands = undefined;
135+
}
136+
this._resolveQuickFixes(command);
137+
this._fixesShown = false;
138+
}));
139+
103140
// The buffer is not ready by the time command finish
104141
// is called. Add the decoration on command start if there are corresponding quick fixes
105142
this._register(commandDetection.onCommandStarted(() => {
@@ -120,9 +157,24 @@ export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon,
120157
if (!result) {
121158
return;
122159
}
123-
const { fixes, onDidRunQuickFix } = result;
160+
const { fixes, onDidRunQuickFix, expectedCommands } = result;
161+
this._expectedCommands = expectedCommands;
124162
this._quickFixes = fixes;
125-
onDidRunQuickFix(() => this._disposeQuickFix());
163+
this._register(onDidRunQuickFix((id) => {
164+
const ranQuickFixCommand = (this._expectedCommands?.includes(command.command) || false);
165+
this._logService.debug(quickFixTelemetryTitle, {
166+
id,
167+
fixesShown: this._fixesShown,
168+
ranQuickFixCommand
169+
});
170+
this._telemetryService?.publicLog2<QuickFixResultTelemetryEvent, QuickFixClassification>(quickFixTelemetryTitle, {
171+
id,
172+
fixesShown: this._fixesShown,
173+
ranQuickFixCommand
174+
});
175+
this._disposeQuickFix();
176+
this._fixesShown = false;
177+
}));
126178
}
127179

128180
private _disposeQuickFix(): void {
@@ -177,11 +229,12 @@ export function getQuickFixesForCommand(
177229
quickFixOptions: Map<string, ITerminalQuickFixOptions[]>,
178230
openerService: IOpenerService,
179231
onDidRequestRerunCommand?: Emitter<{ command: string; addNewLine?: boolean }>
180-
): { fixes: IAction[]; onDidRunQuickFix: Event<void> } | undefined {
181-
const onDidRunQuickFixEmitter = new Emitter<void>();
232+
): { fixes: IAction[]; onDidRunQuickFix: Event<string>; expectedCommands?: string[] } | undefined {
233+
const onDidRunQuickFixEmitter = new Emitter<string>();
182234
const onDidRunQuickFix = onDidRunQuickFixEmitter.event;
183235
const fixes: IAction[] = [];
184236
const newCommand = command.command;
237+
const expectedCommands = [];
185238
for (const options of quickFixOptions.values()) {
186239
for (const option of options) {
187240
if (option.exitStatus !== undefined && option.exitStatus !== (command.exitCode === 0)) {
@@ -196,6 +249,7 @@ export function getQuickFixesForCommand(
196249
if (outputMatcher) {
197250
outputMatch = command.getOutputMatch(outputMatcher);
198251
}
252+
const id = option.id;
199253
const quickFixes = option.getQuickFixes({ commandLineMatch, outputMatch }, command);
200254
if (quickFixes) {
201255
for (const quickFix of asArray(quickFixes)) {
@@ -218,6 +272,7 @@ export function getQuickFixesForCommand(
218272
tooltip: label,
219273
command: quickFix.command
220274
} as IAction;
275+
expectedCommands.push(quickFix.command);
221276
break;
222277
}
223278
case 'opener': {
@@ -231,7 +286,7 @@ export function getQuickFixesForCommand(
231286
openerService.open(quickFix.uri);
232287
// since no command gets run here, need to
233288
// clear the decoration and quick fix
234-
onDidRunQuickFixEmitter.fire();
289+
onDidRunQuickFixEmitter.fire(id);
235290
},
236291
tooltip: label,
237292
uri: quickFix.uri
@@ -245,7 +300,10 @@ export function getQuickFixesForCommand(
245300
label: quickFix.label,
246301
class: quickFix.class,
247302
enabled: quickFix.enabled,
248-
run: () => quickFix.run(),
303+
run: () => {
304+
quickFix.run();
305+
onDidRunQuickFixEmitter.fire(id);
306+
},
249307
tooltip: quickFix.tooltip
250308
};
251309
}
@@ -256,7 +314,7 @@ export function getQuickFixesForCommand(
256314
}
257315
}
258316
}
259-
return fixes.length > 0 ? { fixes, onDidRunQuickFix } : undefined;
317+
return fixes.length > 0 ? { fixes, onDidRunQuickFix, expectedCommands } : undefined;
260318
}
261319

262320

0 commit comments

Comments
 (0)