Skip to content

Commit 48ea303

Browse files
authored
Add telemetry for "Python is not installed" prompt (#11163)
* Add telemetry item description * News file * Send telemetry if "no python installed" displayed * Unit tests * Undo unnecessary changes * Change test name
1 parent b3d2252 commit 48ea303

File tree

8 files changed

+113
-3
lines changed

8 files changed

+113
-3
lines changed

news/1 Enhancements/10885.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add telemetry for "Python is not installed" prompt.

src/client/application/diagnostics/checks/pythonInterpreter.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,19 @@ import '../../../common/extensions';
99
import { IConfigurationService, IDisposableRegistry, Resource } from '../../../common/types';
1010
import { IInterpreterService } from '../../../interpreter/contracts';
1111
import { IServiceContainer } from '../../../ioc/types';
12+
import { sendTelemetryEvent } from '../../../telemetry';
13+
import { EventName } from '../../../telemetry/constants';
1214
import { BaseDiagnostic, BaseDiagnosticsService } from '../base';
1315
import { IDiagnosticsCommandFactory } from '../commands/types';
1416
import { DiagnosticCodes } from '../constants';
1517
import { DiagnosticCommandPromptHandlerServiceId, MessageCommandPrompt } from '../promptHandler';
16-
import { DiagnosticScope, IDiagnostic, IDiagnosticCommand, IDiagnosticHandlerService } from '../types';
18+
import {
19+
DiagnosticScope,
20+
IDiagnostic,
21+
IDiagnosticCommand,
22+
IDiagnosticHandlerService,
23+
IDiagnosticMessageOnCloseHandler
24+
} from '../types';
1725

1826
const messages = {
1927
[DiagnosticCodes.NoPythonInterpretersDiagnostic]:
@@ -91,7 +99,8 @@ export class InvalidPythonInterpreterService extends BaseDiagnosticsService {
9199
return;
92100
}
93101
const commandPrompts = this.getCommandPrompts(diagnostic);
94-
return messageService.handle(diagnostic, { commandPrompts, message: diagnostic.message });
102+
const onClose = this.getOnCloseHandler(diagnostic);
103+
return messageService.handle(diagnostic, { commandPrompts, message: diagnostic.message, onClose });
95104
})
96105
);
97106
}
@@ -125,4 +134,15 @@ export class InvalidPythonInterpreterService extends BaseDiagnosticsService {
125134
}
126135
}
127136
}
137+
private getOnCloseHandler(diagnostic: IDiagnostic): IDiagnosticMessageOnCloseHandler | undefined {
138+
if (diagnostic.code === DiagnosticCodes.NoPythonInterpretersDiagnostic) {
139+
return (response?: string) => {
140+
sendTelemetryEvent(EventName.PYTHON_NOT_INSTALLED_PROMPT, undefined, {
141+
selection: response ? 'Download' : 'Ignore'
142+
});
143+
};
144+
}
145+
146+
return;
147+
}
128148
}

src/client/application/diagnostics/promptHandler.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@ import { inject, injectable } from 'inversify';
77
import { DiagnosticSeverity } from 'vscode';
88
import { IApplicationShell } from '../../common/application/types';
99
import { IServiceContainer } from '../../ioc/types';
10-
import { IDiagnostic, IDiagnosticCommand, IDiagnosticHandlerService } from './types';
10+
import { IDiagnostic, IDiagnosticCommand, IDiagnosticHandlerService, IDiagnosticMessageOnCloseHandler } from './types';
1111

1212
export type MessageCommandPrompt = {
1313
commandPrompts: {
1414
prompt: string;
1515
command?: IDiagnosticCommand;
1616
}[];
1717
message?: string;
18+
onClose?: IDiagnosticMessageOnCloseHandler;
1819
};
1920

2021
export const DiagnosticCommandPromptHandlerServiceId = 'DiagnosticCommandPromptHandlerServiceId';
@@ -35,6 +36,9 @@ export class DiagnosticCommandPromptHandlerService implements IDiagnosticHandler
3536
diagnostic.severity,
3637
prompts
3738
);
39+
if (options.onClose) {
40+
options.onClose(response);
41+
}
3842
if (!response) {
3943
return;
4044
}

src/client/application/diagnostics/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ export interface IDiagnosticCommand {
5454
invoke(): Promise<void>;
5555
}
5656

57+
export type IDiagnosticMessageOnCloseHandler = (response?: string) => void;
58+
5759
export const IInvalidPythonPathInDebuggerService = Symbol('IInvalidPythonPathInDebuggerService');
5860

