Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .github/prompts/add-telemetry.prompt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
mode: agent
---

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.

When adding telemetry:

- 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.
- 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).

Instructions to add a new telemetry event:

1. Add a new event name to the `EventNames` enum in `src/common/telemetry/constants.ts`.
2. Add a corresponding entry to the `IEventNamePropertyMapping` interface in the same file, including a GDPR comment and the expected properties.
3. In the relevant code location, call `sendTelemetryEvent` with the new event name and required properties. Example:
```typescript
sendTelemetryEvent(EventNames.YOUR_EVENT_NAME, undefined, { property: value });
```
4. If the event is triggered by a command, ensure the call is placed at the start of the command handler.

Expected output: The new event is tracked in telemetry and follows the GDPR and codebase conventions.
15 changes: 11 additions & 4 deletions src/common/pickers/environments.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { Uri, ThemeIcon, QuickPickItem, QuickPickItemKind, ProgressLocation, QuickInputButtons } from 'vscode';
import { ProgressLocation, QuickInputButtons, QuickPickItem, QuickPickItemKind, ThemeIcon, Uri } from 'vscode';
import { IconPath, PythonEnvironment, PythonProject } from '../../api';
import { InternalEnvironmentManager } from '../../internal.api';
import { Common, Interpreter, Pickers } from '../localize';
import { showQuickPickWithButtons, showQuickPick, showOpenDialog, withProgress } from '../window.apis';
import { traceError } from '../logging';
import { pickEnvironmentManager } from './managers';
import { handlePythonPath } from '../utils/pythonPath';
import { EventNames } from '../telemetry/constants';
import { sendTelemetryEvent } from '../telemetry/sender';
import { isWindows } from '../utils/platformUtils';
import { handlePythonPath } from '../utils/pythonPath';
import { showOpenDialog, showQuickPick, showQuickPickWithButtons, withProgress } from '../window.apis';
import { pickEnvironmentManager } from './managers';

