From 9c1b96197020acc393bd9db68501f0a82929139b Mon Sep 17 00:00:00 2001 From: eleanorjboyd <26030610+eleanorjboyd@users.noreply.github.com> Date: Mon, 23 Feb 2026 11:45:44 -0800 Subject: [PATCH] feat: add ENVIRONMENT_DISCOVERY telemetry event and update related properties --- src/common/telemetry/constants.ts | 32 +++++++++++++++- src/internal.api.ts | 62 ++++++++++++++++++++++++++++--- 2 files changed, 88 insertions(+), 6 deletions(-) diff --git a/src/common/telemetry/constants.ts b/src/common/telemetry/constants.ts index 508cbaea..bde80901 100644 --- a/src/common/telemetry/constants.ts +++ b/src/common/telemetry/constants.ts @@ -32,6 +32,15 @@ export enum EventNames { * - projectUnderRoot: number (count of projects nested under workspace roots) */ PROJECT_STRUCTURE = 'PROJECT_STRUCTURE', + /** + * Telemetry event for environment discovery per manager. + * Properties: + * - managerId: string (the id of the environment manager) + * - result: 'success' | 'error' | 'timeout' + * - envCount: number (environments found, on success only) + * - errorType: string (error class name, on failure only) + */ + ENVIRONMENT_DISCOVERY = 'ENVIRONMENT_DISCOVERY', } // Map all events to their properties @@ -134,12 +143,17 @@ export interface IEventNamePropertyMapping { /* __GDPR__ "package_management": { "managerId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" }, - "result": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" } + "result": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" }, + "errorType": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" }, + "triggerSource": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" }, + "": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "eleanorjboyd" } } */ [EventNames.PACKAGE_MANAGEMENT]: { managerId: string; result: 'success' | 'error' | 'cancelled'; + errorType?: string; + triggerSource: 'ui' | 'requirements' | 'package' | 'uninstall'; }; /* __GDPR__ @@ -180,4 +194,20 @@ export interface IEventNamePropertyMapping { uniqueInterpreterCount: number; projectUnderRoot: number; }; + + /* __GDPR__ + "environment_discovery": { + "managerId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" }, + "result": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" }, + "envCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "eleanorjboyd" }, + "errorType": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" }, + "": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "eleanorjboyd" } + } + */ + [EventNames.ENVIRONMENT_DISCOVERY]: { + managerId: string; + result: 'success' | 'error' | 'timeout'; + envCount?: number; + errorType?: string; + }; } diff --git a/src/internal.api.ts b/src/internal.api.ts index 2dcd9589..04d50fe7 100644 --- a/src/internal.api.ts +++ b/src/internal.api.ts @@ -28,6 +28,7 @@ import { SetEnvironmentScope, } from './api'; import { CreateEnvironmentNotSupported, RemoveEnvironmentNotSupported } from './common/errors/NotSupportedError'; +import { StopWatch } from './common/stopWatch'; import { EventNames } from './common/telemetry/constants'; import { sendTelemetryEvent } from './common/telemetry/sender'; @@ -201,8 +202,30 @@ export class InternalEnvironmentManager implements EnvironmentManager { : Promise.reject(new RemoveEnvironmentNotSupported(`Remove Environment not supported by: ${this.id}`)); } - refresh(options: RefreshEnvironmentsScope): Promise { - return this.manager.refresh(options); + async refresh(options: RefreshEnvironmentsScope): Promise { + const sw = new StopWatch(); + try { + await this.manager.refresh(options); + const envs = await this.manager.getEnvironments('all').catch(() => []); + sendTelemetryEvent(EventNames.ENVIRONMENT_DISCOVERY, sw.elapsedTime, { + managerId: this.id, + result: 'success', + envCount: envs.length, + }); + } catch (ex) { + const isTimeout = ex instanceof Error && ex.message.includes('timed out'); + sendTelemetryEvent( + EventNames.ENVIRONMENT_DISCOVERY, + sw.elapsedTime, + { + managerId: this.id, + result: isTimeout ? 'timeout' : 'error', + errorType: ex instanceof Error ? ex.name : 'unknown', + }, + ex instanceof Error ? ex : undefined, + ); + throw ex; + } } getEnvironments(options: GetEnvironmentsScope): Promise { @@ -245,6 +268,23 @@ export class InternalEnvironmentManager implements EnvironmentManager { } } +function inferPackageManagementTrigger( + options: PackageManagementOptions, +): 'ui' | 'requirements' | 'package' | 'uninstall' { + const hasInstall = options.install && options.install.length > 0; + const hasUninstall = options.uninstall && options.uninstall.length > 0; + if (!hasInstall && hasUninstall) { + return 'uninstall'; + } + if (!hasInstall) { + return 'ui'; // empty install list opens the package picker UI + } + if (options.install?.some((arg) => arg === '-r' || arg === '--requirement')) { + return 'requirements'; + } + return 'package'; +} + export class InternalPackageManager implements PackageManager { public constructor( public readonly id: string, @@ -271,18 +311,30 @@ export class InternalPackageManager implements PackageManager { } async manage(environment: PythonEnvironment, options: PackageManagementOptions): Promise { + const stopWatch = new StopWatch(); + const triggerSource = inferPackageManagementTrigger(options); try { await this.manager.manage(environment, options); - sendTelemetryEvent(EventNames.PACKAGE_MANAGEMENT, undefined, { managerId: this.id, result: 'success' }); + sendTelemetryEvent(EventNames.PACKAGE_MANAGEMENT, stopWatch.elapsedTime, { + managerId: this.id, + result: 'success', + triggerSource, + }); } catch (error) { if (error instanceof CancellationError) { - sendTelemetryEvent(EventNames.PACKAGE_MANAGEMENT, undefined, { + sendTelemetryEvent(EventNames.PACKAGE_MANAGEMENT, stopWatch.elapsedTime, { managerId: this.id, result: 'cancelled', + triggerSource, }); throw error; } - sendTelemetryEvent(EventNames.PACKAGE_MANAGEMENT, undefined, { managerId: this.id, result: 'error' }); + sendTelemetryEvent(EventNames.PACKAGE_MANAGEMENT, stopWatch.elapsedTime, { + managerId: this.id, + result: 'error', + errorType: error instanceof Error ? error.name : 'unknown', + triggerSource, + }); throw error; } }