5961
export interface IInvalidPythonPathInDebuggerService extends IDiagnosticsService {

src/client/telemetry/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export enum EventName {
3030
PYTHON_INTERPRETER_ACTIVATION_FOR_TERMINAL = 'PYTHON_INTERPRETER_ACTIVATION_FOR_TERMINAL',
3131
TERMINAL_SHELL_IDENTIFICATION = 'TERMINAL_SHELL_IDENTIFICATION',
3232
PYTHON_INTERPRETER_ACTIVATE_ENVIRONMENT_PROMPT = 'PYTHON_INTERPRETER_ACTIVATE_ENVIRONMENT_PROMPT',
33+
PYTHON_NOT_INSTALLED_PROMPT = 'PYTHON_NOT_INSTALLED_PROMPT',
3334
CONDA_INHERIT_ENV_PROMPT = 'CONDA_INHERIT_ENV_PROMPT',
3435
UNSAFE_INTERPRETER_PROMPT = 'UNSAFE_INTERPRETER_PROMPT',
3536
INSIDERS_RELOAD_PROMPT = 'INSIDERS_RELOAD_PROMPT',

src/client/telemetry/index.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1050,6 +1050,20 @@ export interface IEventNamePropertyMapping {
10501050
*/
10511051
selection: 'Yes' | 'No' | 'Ignore' | undefined;
10521052
};
1053+
/**
1054+
* Telemetry event sent with details when the user clicks a button in the "Python is not installed" prompt.
1055+
* * `Prompt message` :- 'Python is not installed. Please download and install Python before using the extension.'
1056+
*/
1057+
[EventName.PYTHON_NOT_INSTALLED_PROMPT]: {
1058+
/**
1059+
* `Download` When the 'Download' option is clicked
1060+
* `Ignore` When the prompt is dismissed
1061+
*
1062+
* @type {('Download' | 'Ignore' | undefined)}
1063+
*/
1064+
selection: 'Download' | 'Ignore' | undefined;
1065+
};
1066+
10531067
/**
10541068
* Telemetry event sent with details when user clicks a button in the following prompt
10551069
* `Prompt message` :- 'We noticed you are using Visual Studio Code Insiders. Would you like to use the Insiders build of the Python extension?'

src/test/application/diagnostics/checks/pythonInterpreter.unit.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ suite('Application Diagnostics - Checks Python Interpreter', () => {
225225
commandFactory.verifyAll();
226226
expect(messagePrompt).not.be.equal(undefined, 'Message prompt not set');
227227
expect(messagePrompt!.commandPrompts).to.be.deep.equal([{ prompt: 'Download', command: cmd }]);
228+
expect(messagePrompt!.onClose).to.not.be.equal(undefined, 'onClose handler should be set.');
228229
});
229230
test('Handling no currently selected interpreter diagnostic should show select interpreter message', async () => {
230231
const diagnostic = new InvalidPythonInterpreterDiagnostic(
@@ -255,6 +256,7 @@ suite('Application Diagnostics - Checks Python Interpreter', () => {
255256
messageHandler.verifyAll();
256257
commandFactory.verifyAll();
257258
expect(messagePrompt).not.be.equal(undefined, 'Message prompt not set');
259+
expect(messagePrompt!.onClose).be.equal(undefined, 'onClose handler should not be set.');
258260
expect(messagePrompt!.commandPrompts).to.be.deep.equal([
259261
{ prompt: 'Select Python Interpreter', command: cmd }
260262
]);
@@ -288,6 +290,7 @@ suite('Application Diagnostics - Checks Python Interpreter', () => {
288290
messageHandler.verifyAll();
289291
commandFactory.verifyAll();
290292
expect(messagePrompt).not.be.equal(undefined, 'Message prompt not set');
293+
expect(messagePrompt!.onClose).be.equal(undefined, 'onClose handler should not be set.');
291294
expect(messagePrompt!.commandPrompts).to.be.deep.equal([
292295
{ prompt: 'Select Python Interpreter', command: cmd }
293296
]);

src/test/application/diagnostics/promptHandler.unit.test.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
// tslint:disable:insecure-random max-func-body-length no-any
77

8+
import { expect } from 'chai';
89
import * as typemoq from 'typemoq';
910
import { DiagnosticSeverity } from 'vscode';
1011
import {
@@ -69,6 +70,70 @@ suite('Application Diagnostics - PromptHandler', () => {
6970
await promptHandler.handle(diagnostic);
7071
appShell.verifyAll();
7172
});
73+
test(`Handling a diagnositic of severity '${severity.name}' should invoke the onClose handler`, async () => {
74+
const diagnostic: IDiagnostic = {
75+
code: '1' as any,
76+
message: 'one',
77+
scope: DiagnosticScope.Global,
78+
severity: severity.value,
79+
resource: undefined,
80+
invokeHandler: 'default'
81+
};
82+
let onCloseHandlerInvoked = false;
83+
const options: MessageCommandPrompt = {
84+
commandPrompts: [{ prompt: 'Yes' }, { prompt: 'No' }],
85+
message: 'Custom Message',
86+
onClose: () => {
87+
onCloseHandlerInvoked = true;
88+
}
89+
};
90+
91+
switch (severity.value) {
92+
case DiagnosticSeverity.Error: {
93+
appShell
94+
.setup((a) =>
95+
a.showErrorMessage(
96+
typemoq.It.isValue(options.message!),
97+
typemoq.It.isValue('Yes'),
98+
typemoq.It.isValue('No')
99+
)
100+
)
101+
.returns(() => Promise.resolve('Yes'))
102+
.verifiable(typemoq.Times.once());
103+
break;
104+
}
105+
case DiagnosticSeverity.Warning: {
106+
appShell
107+
.setup((a) =>
108+
a.showWarningMessage(
109+
typemoq.It.isValue(options.message!),
110+
typemoq.It.isValue('Yes'),
111+
typemoq.It.isValue('No')
112+
)
113+
)
114+
.returns(() => Promise.resolve('Yes'))
115+
.verifiable(typemoq.Times.once());
116+
break;
117+
}
118+
default: {
119+
appShell
120+
.setup((a) =>
121+
a.showInformationMessage(
122+
typemoq.It.isValue(options.message!),
123+
typemoq.It.isValue('Yes'),
124+
typemoq.It.isValue('No')
125+
)
126+
)
127+
.returns(() => Promise.resolve('Yes'))
128+
.verifiable(typemoq.Times.once());
129+
break;
130+
}
131+
}
132+
133+
await promptHandler.handle(diagnostic, options);
134+
appShell.verifyAll();
135+
expect(onCloseHandlerInvoked).to.equal(true, 'onClose handler should be called.');
136+
});
72137
test(`Handling a diagnositic of severity '${severity.name}' should display a custom message with buttons`, async () => {
73138
const diagnostic: IDiagnostic = {
74139
code: '1' as any,

0 commit comments

Comments
 (0)