type QuickPickIcon =
| Uri
Expand Down Expand Up @@ -80,6 +82,7 @@ async function createEnvironment(
const manager = managers.find((m) => m.id === managerId);
if (manager) {
try {
// add telemetry here
const env = await manager.create(
options.projects.map((p) => p.uri),
undefined,
Expand Down Expand Up @@ -111,6 +114,10 @@ async function pickEnvironmentImpl(
if (selected.label === Interpreter.browsePath) {
return browseForPython(managers, projectEnvManagers);
} else if (selected.label === Interpreter.createVirtualEnvironment) {
sendTelemetryEvent(EventNames.CREATE_ENVIRONMENT, undefined, {
manager: 'none',
triggeredLocation: 'pickEnv',
});
return createEnvironment(managers, projectEnvManagers, options);
}
return (selected as { result: PythonEnvironment })?.result;
Expand Down
54 changes: 44 additions & 10 deletions src/common/telemetry/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,34 @@ export enum EventNames {
VENV_CREATION = 'VENV.CREATION',

PACKAGE_MANAGEMENT = 'PACKAGE_MANAGEMENT',
ADD_PROJECT = 'ADD_PROJECT',
/**
* Telemetry event for when a Python environment is created via command.
* Properties:
* - manager: string (the id of the environment manager used, or 'none')
* - triggeredLocation: string (where the create command is called from)
*/
CREATE_ENVIRONMENT = 'CREATE_ENVIRONMENT',
}

// Map all events to their properties
export interface IEventNamePropertyMapping {
/* __GDPR__
"extension.activation_duration": {
"duration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" }
"duration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" }
}
*/
[EventNames.EXTENSION_ACTIVATION_DURATION]: never | undefined;
/* __GDPR__
"extension.manager_registration_duration": {
"duration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" }
"duration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" }
}
*/
[EventNames.EXTENSION_MANAGER_REGISTRATION_DURATION]: never | undefined;

/* __GDPR__
"environment_manager.registered": {
"managerId" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" }
"managerId" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" }
}
*/
[EventNames.ENVIRONMENT_MANAGER_REGISTERED]: {
Expand All @@ -39,7 +47,7 @@ export interface IEventNamePropertyMapping {

/* __GDPR__
"package_manager.registered": {
"managerId" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" }
"managerId" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" }
}
*/
[EventNames.PACKAGE_MANAGER_REGISTERED]: {
Expand All @@ -48,7 +56,7 @@ export interface IEventNamePropertyMapping {

/* __GDPR__
"environment_manager.selected": {
"managerId" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" }
"managerId" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" }
}
*/
[EventNames.ENVIRONMENT_MANAGER_SELECTED]: {
Expand All @@ -57,19 +65,19 @@ export interface IEventNamePropertyMapping {

/* __GDPR__
"package_manager.selected": {
"managerId" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" }
"managerId" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" }
}
*/
[EventNames.PACKAGE_MANAGER_SELECTED]: {
managerId: string;
};

/* __GDPR__
"venv.using_uv": {"owner": "karthiknadig" }
"venv.using_uv": {"owner": "eleanorjboyd" }
*/
[EventNames.VENV_USING_UV]: never | undefined /* __GDPR__
"venv.creation": {
"creationType": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" }
"creationType": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" }
}
*/;
[EventNames.VENV_CREATION]: {
Expand All @@ -78,12 +86,38 @@ export interface IEventNamePropertyMapping {

/* __GDPR__
"package_management": {
"managerId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" },
"result": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" }
"managerId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" },
"result": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" }
}
*/
[EventNames.PACKAGE_MANAGEMENT]: {
managerId: string;
result: 'success' | 'error' | 'cancelled';
};

/* __GDPR__
"add_project": {
"template": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" },
"quickCreate": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" },
"totalProjectCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" },
"triggeredLocation": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" }
}
*/
[EventNames.ADD_PROJECT]: {
template: string;
quickCreate: boolean;
totalProjectCount: number;
triggeredLocation: 'templateCreate' | 'add' | 'addGivenResource';
};

/* __GDPR__
"create_environment": {
"manager": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" },
"triggeredLocation": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" }
}
*/
[EventNames.CREATE_ENVIRONMENT]: {
manager: string;
triggeredLocation: string;
};
}
40 changes: 40 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ async function collectEnvironmentInfo(
projectManager: PythonProjectManager,
): Promise<string> {
const info: string[] = [];

try {
// Extension version
const extensionVersion = context.extension?.packageJSON?.version || 'unknown';
Expand Down Expand Up @@ -241,9 +242,23 @@ export async function activate(context: ExtensionContext): Promise<PythonEnviron
await refreshPackagesCommand(item, envManagers);
}),
commands.registerCommand('python-envs.create', async (item) => {
// Telemetry: record environment creation attempt with selected manager
let managerId = 'unknown';
if (item && item.manager && item.manager.id) {
managerId = item.manager.id;
}
sendTelemetryEvent(EventNames.CREATE_ENVIRONMENT, undefined, {
manager: managerId,
triggeredLocation: 'createSpecifiedCommand',
});
return await createEnvironmentCommand(item, envManagers, projectManager);
}),
commands.registerCommand('python-envs.createAny', async (options) => {
// Telemetry: record environment creation attempt with no specific manager
sendTelemetryEvent(EventNames.CREATE_ENVIRONMENT, undefined, {
manager: 'none',
triggeredLocation: 'createAnyCommand',
});
return await createAnyEnvironmentCommand(
envManagers,
projectManager,
Expand Down Expand Up @@ -282,13 +297,28 @@ export async function activate(context: ExtensionContext): Promise<PythonEnviron
}),
commands.registerCommand('python-envs.addPythonProject', async () => {
await addPythonProjectCommand(undefined, projectManager, envManagers, projectCreators);
const totalProjectCount = projectManager.getProjects().length + 1;
sendTelemetryEvent(EventNames.ADD_PROJECT, undefined, {
template: 'none',
quickCreate: false,
totalProjectCount,
triggeredLocation: 'add',
});
}),
commands.registerCommand('python-envs.addPythonProjectGivenResource', async (resource) => {
// Set context to show/hide menu item depending on whether the resource is already a Python project
if (resource instanceof Uri) {
commands.executeCommand('setContext', 'python-envs:isExistingProject', isExistingProject(resource));
}

await addPythonProjectCommand(resource, projectManager, envManagers, projectCreators);
const totalProjectCount = projectManager.getProjects().length + 1;
sendTelemetryEvent(EventNames.ADD_PROJECT, undefined, {
template: 'none',
quickCreate: false,
totalProjectCount,
triggeredLocation: 'addGivenResource',
});
}),
commands.registerCommand('python-envs.removePythonProject', async (item) => {
// Clear environment association before removing project
Expand Down Expand Up @@ -344,6 +374,9 @@ export async function activate(context: ExtensionContext): Promise<PythonEnviron
commands.registerCommand(
'python-envs.createNewProjectFromTemplate',
async (projectType: string, quickCreate: boolean, newProjectName: string, newProjectPath: string) => {
let projectTemplateName = projectType || 'unknown';
let triggeredLocation: 'templateCreate' = 'templateCreate';
let totalProjectCount = projectManager.getProjects().length + 1;
if (quickCreate) {
if (!projectType || !newProjectName || !newProjectPath) {
throw new Error('Project type, name, and path are required for quick create.');
Expand All @@ -367,9 +400,16 @@ export async function activate(context: ExtensionContext): Promise<PythonEnviron
} else {
const selected = await newProjectSelection(projectCreators.getProjectCreators());
if (selected) {
projectTemplateName = selected.name || 'unknown';
await selected.create();
}
}
sendTelemetryEvent(EventNames.ADD_PROJECT, undefined, {
template: projectTemplateName,
quickCreate: quickCreate,
totalProjectCount,
triggeredLocation,
});
},
),
commands.registerCommand('python-envs.reportIssue', async () => {
Expand Down