From 2c5d94564ef17e1dfdd1ee980991c3f42c5dd9bd Mon Sep 17 00:00:00 2001 From: "@rugpanov" Date: Tue, 1 Sep 2026 11:24:06 +0200 Subject: [PATCH 1/3] Add Python install fallback recovery UX --- .../PythonSetupEnvironmentSetup.test.ts | 92 ++++++++++++++++++- .../PythonSetupEnvironmentSetup.ts | 34 ++++++- .../python-setup/models/PythonSetupResult.ts | 4 + .../python-setup/utils/errorMessages.test.ts | 23 ++++- .../src/python-setup/utils/errorMessages.ts | 21 +++-- .../python-setup/utils/setupSummary.test.ts | 13 +++ .../src/python-setup/utils/setupSummary.ts | 9 +- .../src/telemetry/constants.ts | 14 +++ .../telemetry/pythonSetupExtensions.test.ts | 23 +++++ .../src/telemetry/pythonSetupExtensions.ts | 5 + 10 files changed, 220 insertions(+), 18 deletions(-) diff --git a/packages/databricks-vscode/src/python-setup/controllers/PythonSetupEnvironmentSetup.test.ts b/packages/databricks-vscode/src/python-setup/controllers/PythonSetupEnvironmentSetup.test.ts index 368d88e53..488467fff 100644 --- a/packages/databricks-vscode/src/python-setup/controllers/PythonSetupEnvironmentSetup.test.ts +++ b/packages/databricks-vscode/src/python-setup/controllers/PythonSetupEnvironmentSetup.test.ts @@ -386,6 +386,94 @@ describe("PythonSetupEnvironmentSetup.setup", () => { ]); }); + it("offers manual interpreter selection without retrying a failed download", async () => { + const shown: {actions?: PythonSetupErrorAction[]}[] = []; + const telemetry = makeTelemetryRecorder(); + const pythonInstallFailure: PythonSetupResult = { + schemaVersion: 1, + command: "environments setup-local", + ok: false, + mode: "default", + dryRun: false, + greenfield: false, + phases: [], + warnings: [], + durationMs: 0, + error: { + code: "E_PYTHON_INSTALL", + failurePhase: "provision", + message: "Python download failed", + diskMutated: false, + }, + }; + const cli = makeCli({resolve: pythonInstallFailure}); + const setup = new PythonSetupEnvironmentSetup( + makeDeps({ + cli, + recordSetupAttempt: telemetry.recordSetupAttempt, + showError: async (_message, _detail, actions) => { + shown.push({actions}); + }, + }) + ); + + await setup.setup(); + + expect(cli.calls).to.have.length(1); + expect(telemetry.results[0].pythonInstallFlow).to.equal( + "manual_selection_requested" + ); + expect(shown[0].actions).to.deep.equal([ + { + label: "Select Python interpreter", + command: "databricks.environment.selectPythonInterpreter", + }, + ]); + }); + + it("prioritizes manual selection after installed fallback and keeps report in logs", async () => { + const shown: { + detail?: string; + actions?: PythonSetupErrorAction[]; + }[] = []; + const fallbackFailure: PythonSetupResult = { + schemaVersion: 1, + command: "environments setup-local", + ok: false, + mode: "default", + dryRun: false, + pythonResolution: "installed_fallback", + greenfield: false, + phases: [], + warnings: [], + durationMs: 0, + error: { + code: "E_VALIDATE", + failurePhase: "validate", + message: "selected Python could not be validated", + diskMutated: false, + }, + }; + const setup = new PythonSetupEnvironmentSetup( + makeDeps({ + cli: makeCli({resolve: fallbackFailure}), + showError: async (_message, detail, actions) => { + shown.push({detail, actions}); + }, + }) + ); + + await setup.setup(); + + expect(shown[0].actions).to.deep.equal([ + { + label: "Select Python interpreter", + command: "databricks.environment.selectPythonInterpreter", + }, + ]); + expect(shown[0].detail).to.contain("Report this problem:"); + }); + it("offers the mapped documentation action for a doc-linked failure", async () => { const shown: {actions?: PythonSetupErrorAction[]}[] = []; const setup = new PythonSetupEnvironmentSetup( @@ -1167,7 +1255,9 @@ describe("PythonSetupEnvironmentSetup telemetry", () => { await setup.setup(); // Distinct from `failed`: the user gave up, nothing broke. - expect(telemetry.results).to.deep.equal([{outcome: "cancelled"}]); + expect(telemetry.results).to.deep.equal([ + {outcome: "cancelled", pythonInstallFlow: "cancelled"}, + ]); }); it("reports not_started when the CLI produces no result", async () => { diff --git a/packages/databricks-vscode/src/python-setup/controllers/PythonSetupEnvironmentSetup.ts b/packages/databricks-vscode/src/python-setup/controllers/PythonSetupEnvironmentSetup.ts index 684146cbd..e08502062 100644 --- a/packages/databricks-vscode/src/python-setup/controllers/PythonSetupEnvironmentSetup.ts +++ b/packages/databricks-vscode/src/python-setup/controllers/PythonSetupEnvironmentSetup.ts @@ -17,6 +17,7 @@ import { isIndexUnreachableFailure, NO_COMPUTE_TARGET_MESSAGE, PythonSetupErrorAction, + SELECT_PYTHON_INTERPRETER_COMMAND_ID, } from "../utils/errorMessages"; import { buildExtensionFailureReportAction, @@ -385,7 +386,10 @@ export class PythonSetupEnvironmentSetup implements Disposable { } catch (e) { // A cancelled run is a user action, not a failure: stay quiet. if (e instanceof PythonSetupCancelledError) { - reportResult({outcome: "cancelled"}); + reportResult({ + outcome: "cancelled", + pythonInstallFlow: "cancelled", + }); return; } // Spawn/parse errors reject with a real Error carrying CLI stderr; @@ -418,8 +422,23 @@ export class PythonSetupEnvironmentSetup implements Disposable { // its doc-link button, unchanged. const reportAction = getPythonSetupReportAction(result, reportEnv); const reportRepo = reportRepoForResult(result); + const remediationActions = getPythonSetupErrorActions(result); + const pythonInstallFlow = + result.error?.code === "E_PYTHON_INSTALL" || + result.pythonResolution === "installed_fallback" + ? "manual_selection_requested" + : result.pythonResolution; + const actions = remediationActions.some( + (candidate) => + candidate.command === SELECT_PYTHON_INTERPRETER_COMMAND_ID + ) + ? remediationActions + : reportAction + ? [reportAction] + : remediationActions; reportResult({ outcome: "failed", + ...(pythonInstallFlow !== undefined ? {pythonInstallFlow} : {}), failurePhase: result.error?.failurePhase, errorCode: result.error?.code, envKey: result.compute?.envKey, @@ -435,9 +454,7 @@ export class PythonSetupEnvironmentSetup implements Disposable { result, reportRepo ? reportLogLink(reportRepo) : undefined ), - reportAction - ? [reportAction] - : getPythonSetupErrorActions(result) + actions ) ); return; @@ -466,6 +483,9 @@ export class PythonSetupEnvironmentSetup implements Disposable { }); reportResult({ outcome: "failed", + ...(result.pythonResolution !== undefined + ? {pythonInstallFlow: result.pythonResolution} + : {}), failurePhase: "adopt", envKey: result.compute.envKey, reportOffered: true, @@ -501,6 +521,9 @@ export class PythonSetupEnvironmentSetup implements Disposable { const message = e instanceof Error ? e.message : String(e); reportResult({ outcome: "failed", + ...(result.pythonResolution !== undefined + ? {pythonInstallFlow: result.pythonResolution} + : {}), failurePhase: "persist", envKey: result.compute.envKey, reportOffered: true, @@ -527,6 +550,9 @@ export class PythonSetupEnvironmentSetup implements Disposable { // releases regardless of whether the user dismisses the notification. reportResult({ outcome: "ok", + ...(result.pythonResolution !== undefined + ? {pythonInstallFlow: result.pythonResolution} + : {}), envKey: result.compute.envKey, warnings: result.warnings, }); diff --git a/packages/databricks-vscode/src/python-setup/models/PythonSetupResult.ts b/packages/databricks-vscode/src/python-setup/models/PythonSetupResult.ts index 9226ffa0a..9f9e50976 100644 --- a/packages/databricks-vscode/src/python-setup/models/PythonSetupResult.ts +++ b/packages/databricks-vscode/src/python-setup/models/PythonSetupResult.ts @@ -14,6 +14,9 @@ /** Provisioning mode. `constraints-only` omits the databricks-connect dep. */ export type PythonSetupMode = "default" | "constraints-only"; +/** How the CLI obtained the Python interpreter used for provisioning. */ +export type PythonResolution = "uv_install_succeeded" | "installed_fallback"; + /** Canonical execution phases, always reported in this order. */ export type PythonSetupPhaseName = | "preflight" @@ -97,6 +100,7 @@ export interface PythonSetupResult { ok: boolean; mode: PythonSetupMode; dryRun: boolean; + pythonResolution?: PythonResolution; /** * The resolved compute the environment was provisioned against. Mirrors the * CLI's `compute` result key (renamed from `target` in databricks/cli#6100) diff --git a/packages/databricks-vscode/src/python-setup/utils/errorMessages.test.ts b/packages/databricks-vscode/src/python-setup/utils/errorMessages.test.ts index 0377f59d2..c6d2bff88 100644 --- a/packages/databricks-vscode/src/python-setup/utils/errorMessages.test.ts +++ b/packages/databricks-vscode/src/python-setup/utils/errorMessages.test.ts @@ -316,12 +316,29 @@ describe("getPythonSetupErrorAction", () => { }); }); - it("points E_PYTHON_INSTALL at the uv install-Python docs", () => { + it("asks for manual interpreter selection after Python download fails", () => { expect( getPythonSetupErrorAction(failure("E_PYTHON_INSTALL")) ).to.deep.equal({ - label: "Install a Python version", - url: "https://docs.astral.sh/uv/guides/install-python/", + label: "Select Python interpreter", + command: "databricks.environment.selectPythonInterpreter", + }); + }); + + it("asks for manual selection when provisioning fails after installed fallback", () => { + expect( + getPythonSetupErrorAction( + failure( + "E_PROVISION", + {}, + { + pythonResolution: "installed_fallback", + } + ) + ) + ).to.deep.equal({ + label: "Select Python interpreter", + command: "databricks.environment.selectPythonInterpreter", }); }); diff --git a/packages/databricks-vscode/src/python-setup/utils/errorMessages.ts b/packages/databricks-vscode/src/python-setup/utils/errorMessages.ts index 043a24288..df3d2663f 100644 --- a/packages/databricks-vscode/src/python-setup/utils/errorMessages.ts +++ b/packages/databricks-vscode/src/python-setup/utils/errorMessages.ts @@ -45,10 +45,6 @@ export const UV_INDEX_DOCS_URL = export const UV_PROJECTS_DOCS_URL = "https://docs.astral.sh/uv/concepts/projects/"; -/** uv's Python-version guide — for E_PYTHON_INSTALL. */ -export const UV_PYTHON_INSTALL_DOCS_URL = - "https://docs.astral.sh/uv/guides/install-python/"; - /** uv's resolution concept page — for a genuine E_PROVISION dependency conflict. */ export const UV_RESOLUTION_DOCS_URL = "https://docs.astral.sh/uv/concepts/resolution/"; @@ -142,6 +138,10 @@ export function isIndexUnreachableFailure(result: PythonSetupResult): boolean { export const USE_MANUAL_SETUP_COMMAND_ID = "databricks.environment.useManualPythonSetup"; +/** Existing extension command that opens the Python interpreter picker. */ +export const SELECT_PYTHON_INTERPRETER_COMMAND_ID = + "databricks.environment.selectPythonInterpreter"; + /** * Command that runs uv's official installer in a terminal for the current * platform, surfaced as the primary E_UV_MISSING remediation button (see @@ -251,10 +251,6 @@ const DOC_LINKS: Partial> = label: "Set up a uv project", url: UV_PROJECTS_DOCS_URL, }, - E_PYTHON_INSTALL: { - label: "Install a Python version", - url: UV_PYTHON_INSTALL_DOCS_URL, - }, E_PROVISION: { label: "Resolve dependency conflicts", url: UV_RESOLUTION_DOCS_URL, @@ -287,6 +283,15 @@ export function getPythonSetupErrorAction( if (!err) { return undefined; } + if ( + err.code === "E_PYTHON_INSTALL" || + result.pythonResolution === "installed_fallback" + ) { + return { + label: "Select Python interpreter", + command: SELECT_PYTHON_INTERPRETER_COMMAND_ID, + }; + } // A blocked package index arrives as E_PROVISION and is distinguished by the // CLI's message, not its code — so resolve it before the code-keyed map, // ahead of E_PROVISION's generic dependency-conflict link. diff --git a/packages/databricks-vscode/src/python-setup/utils/setupSummary.test.ts b/packages/databricks-vscode/src/python-setup/utils/setupSummary.test.ts index 9f24dafa2..c7fd8ff1e 100644 --- a/packages/databricks-vscode/src/python-setup/utils/setupSummary.test.ts +++ b/packages/databricks-vscode/src/python-setup/utils/setupSummary.test.ts @@ -38,6 +38,19 @@ describe("formatSetupNotification", () => { expect(message).to.contain("with 1 warning —"); expect(message).to.not.contain("1 warnings"); }); + + it("explains when setup used an installed Python after download failed", () => { + const fallback = { + ...SUCCESS_DEFAULT, + pythonResolution: "installed_fallback", + } as PythonSetupResult; + + expect(formatSetupNotification(fallback)).to.equal( + "Python environment ready — Python download failed; used a " + + "compatible installed Python instead. .venv created and " + + "selected for your Databricks project." + ); + }); }); describe("formatSetupLog", () => { diff --git a/packages/databricks-vscode/src/python-setup/utils/setupSummary.ts b/packages/databricks-vscode/src/python-setup/utils/setupSummary.ts index e93fdb7bd..8957f3d95 100644 --- a/packages/databricks-vscode/src/python-setup/utils/setupSummary.ts +++ b/packages/databricks-vscode/src/python-setup/utils/setupSummary.ts @@ -16,13 +16,18 @@ import {venvInterpreterPath} from "./venvInterpreterPath"; */ export function formatSetupNotification(result: PythonSetupResult): string { const tail = ".venv created and selected for your Databricks project."; + const fallback = + result.pythonResolution === "installed_fallback" + ? "Python download failed; used a compatible installed Python " + + "instead. " + : ""; const n = result.warnings.length; if (n === 0) { - return `Python environment ready — ${tail}`; + return `Python environment ready — ${fallback}${tail}`; } return ( `Python environment ready, with ${n} ` + - `warning${n === 1 ? "" : "s"} — ${tail}` + `warning${n === 1 ? "" : "s"} — ${fallback}${tail}` ); } diff --git a/packages/databricks-vscode/src/telemetry/constants.ts b/packages/databricks-vscode/src/telemetry/constants.ts index 0642edf3b..94390df01 100644 --- a/packages/databricks-vscode/src/telemetry/constants.ts +++ b/packages/databricks-vscode/src/telemetry/constants.ts @@ -118,6 +118,13 @@ export type SetupTrigger = "auto_open" | "explicit_command" | "run" | "debug"; */ export type PythonSetupRunTrigger = "initial" | "rerun"; +/** Categorical outcome of Python acquisition and its user recovery path. */ +export type PythonInstallFlow = + | "uv_install_succeeded" + | "installed_fallback" + | "manual_selection_requested" + | "cancelled"; + // The uv-native ("VPEX") python-setup flow mirrors the CLI's `environments // setup-local --output json` contract, so the setup event unions are owned by // the result model (the TypeScript view of that contract) and re-exported here. @@ -528,6 +535,7 @@ export class EventTypes { }; [Events.PYTHON_ENV_SETUP_RESULT]: EventType<{ outcome: PythonSetupOutcome; + pythonInstallFlow?: PythonInstallFlow; failurePhase?: PythonSetupFailurePhase; errorCode?: PythonSetupErrorCode; envKey?: string; @@ -556,6 +564,12 @@ export class EventTypes { "ok | failed | cancelled (user aborted) | not_started (the CLI produced no " + "result) | no_compute (the CTA was a dead end: nothing was attached to set up for)", }, + pythonInstallFlow: { + comment: + "Whether uv downloaded Python, an installed compatible Python was used, " + + "manual interpreter selection was requested, or the operation was cancelled. " + + "Categorical only; never contains an interpreter path or version", + }, failurePhase: { comment: "Which phase broke: the CLI's preflight/resolve/fetch/merge/provision/validate, " + diff --git a/packages/databricks-vscode/src/telemetry/pythonSetupExtensions.test.ts b/packages/databricks-vscode/src/telemetry/pythonSetupExtensions.test.ts index d059bc956..4b44366a7 100644 --- a/packages/databricks-vscode/src/telemetry/pythonSetupExtensions.test.ts +++ b/packages/databricks-vscode/src/telemetry/pythonSetupExtensions.test.ts @@ -209,6 +209,29 @@ describe(__filename, () => { expect(events[1].props["event.outcome"]).to.equal("ok"); }); + it("reports only the categorical Python install flow", () => { + for (const pythonInstallFlow of [ + "uv_install_succeeded", + "installed_fallback", + "manual_selection_requested", + "cancelled", + ] as const) { + const {telemetry, events} = makeTelemetry(); + telemetry.recordPythonSetupAttempt({ + packageManager: "uv", + targetType: "cluster", + mode: "default", + trigger: "initial", + })({outcome: "ok", pythonInstallFlow}); + + expect(events[1].props["event.pythonInstallFlow"]).to.equal( + pythonInstallFlow + ); + expect(JSON.stringify(events[1])).to.not.contain("/usr/bin/python"); + expect(JSON.stringify(events[1])).to.not.contain("3.12"); + } + }); + it("passes through the CLI's documented env-key shapes", () => { for (const envKey of [ "serverless/serverless-v5", diff --git a/packages/databricks-vscode/src/telemetry/pythonSetupExtensions.ts b/packages/databricks-vscode/src/telemetry/pythonSetupExtensions.ts index d96af01b9..cbd6cfce0 100644 --- a/packages/databricks-vscode/src/telemetry/pythonSetupExtensions.ts +++ b/packages/databricks-vscode/src/telemetry/pythonSetupExtensions.ts @@ -5,6 +5,7 @@ import { PythonSetupDriftTrigger, PythonSetupErrorCode, PythonSetupFailurePhase, + PythonInstallFlow, PythonSetupMode, PythonSetupOutcome, PythonSetupRunTrigger, @@ -61,6 +62,7 @@ export interface PythonSetupAdoption { /** How a setup run ended, reduced to the categorical fields we report. */ export interface PythonSetupOutcomeReport { outcome: PythonSetupOutcome; + pythonInstallFlow?: PythonInstallFlow; failurePhase?: PythonSetupFailurePhase; errorCode?: PythonSetupErrorCode; envKey?: string; @@ -282,6 +284,9 @@ Telemetry.prototype.recordPythonSetupAttempt = function ( reported = true; reportResult({ outcome: report.outcome, + ...(report.pythonInstallFlow !== undefined + ? {pythonInstallFlow: report.pythonInstallFlow} + : {}), ...(report.failurePhase !== undefined ? {failurePhase: report.failurePhase} : {}), From b5f6aed47f6ae0b492cca527bb5dce5d56ebddfe Mon Sep 17 00:00:00 2001 From: "@rugpanov" Date: Tue, 1 Sep 2026 14:27:57 +0200 Subject: [PATCH 2/3] Update Python recovery action coverage --- .../src/python-setup/utils/errorMessages.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/databricks-vscode/src/python-setup/utils/errorMessages.test.ts b/packages/databricks-vscode/src/python-setup/utils/errorMessages.test.ts index c6d2bff88..e0ab5e5b6 100644 --- a/packages/databricks-vscode/src/python-setup/utils/errorMessages.test.ts +++ b/packages/databricks-vscode/src/python-setup/utils/errorMessages.test.ts @@ -423,13 +423,13 @@ describe("getPythonSetupErrorActions", () => { ]); }); - it("wraps a non-uv code's single action in a one-element list", () => { + it("wraps Python install recovery in a one-element list", () => { expect( getPythonSetupErrorActions(failure("E_PYTHON_INSTALL")) ).to.deep.equal([ { - label: "Install a Python version", - url: "https://docs.astral.sh/uv/guides/install-python/", + label: "Select Python interpreter", + command: "databricks.environment.selectPythonInterpreter", }, ]); }); From a2c553d90161fd0488d5433b92b0a16397d933b6 Mon Sep 17 00:00:00 2001 From: "@rugpanov" Date: Tue, 1 Sep 2026 14:41:25 +0200 Subject: [PATCH 3/3] Clarify Python setup flow telemetry --- .../controllers/PythonSetupEnvironmentSetup.test.ts | 4 ++-- .../controllers/PythonSetupEnvironmentSetup.ts | 12 ++++++------ .../databricks-vscode/src/telemetry/constants.ts | 6 +++--- .../src/telemetry/pythonSetupExtensions.test.ts | 8 ++++---- .../src/telemetry/pythonSetupExtensions.ts | 8 ++++---- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/packages/databricks-vscode/src/python-setup/controllers/PythonSetupEnvironmentSetup.test.ts b/packages/databricks-vscode/src/python-setup/controllers/PythonSetupEnvironmentSetup.test.ts index 488467fff..16a0658a1 100644 --- a/packages/databricks-vscode/src/python-setup/controllers/PythonSetupEnvironmentSetup.test.ts +++ b/packages/databricks-vscode/src/python-setup/controllers/PythonSetupEnvironmentSetup.test.ts @@ -420,7 +420,7 @@ describe("PythonSetupEnvironmentSetup.setup", () => { await setup.setup(); expect(cli.calls).to.have.length(1); - expect(telemetry.results[0].pythonInstallFlow).to.equal( + expect(telemetry.results[0].pythonSetupFlow).to.equal( "manual_selection_requested" ); expect(shown[0].actions).to.deep.equal([ @@ -1256,7 +1256,7 @@ describe("PythonSetupEnvironmentSetup telemetry", () => { // Distinct from `failed`: the user gave up, nothing broke. expect(telemetry.results).to.deep.equal([ - {outcome: "cancelled", pythonInstallFlow: "cancelled"}, + {outcome: "cancelled", pythonSetupFlow: "cancelled"}, ]); }); diff --git a/packages/databricks-vscode/src/python-setup/controllers/PythonSetupEnvironmentSetup.ts b/packages/databricks-vscode/src/python-setup/controllers/PythonSetupEnvironmentSetup.ts index e08502062..2f88d7f32 100644 --- a/packages/databricks-vscode/src/python-setup/controllers/PythonSetupEnvironmentSetup.ts +++ b/packages/databricks-vscode/src/python-setup/controllers/PythonSetupEnvironmentSetup.ts @@ -388,7 +388,7 @@ export class PythonSetupEnvironmentSetup implements Disposable { if (e instanceof PythonSetupCancelledError) { reportResult({ outcome: "cancelled", - pythonInstallFlow: "cancelled", + pythonSetupFlow: "cancelled", }); return; } @@ -423,7 +423,7 @@ export class PythonSetupEnvironmentSetup implements Disposable { const reportAction = getPythonSetupReportAction(result, reportEnv); const reportRepo = reportRepoForResult(result); const remediationActions = getPythonSetupErrorActions(result); - const pythonInstallFlow = + const pythonSetupFlow = result.error?.code === "E_PYTHON_INSTALL" || result.pythonResolution === "installed_fallback" ? "manual_selection_requested" @@ -438,7 +438,7 @@ export class PythonSetupEnvironmentSetup implements Disposable { : remediationActions; reportResult({ outcome: "failed", - ...(pythonInstallFlow !== undefined ? {pythonInstallFlow} : {}), + ...(pythonSetupFlow !== undefined ? {pythonSetupFlow} : {}), failurePhase: result.error?.failurePhase, errorCode: result.error?.code, envKey: result.compute?.envKey, @@ -484,7 +484,7 @@ export class PythonSetupEnvironmentSetup implements Disposable { reportResult({ outcome: "failed", ...(result.pythonResolution !== undefined - ? {pythonInstallFlow: result.pythonResolution} + ? {pythonSetupFlow: result.pythonResolution} : {}), failurePhase: "adopt", envKey: result.compute.envKey, @@ -522,7 +522,7 @@ export class PythonSetupEnvironmentSetup implements Disposable { reportResult({ outcome: "failed", ...(result.pythonResolution !== undefined - ? {pythonInstallFlow: result.pythonResolution} + ? {pythonSetupFlow: result.pythonResolution} : {}), failurePhase: "persist", envKey: result.compute.envKey, @@ -551,7 +551,7 @@ export class PythonSetupEnvironmentSetup implements Disposable { reportResult({ outcome: "ok", ...(result.pythonResolution !== undefined - ? {pythonInstallFlow: result.pythonResolution} + ? {pythonSetupFlow: result.pythonResolution} : {}), envKey: result.compute.envKey, warnings: result.warnings, diff --git a/packages/databricks-vscode/src/telemetry/constants.ts b/packages/databricks-vscode/src/telemetry/constants.ts index 94390df01..834cdd5f6 100644 --- a/packages/databricks-vscode/src/telemetry/constants.ts +++ b/packages/databricks-vscode/src/telemetry/constants.ts @@ -119,7 +119,7 @@ export type SetupTrigger = "auto_open" | "explicit_command" | "run" | "debug"; export type PythonSetupRunTrigger = "initial" | "rerun"; /** Categorical outcome of Python acquisition and its user recovery path. */ -export type PythonInstallFlow = +export type PythonSetupFlow = | "uv_install_succeeded" | "installed_fallback" | "manual_selection_requested" @@ -535,7 +535,7 @@ export class EventTypes { }; [Events.PYTHON_ENV_SETUP_RESULT]: EventType<{ outcome: PythonSetupOutcome; - pythonInstallFlow?: PythonInstallFlow; + pythonSetupFlow?: PythonSetupFlow; failurePhase?: PythonSetupFailurePhase; errorCode?: PythonSetupErrorCode; envKey?: string; @@ -564,7 +564,7 @@ export class EventTypes { "ok | failed | cancelled (user aborted) | not_started (the CLI produced no " + "result) | no_compute (the CTA was a dead end: nothing was attached to set up for)", }, - pythonInstallFlow: { + pythonSetupFlow: { comment: "Whether uv downloaded Python, an installed compatible Python was used, " + "manual interpreter selection was requested, or the operation was cancelled. " + diff --git a/packages/databricks-vscode/src/telemetry/pythonSetupExtensions.test.ts b/packages/databricks-vscode/src/telemetry/pythonSetupExtensions.test.ts index 4b44366a7..105d4b7f7 100644 --- a/packages/databricks-vscode/src/telemetry/pythonSetupExtensions.test.ts +++ b/packages/databricks-vscode/src/telemetry/pythonSetupExtensions.test.ts @@ -210,7 +210,7 @@ describe(__filename, () => { }); it("reports only the categorical Python install flow", () => { - for (const pythonInstallFlow of [ + for (const pythonSetupFlow of [ "uv_install_succeeded", "installed_fallback", "manual_selection_requested", @@ -222,10 +222,10 @@ describe(__filename, () => { targetType: "cluster", mode: "default", trigger: "initial", - })({outcome: "ok", pythonInstallFlow}); + })({outcome: "ok", pythonSetupFlow}); - expect(events[1].props["event.pythonInstallFlow"]).to.equal( - pythonInstallFlow + expect(events[1].props["event.pythonSetupFlow"]).to.equal( + pythonSetupFlow ); expect(JSON.stringify(events[1])).to.not.contain("/usr/bin/python"); expect(JSON.stringify(events[1])).to.not.contain("3.12"); diff --git a/packages/databricks-vscode/src/telemetry/pythonSetupExtensions.ts b/packages/databricks-vscode/src/telemetry/pythonSetupExtensions.ts index cbd6cfce0..2d1238500 100644 --- a/packages/databricks-vscode/src/telemetry/pythonSetupExtensions.ts +++ b/packages/databricks-vscode/src/telemetry/pythonSetupExtensions.ts @@ -5,7 +5,7 @@ import { PythonSetupDriftTrigger, PythonSetupErrorCode, PythonSetupFailurePhase, - PythonInstallFlow, + PythonSetupFlow, PythonSetupMode, PythonSetupOutcome, PythonSetupRunTrigger, @@ -62,7 +62,7 @@ export interface PythonSetupAdoption { /** How a setup run ended, reduced to the categorical fields we report. */ export interface PythonSetupOutcomeReport { outcome: PythonSetupOutcome; - pythonInstallFlow?: PythonInstallFlow; + pythonSetupFlow?: PythonSetupFlow; failurePhase?: PythonSetupFailurePhase; errorCode?: PythonSetupErrorCode; envKey?: string; @@ -284,8 +284,8 @@ Telemetry.prototype.recordPythonSetupAttempt = function ( reported = true; reportResult({ outcome: report.outcome, - ...(report.pythonInstallFlow !== undefined - ? {pythonInstallFlow: report.pythonInstallFlow} + ...(report.pythonSetupFlow !== undefined + ? {pythonSetupFlow: report.pythonSetupFlow} : {}), ...(report.failurePhase !== undefined ? {failurePhase: report.failurePhase}