Skip to content
Draft
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1342,7 +1342,7 @@
"altimateai.vscode-altimate-mcp-server"
],
"dependencies": {
"@altimateai/dbt-integration": "^0.3.0",
"@altimateai/dbt-integration": "^0.3.1",
"@jupyterlab/coreutils": "^6.2.4",
"@jupyterlab/nbformat": "^4.2.4",
"@jupyterlab/services": "^7.0.0",
Expand Down
88 changes: 88 additions & 0 deletions src/dbtPowerUserExtension.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { probeDbtCoreVersion } from "@altimateai/dbt-integration";
import { NotebookProviders } from "@lib";
import { commands, Disposable, ExtensionContext, workspace } from "vscode";
import { AutocompletionProviders } from "./autocompletion_provider";
Expand Down Expand Up @@ -41,6 +42,13 @@ export class DBTPowerUserExtension implements Disposable {
];

private disposables: Disposable[] = [];
/**
* Monotonic sequence counter for `refreshVersionTelemetryAttributes` so
* a slow earlier invocation can't overwrite attributes written by a
* faster later one. Each refresh captures the seq at start and only
* applies its results if the seq still matches.
*/
private versionRefreshSeq = 0;

constructor(
private dbtProjectContainer: DBTProjectContainer,
Expand Down Expand Up @@ -97,6 +105,34 @@ export class DBTPowerUserExtension implements Disposable {
this.dbtProjectContainer.setContext(context);
this.dbtProjectContainer.initializeWalkthrough();
await this.dbtProjectContainer.detectDBT();

// Enrich every telemetry event with the active Python interpreter version
// and dbt-core dist version. Without these dimensions, App Insights can't
// split error clusters by Python or dbt-core version — e.g. we can't
// tell whether a `pythonBridgeInitPythonError "mashumaro
// UnserializableField"` is a Python 3.13 + mashumaro 3.14 incompat or
// something else. Both probes are best-effort: failure → attribute is
// cleared rather than blocking activation.
//
// Primed BEFORE `initializeDBTProjects()` because that call is the
// python-bridge init step where `pythonBridgeInitPythonError` itself
// originates. The `void`-fired refresh runs synchronously up to its
// first await, so `pythonVersion` is guaranteed in `customAttributes`
// by the time `initializeDBTProjects()` starts — meaning a thrown
// error caught below is always dimensioned with at least Python
// version. (`dbtCoreVersion` arrives best-effort once the spawn
// resolves.) Re-runs on interpreter change so the dimensions track
// the user's selection. `detectDBT()` is the earliest point where
// `pythonEnvironment.executionDetails` is set and `pythonVersion`
// is populated.
void this.refreshVersionTelemetryAttributes();
const pythonEnv = this.dbtProjectContainer.getPythonEnvironment();
this.disposables.push(
pythonEnv.onPythonEnvironmentChanged(() => {
void this.refreshVersionTelemetryAttributes();
}),
Comment thread
coderabbitai[bot] marked this conversation as resolved.
);
Comment thread
coderabbitai[bot] marked this conversation as resolved.

await this.dbtProjectContainer.initializeDBTProjects();
await this.statusBars.initialize();
// Ask to reload the window if the dbt integration changes
Expand All @@ -121,4 +157,56 @@ export class DBTPowerUserExtension implements Disposable {
this.telemetry.sendTelemetryError("extensionActivationError", error);
}
}

/**
* Populate `pythonVersion` and `dbtCoreVersion` customAttributes on the
* telemetry service so every event from this point forward carries them.
* Best-effort: if either probe fails (interpreter missing, dbt-core not
* installed in this venv, probe timeout), the corresponding attribute is
* **cleared** rather than left at a stale value from the previous
* interpreter. Idempotent — wired both at activation and from the
* `onPythonEnvironmentChanged` listener.
*
* Sequence-guarded: rapid interpreter switches can fire two refreshes
* concurrently. A slower earlier probe finishing last would otherwise
* overwrite a faster later probe's results. We capture the
* `versionRefreshSeq` at entry and bail before any write if a newer
* refresh has bumped the counter.
*/
private async refreshVersionTelemetryAttributes(): Promise<void> {
const seq = ++this.versionRefreshSeq;
try {
const pythonEnv = this.dbtProjectContainer.getPythonEnvironment();
const pythonVersion = pythonEnv.pythonVersion;
if (seq !== this.versionRefreshSeq) {
return;
}
if (pythonVersion) {
this.telemetry.setTelemetryCustomAttribute(
"pythonVersion",
pythonVersion,
);
} else {
this.telemetry.clearTelemetryCustomAttribute("pythonVersion");
}
const pythonPath = pythonEnv.pythonPath;
const dbtCoreVersion = pythonPath
? await probeDbtCoreVersion(pythonPath)
: undefined;
if (seq !== this.versionRefreshSeq) {
return;
}
if (dbtCoreVersion) {
this.telemetry.setTelemetryCustomAttribute(
"dbtCoreVersion",
dbtCoreVersion,
);
} else {
this.telemetry.clearTelemetryCustomAttribute("dbtCoreVersion");
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
} catch {
// Telemetry-enrichment failures must never block activation or
// surface as an error — by design these dimensions are best-effort.
}
}
}
12 changes: 12 additions & 0 deletions src/telemetry/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@ export class TelemetryService implements vscode.Disposable {
this.customAttributes[key] = value;
}

/**
* Remove a previously-set custom attribute so it stops appearing on
* subsequent telemetry events. Use when the dimension no longer applies
* — e.g. the user switched Python interpreters and we couldn't probe a
* fresh value, so the previous interpreter's `pythonVersion` /
* `dbtCoreVersion` would otherwise carry over and corrupt dimensioning
* for events from the new interpreter.
*/
clearTelemetryCustomAttribute(key: string) {
delete this.customAttributes[key];
}

startTelemetryEvent(
eventName: string,
properties?: { [key: string]: string },
Expand Down
Loading
Loading