Skip to content

Commit ec992b0

Browse files
authored
Merge branch 'main' into quick-create-via-project
2 parents f2f5e3e + e88993e commit ec992b0

4 files changed

Lines changed: 136 additions & 24 deletions

File tree

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
mode: agent
3+
---
4+
5+
If the user does not specify an event name or properties, pick an informative and descriptive name for the telemetry event based on the task or feature. Add properties as you see fit to collect the necessary information to achieve the telemetry goal, ensuring they are relevant and useful for diagnostics or analytics.
6+
7+
When adding telemetry:
8+
9+
- If the user wants to record when an action is started (such as a command invocation), place the telemetry call at the start of the handler or function.
10+
- If the user wants to record successful completions or outcomes, place the telemetry call at the end of the action, after the operation has succeeded (and optionally, record errors or failures as well).
11+
12+
Instructions to add a new telemetry event:
13+
14+
1. Add a new event name to the `EventNames` enum in `src/common/telemetry/constants.ts`.
15+
2. Add a corresponding entry to the `IEventNamePropertyMapping` interface in the same file, including a GDPR comment and the expected properties.
16+
3. In the relevant code location, call `sendTelemetryEvent` with the new event name and required properties. Example:
17+
```typescript
18+
sendTelemetryEvent(EventNames.YOUR_EVENT_NAME, undefined, { property: value });
19+
```
20+
4. If the event is triggered by a command, ensure the call is placed at the start of the command handler.
21+
22+
Expected output: The new event is tracked in telemetry and follows the GDPR and codebase conventions.

src/common/pickers/environments.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { CreateEnvironmentOptions, IconPath, PythonEnvironment, PythonProject }
33
import { InternalEnvironmentManager } from '../../internal.api';
44
import { Common, Interpreter, Pickers } from '../localize';
55
import { traceError } from '../logging';
6+
import { EventNames } from '../telemetry/constants';
7+
import { sendTelemetryEvent } from '../telemetry/sender';
68
import { isWindows } from '../utils/platformUtils';
79
import { handlePythonPath } from '../utils/pythonPath';
810
import { showOpenDialog, showQuickPick, showQuickPickWithButtons, withProgress } from '../window.apis';
@@ -91,6 +93,7 @@ async function createEnvironment(
9193

9294
if (manager) {
9395
try {
96+
// add telemetry here
9497
const env = await manager.create(
9598
options.projects.map((p) => p.uri),
9699
createOptions,
@@ -122,6 +125,10 @@ async function pickEnvironmentImpl(
122125
if (selected.label === Interpreter.browsePath) {
123126
return browseForPython(managers, projectEnvManagers);
124127
} else if (selected.label === Interpreter.createVirtualEnvironment) {
128+
sendTelemetryEvent(EventNames.CREATE_ENVIRONMENT, undefined, {
129+
manager: 'none',
130+
triggeredLocation: 'pickEnv',
131+
});
125132
return createEnvironment(managers, projectEnvManagers, options);
126133
}
127134
return (selected as { result: PythonEnvironment })?.result;

src/common/telemetry/constants.ts

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,34 @@ export enum EventNames {
1111
VENV_CREATION = 'VENV.CREATION',
1212

1313
PACKAGE_MANAGEMENT = 'PACKAGE_MANAGEMENT',
14+
ADD_PROJECT = 'ADD_PROJECT',
15+
/**
16+
* Telemetry event for when a Python environment is created via command.
17+
* Properties:
18+
* - manager: string (the id of the environment manager used, or 'none')
19+
* - triggeredLocation: string (where the create command is called from)
20+
*/
21+
CREATE_ENVIRONMENT = 'CREATE_ENVIRONMENT',
1422
}
1523

1624
// Map all events to their properties
1725
export interface IEventNamePropertyMapping {
1826
/* __GDPR__
1927
"extension.activation_duration": {
20-
"duration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" }
28+
"duration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" }
2129
}
2230
*/
2331
[EventNames.EXTENSION_ACTIVATION_DURATION]: never | undefined;
2432
/* __GDPR__
2533
"extension.manager_registration_duration": {
26-
"duration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" }
34+
"duration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" }
2735
}
2836
*/
2937
[EventNames.EXTENSION_MANAGER_REGISTRATION_DURATION]: never | undefined;
3038

3139
/* __GDPR__
3240
"environment_manager.registered": {
33-
"managerId" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" }
41+
"managerId" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" }
3442
}
3543
*/
3644
[EventNames.ENVIRONMENT_MANAGER_REGISTERED]: {
@@ -39,7 +47,7 @@ export interface IEventNamePropertyMapping {
3947

4048
/* __GDPR__
4149
"package_manager.registered": {
42-
"managerId" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" }
50+
"managerId" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" }
4351
}
4452
*/
4553
[EventNames.PACKAGE_MANAGER_REGISTERED]: {
@@ -48,7 +56,7 @@ export interface IEventNamePropertyMapping {
4856

4957
/* __GDPR__
5058
"environment_manager.selected": {
51-
"managerId" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" }
59+
"managerId" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" }
5260
}
5361
*/
5462
[EventNames.ENVIRONMENT_MANAGER_SELECTED]: {
@@ -57,19 +65,19 @@ export interface IEventNamePropertyMapping {
5765

5866
/* __GDPR__
5967
"package_manager.selected": {
60-
"managerId" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" }
68+
"managerId" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" }
6169
}
6270
*/
6371
[EventNames.PACKAGE_MANAGER_SELECTED]: {
6472
managerId: string;
6573
};
6674

6775
/* __GDPR__
68-
"venv.using_uv": {"owner": "karthiknadig" }
76+
"venv.using_uv": {"owner": "eleanorjboyd" }
6977
*/
7078
[EventNames.VENV_USING_UV]: never | undefined /* __GDPR__
7179
"venv.creation": {
72-
"creationType": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" }
80+
"creationType": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" }
7381
}
7482
*/;
7583
[EventNames.VENV_CREATION]: {
@@ -78,12 +86,38 @@ export interface IEventNamePropertyMapping {
7886

7987
/* __GDPR__
8088
"package_management": {
81-
"managerId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" },
82-
"result": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" }
89+
"managerId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" },
90+
"result": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" }
8391
}
8492
*/
8593
[EventNames.PACKAGE_MANAGEMENT]: {
8694
managerId: string;
8795
result: 'success' | 'error' | 'cancelled';
8896
};
97+
98+
/* __GDPR__
99+
"add_project": {
100+
"template": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" },
101+
"quickCreate": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" },
102+
"totalProjectCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" },
103+
"triggeredLocation": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" }
104+
}
105+
*/
106+
[EventNames.ADD_PROJECT]: {
107+
template: string;
108+
quickCreate: boolean;
109+
totalProjectCount: number;
110+
triggeredLocation: 'templateCreate' | 'add' | 'addGivenResource';
111+
};
112+
113+
/* __GDPR__
114+
"create_environment": {
115+
"manager": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" },
116+
"triggeredLocation": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" }
117+
}
118+
*/
119+
[EventNames.CREATE_ENVIRONMENT]: {
120+
manager: string;
121+
triggeredLocation: string;
122+
};
89123
}

src/extension.ts

Lines changed: 63 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { commands, extensions, ExtensionContext, LogOutputChannel, Terminal, Uri, window, workspace } from 'vscode';
1+
import { commands, ExtensionContext, extensions, LogOutputChannel, Terminal, Uri, window, workspace } from 'vscode';
22
import { PythonEnvironment, PythonEnvironmentApi, PythonProjectCreator } from './api';
33
import { ensureCorrectVersion } from './common/extVersion';
44
import { registerLogger, traceError, traceInfo } from './common/logging';
@@ -75,27 +75,27 @@ import { registerPyenvFeatures } from './managers/pyenv/main';
7575
async function collectEnvironmentInfo(
7676
context: ExtensionContext,
7777
envManagers: EnvironmentManagers,
78-
projectManager: PythonProjectManager
78+
projectManager: PythonProjectManager,
7979
): Promise<string> {
8080
const info: string[] = [];
81-
81+
8282
try {
8383
// Extension version
8484
const extensionVersion = context.extension?.packageJSON?.version || 'unknown';
8585
info.push(`Extension Version: ${extensionVersion}`);
86-
86+
8787
// Python extension version
8888
const pythonExtension = extensions.getExtension('ms-python.python');
8989
const pythonVersion = pythonExtension?.packageJSON?.version || 'not installed';
9090
info.push(`Python Extension Version: ${pythonVersion}`);
91-
91+
9292
// Environment managers
9393
const managers = envManagers.managers;
9494
info.push(`\nRegistered Environment Managers (${managers.length}):`);
95-
managers.forEach(manager => {
95+
managers.forEach((manager) => {
9696
info.push(` - ${manager.id} (${manager.displayName})`);
9797
});
98-
98+
9999
// Available environments
100100
const allEnvironments: PythonEnvironment[] = [];
101101
for (const manager of managers) {
@@ -106,7 +106,7 @@ async function collectEnvironmentInfo(
106106
info.push(` Error getting environments from ${manager.id}: ${err}`);
107107
}
108108
}
109-
109+
110110
info.push(`\nTotal Available Environments: ${allEnvironments.length}`);
111111
if (allEnvironments.length > 0) {
112112
info.push('Environment Details:');
@@ -117,7 +117,7 @@ async function collectEnvironmentInfo(
117117
info.push(` ... and ${allEnvironments.length - 10} more environments`);
118118
}
119119
}
120-
120+
121121
// Python projects
122122
const projects = projectManager.getProjects();
123123
info.push(`\nPython Projects (${projects.length}):`);
@@ -133,24 +133,34 @@ async function collectEnvironmentInfo(
133133
info.push(` Error getting environment: ${err}`);
134134
}
135135
}
136-
136+
137137
// Current settings (non-sensitive)
138138
const config = workspace.getConfiguration('python-envs');
139139
info.push('\nExtension Settings:');
140140
info.push(` Default Environment Manager: ${config.get('defaultEnvManager')}`);
141141
info.push(` Default Package Manager: ${config.get('defaultPackageManager')}`);
142142
info.push(` Terminal Auto Activation: ${config.get('terminal.autoActivationType')}`);
143-
144143
} catch (err) {
145144
info.push(`\nError collecting environment information: ${err}`);
146145
}
147-
146+
148147
return info.join('\n');
149148
}
150149

151150
export async function activate(context: ExtensionContext): Promise<PythonEnvironmentApi> {
152151
const start = new StopWatch();
153152

153+
// Attempt to set setting of config.python.useEnvironmentsExtension to true
154+
try {
155+
const config = workspace.getConfiguration('python');
156+
await config.update('useEnvironmentsExtension', true, true);
157+
} catch (err) {
158+
traceError(
159+
'Failed to set config.python.useEnvironmentsExtension to true. Please do so manually in your user settings now to ensure the Python environment extension is enabled during upcoming experimentation.',
160+
err,
161+
);
162+
}
163+
154164
// Logging should be set up before anything else.
155165
const outputChannel: LogOutputChannel = createLogOutputChannel('Python Environments');
156166
context.subscriptions.push(outputChannel, registerLogger(outputChannel));
@@ -232,9 +242,23 @@ export async function activate(context: ExtensionContext): Promise<PythonEnviron
232242
await refreshPackagesCommand(item, envManagers);
233243
}),
234244
commands.registerCommand('python-envs.create', async (item) => {
245+
// Telemetry: record environment creation attempt with selected manager
246+
let managerId = 'unknown';
247+
if (item && item.manager && item.manager.id) {
248+
managerId = item.manager.id;
249+
}
250+
sendTelemetryEvent(EventNames.CREATE_ENVIRONMENT, undefined, {
251+
manager: managerId,
252+
triggeredLocation: 'createSpecifiedCommand',
253+
});
235254
return await createEnvironmentCommand(item, envManagers, projectManager);
236255
}),
237256
commands.registerCommand('python-envs.createAny', async (options) => {
257+
// Telemetry: record environment creation attempt with no specific manager
258+
sendTelemetryEvent(EventNames.CREATE_ENVIRONMENT, undefined, {
259+
manager: 'none',
260+
triggeredLocation: 'createAnyCommand',
261+
});
238262
return await createAnyEnvironmentCommand(
239263
envManagers,
240264
projectManager,
@@ -273,13 +297,28 @@ export async function activate(context: ExtensionContext): Promise<PythonEnviron
273297
}),
274298
commands.registerCommand('python-envs.addPythonProject', async () => {
275299
await addPythonProjectCommand(undefined, projectManager, envManagers, projectCreators);
300+
const totalProjectCount = projectManager.getProjects().length + 1;
301+
sendTelemetryEvent(EventNames.ADD_PROJECT, undefined, {
302+
template: 'none',
303+
quickCreate: false,
304+
totalProjectCount,
305+
triggeredLocation: 'add',
306+
});
276307
}),
277308
commands.registerCommand('python-envs.addPythonProjectGivenResource', async (resource) => {
278309
// Set context to show/hide menu item depending on whether the resource is already a Python project
279310
if (resource instanceof Uri) {
280311
commands.executeCommand('setContext', 'python-envs:isExistingProject', isExistingProject(resource));
281312
}
313+
282314
await addPythonProjectCommand(resource, projectManager, envManagers, projectCreators);
315+
const totalProjectCount = projectManager.getProjects().length + 1;
316+
sendTelemetryEvent(EventNames.ADD_PROJECT, undefined, {
317+
template: 'none',
318+
quickCreate: false,
319+
totalProjectCount,
320+
triggeredLocation: 'addGivenResource',
321+
});
283322
}),
284323
commands.registerCommand('python-envs.removePythonProject', async (item) => {
285324
// Clear environment association before removing project
@@ -335,6 +374,9 @@ export async function activate(context: ExtensionContext): Promise<PythonEnviron
335374
commands.registerCommand(
336375
'python-envs.createNewProjectFromTemplate',
337376
async (projectType: string, quickCreate: boolean, newProjectName: string, newProjectPath: string) => {
377+
let projectTemplateName = projectType || 'unknown';
378+
let triggeredLocation: 'templateCreate' = 'templateCreate';
379+
let totalProjectCount = projectManager.getProjects().length + 1;
338380
if (quickCreate) {
339381
if (!projectType || !newProjectName || !newProjectPath) {
340382
throw new Error('Project type, name, and path are required for quick create.');
@@ -358,19 +400,26 @@ export async function activate(context: ExtensionContext): Promise<PythonEnviron
358400
} else {
359401
const selected = await newProjectSelection(projectCreators.getProjectCreators());
360402
if (selected) {
403+
projectTemplateName = selected.name || 'unknown';
361404
await selected.create();
362405
}
363406
}
407+
sendTelemetryEvent(EventNames.ADD_PROJECT, undefined, {
408+
template: projectTemplateName,
409+
quickCreate: quickCreate,
410+
totalProjectCount,
411+
triggeredLocation,
412+
});
364413
},
365414
),
366415
commands.registerCommand('python-envs.reportIssue', async () => {
367416
try {
368417
const issueData = await collectEnvironmentInfo(context, envManagers, projectManager);
369-
418+
370419
await commands.executeCommand('workbench.action.openIssueReporter', {
371420
extensionId: 'ms-python.vscode-python-envs',
372421
issueTitle: '[Python Environments] ',
373-
issueBody: `<!-- Please describe the issue you're experiencing -->\n\n<!-- The following information was automatically generated -->\n\n<details>\n<summary>Environment Information</summary>\n\n\`\`\`\n${issueData}\n\`\`\`\n\n</details>`
422+
issueBody: `<!-- Please describe the issue you're experiencing -->\n\n<!-- The following information was automatically generated -->\n\n<details>\n<summary>Environment Information</summary>\n\n\`\`\`\n${issueData}\n\`\`\`\n\n</details>`,
374423
});
375424
} catch (error) {
376425
window.showErrorMessage(`Failed to open issue reporter: ${error}`);

0 commit comments

Comments
 (0)