feat: enrich telemetry with pythonVersion and dbtCoreVersion customAttributes#1942
feat: enrich telemetry with pythonVersion and dbtCoreVersion customAttributes#1942ralphstodomingo wants to merge 1 commit intomasterfrom
Conversation
WalkthroughThis PR adds a best-effort Python/dbt-core version probe, exposes an injectable SpawnFn, wires a sequence-guarded refresh into extension activation and Python environment-change subscriptions, and adds tests plus a TelemetryService method to clear custom attributes. ChangesTelemetry Version Enrichment
Sequence Diagram(s)sequenceDiagram
participant VSCode
participant Extension
participant PythonExt
participant ProbeModule
participant Telemetry
VSCode->>Extension: activate()
Extension->>Extension: refreshVersionTelemetryAttributes()
Extension->>PythonExt: read active pythonVersion / pythonPath
alt pythonPath available
Extension->>ProbeModule: probeDbtCoreVersion(pythonPath)
ProbeModule-->>Extension: dbtCoreVersion or undefined
end
Extension->>Telemetry: setCustomAttribute / clearTelemetryCustomAttribute
PythonExt->>Extension: onPythonEnvironmentChanged
Extension->>Extension: refreshVersionTelemetryAttributes()
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsGit: Failed to clone repository. Please run the Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Bundle Size Reportdarwin-arm64: 74.2 MB
linux-x64: 75.9 MB
win32-x64: 76.8 MB
|
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/dbtPowerUserExtension.ts`:
- Around line 154-169: The telemetry attributes for interpreter info are only
set when truthy, leaving stale values when an interpreter lacks data; update the
logic in dbtPowerUserExtension.ts around pythonVersion, pythonPath and
probeDbtCoreVersion so that you always call
this.telemetry.setTelemetryCustomAttribute("pythonVersion", <value>) and
this.telemetry.setTelemetryCustomAttribute("dbtCoreVersion", <value>) — passing
the actual value when present and a clear/reset placeholder (e.g., empty string
or null) when not — and ensure probeDbtCoreVersion(pythonPath) errors are
handled so dbtCoreVersion is explicitly cleared when no value is found.
- Around line 112-117: refreshVersionTelemetryAttributes can run concurrently
and an earlier slower invocation may overwrite a later one; add a sequence-guard
(e.g., an incrementing requestId or a per-instance CancellationToken) in the
class so each invocation captures the current token/id at start and only applies
its results if the token/id still matches before writing telemetry attributes.
Update the call sites around pythonEnv.onPythonEnvironmentChanged and the
similar block at the other occurrence (the code between the earlier and later
refresh locations) to use the same guard (increment the id or generate a new
token before calling refreshVersionTelemetryAttributes and have
refreshVersionTelemetryAttributes check/compare the token/id before mutating
state).
In `@src/telemetry/versionProbes.ts`:
- Around line 72-93: The timeout scheduled with setTimeout(PROBE_TIMEOUT_MS) can
remain pending after early settlement; modify the probe logic around
settled/settle/proc to capture the timer id (e.g. const timer = setTimeout(...))
and call clearTimeout(timer) inside the settle function before killing the
process and resolve to prevent dangling timers; ensure the timeout handler still
calls settle(undefined) so behavior is unchanged and use a compatible timer type
(ReturnType<typeof setTimeout> / NodeJS.Timeout) if needed.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 5b70ac0b-2b2f-492b-a646-c131ea782f28
📒 Files selected for processing (3)
src/dbtPowerUserExtension.tssrc/telemetry/versionProbes.tssrc/test/suite/versionProbes.test.ts
2a05a6f to
a47a2bd
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/test/suite/versionTelemetryRefresh.test.ts (1)
16-20: 🏗️ Heavy liftThese tests can drift away from the real implementation.
RefreshHarnessandFakeTelemetryre-implement the production logic, so this suite can stay green even ifDBTPowerUserExtension.refreshVersionTelemetryAttributes()orTelemetryService.clearTelemetryCustomAttribute()regresses. I’d strongly prefer extracting the refresh helper and testing that directly, or adding at least one case that drives the real extension + telemetry classes through the refresh path.Also applies to: 55-105
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/test/suite/versionTelemetryRefresh.test.ts` around lines 16 - 20, The test currently re-implements production behavior via RefreshHarness and FakeTelemetry which allows regressions in DBTPowerUserExtension.refreshVersionTelemetryAttributes and TelemetryService.clearTelemetryCustomAttribute to go unnoticed; fix by extracting the refresh helper logic into a single exported function (e.g., refreshVersionTelemetryAttributesHelper) and update tests to import and exercise that helper directly, or alternately add an integration-style test that instantiates the real DBTPowerUserExtension and the real TelemetryService (or a thin spy) and asserts the same outcomes so the actual DBTPowerUserExtension.refreshVersionTelemetryAttributes and TelemetryService.clearTelemetryCustomAttribute code paths are executed; update or remove RefreshHarness/FakeTelemetry usages to ensure at least one test drives the real implementation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/dbtPowerUserExtension.ts`:
- Around line 107-125: Move the initial telemetry priming so version attributes
are set before any init that can emit telemetry: call
this.refreshVersionTelemetryAttributes() immediately after await
this.dbtProjectContainer.detectDBT() (i.e., before await
this.dbtProjectContainer.initializeDBTProjects() and before await
this.statusBars.initialize()), and keep the existing
pythonEnv.onPythonEnvironmentChanged subscription so subsequent changes still
re-run refreshVersionTelemetryAttributes(); alternatively, if a synchronous
"prime only python version" helper is preferred, add and call a small
synchronous priming method (e.g., primePythonVersionTelemetry()) before
initializeDBTProjects(), then leave refreshVersionTelemetryAttributes() where it
is for the full async refresh.
---
Nitpick comments:
In `@src/test/suite/versionTelemetryRefresh.test.ts`:
- Around line 16-20: The test currently re-implements production behavior via
RefreshHarness and FakeTelemetry which allows regressions in
DBTPowerUserExtension.refreshVersionTelemetryAttributes and
TelemetryService.clearTelemetryCustomAttribute to go unnoticed; fix by
extracting the refresh helper logic into a single exported function (e.g.,
refreshVersionTelemetryAttributesHelper) and update tests to import and exercise
that helper directly, or alternately add an integration-style test that
instantiates the real DBTPowerUserExtension and the real TelemetryService (or a
thin spy) and asserts the same outcomes so the actual
DBTPowerUserExtension.refreshVersionTelemetryAttributes and
TelemetryService.clearTelemetryCustomAttribute code paths are executed; update
or remove RefreshHarness/FakeTelemetry usages to ensure at least one test drives
the real implementation.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 122543c8-027a-4375-845b-d613d82b5098
📒 Files selected for processing (5)
src/dbtPowerUserExtension.tssrc/telemetry/index.tssrc/telemetry/versionProbes.tssrc/test/suite/versionProbes.test.tssrc/test/suite/versionTelemetryRefresh.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- src/test/suite/versionProbes.test.ts
- src/telemetry/versionProbes.ts
a47a2bd to
66711da
Compare
✅ Tests — All Passed |
mdesmet
left a comment
There was a problem hiding this comment.
This should not be implemented here. this should be in dbt integration.
…tributes
Production telemetry currently carries `common.os` / `common.nodeArch` /
`common.extversion` etc. on every event but **not** the user's Python
interpreter version or installed dbt-core distribution version. That's a
real blindspot — App Insights can't split error clusters by Python
version (e.g. is `pythonBridgeInitPythonError "mashumaro
UnserializableField"` a Python 3.13 + mashumaro 3.14 incompat or
something else?) and we can't tell whether `catalogPythonError "missing
used_schemas"` correlates with a specific dbt-core minor.
Adds two customAttributes that get merged into every event going
forward via `TelemetryService.setTelemetryCustomAttribute`:
- `pythonVersion` — sourced from `PythonEnvironment.pythonVersion`
(already populated via the VS Code Python extension's API on
interpreter activation, no additional probe needed).
- `dbtCoreVersion` — sourced from a small `child_process.spawn` probe
in `src/telemetry/versionProbes.ts` that runs
`python -c "from importlib.metadata import version; print(version('dbt-core'))"`.
Reads dist-info directly so it survives even when dbt's own import
chain is broken (the failure mode PR #96 targeted) — the bridge can
be dead and we still get the version.
Both are best-effort: probe failure → no customAttribute set rather
than blocking activation. Wired into `DBTPowerUserExtension.activate`
after `initializeDBTProjects` (when the Python interpreter is known)
plus a refresh on `pythonEnvironment.onPythonEnvironmentChanged` so the
dimensions track interpreter changes.
`probeDbtCoreVersion` accepts an injectable `SpawnFn` parameter
defaulted to `child_process.spawn`. `import * as childProcess` produces
ESM-style bindings that resist `jest.spyOn` / `jest.mock` redefinition
under ts-jest, so the tests pass their own spawner directly. Adds 6
unit tests covering: success, empty stdout (PackageNotFoundError),
non-zero exit, error event, sync throw, and timeout.
After this ships and rolls out, App Insights queries can split by
`customDimensions.pythonVersion == "3.13.0"` or
`customDimensions.dbtCoreVersion startswith "1.10"` on every event,
not just the wrapper-failure events. Dashboards become diagnostic by
construction without per-event injection.
Tests: 33 suites / 486 passed (32 prior + 1 new).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
66711da to
f735717
Compare
Summary
Production telemetry currently carries
common.os/common.nodeArch/common.extversionetc. on every event, but not the user's Python interpreter version or installed dbt-core distribution version. That's a real blindspot — App Insights can't split error clusters by Python version (e.g. ispythonBridgeInitPythonError "mashumaro UnserializableField"a Python 3.13 + mashumaro 3.14 incompat or something else?), and we can't tell whethercatalogPythonError "missing used_schemas"correlates with a specific dbt-core minor.This PR adds two
customAttributesthat get merged into every event going forward.What's added
pythonVersionPythonEnvironment.pythonVersion(already populated via the VS Code Python extension's API on interpreter activation)dbtCoreVersionprobeDbtCoreVersion(pythonPath)from@altimateai/dbt-integration— spawnspython -c "from importlib.metadata import version; print(version('dbt-core'))"importlib.metadata.versionreads dist-info directly so the probe survives even when dbt's own import chain is broken — exactly the failure mode PR #96 targeted. The bridge can be dead and we still get the version.Both are best-effort — if either probe fails (interpreter missing, dbt-core not installed, probe times out), the corresponding customAttribute is cleared rather than left at a stale value from the previous interpreter, and activation is never blocked.
Wiring
The refresh helper sets
pythonVersionsynchronously from the already-populatedPythonEnvironment.pythonVersion, then awaitsprobeDbtCoreVersionfor the dbt-core probe.Why this priming order matters
initializeDBTProjects()is the python-bridge init step wherepythonBridgeInitPythonErroritself originates — exactly the failure mode this PR exists to dimension. If the refresh started after that line, an init-throw caught byextensionActivationErrorinactivate()'scatchwould emit withoutpythonVersion/dbtCoreVersion, defeating the PR's stated purpose.Because
void-fired async functions execute synchronously up to their firstawait(theprobeDbtCoreVersioncall),pythonVersionis guaranteed incustomAttributesBEFOREinitializeDBTProjects()synchronously starts. So a throw caught by the activate handler is dimensioned withpythonVersiondeterministically;dbtCoreVersionarrives best-effort once the spawn resolves.detectDBT()is the earliest point wherePythonEnvironment.executionDetailsis set and_pythonVersionis populated — running the refresh before it would no-op.Refresh contract
versionRefreshSeqis captured at entry; both the synchronouspythonVersionwrite and the post-awaitdbtCoreVersionwrite are gated byseq !== this.versionRefreshSeq. A slower earlier probe finishing last cannot overwrite a faster later probe's results.TelemetryService.clearTelemetryCustomAttribute(key)rather than left at a stale value from the previous interpreter.Test plan
npx tsc --noEmit— passes (validated against locally-linked@altimateai/dbt-integrationfrom the upstream PR's worktree)npx jest— 33 suites / 486 passed / 1 skippedversionTelemetryRefresh.test.ts) cover:pythonVersion→ cleared; new venv without dbt-core → onlydbtCoreVersioncleared; no python interpreter at all → both cleared.altimate-dbt-integrationPR 'dbtPowerUser.showCompiledSQL' not found #100.What this enables
After this ships and rolls out, App Insights queries like:
— work for every event from the rollout forward, not just the wrapper-failure ones. Lets us split all dashboards by Python or dbt-core version without per-event injection.
Follow-up
Could similarly add
dbtAdaptersVersion(relevant for thecatalogPythonErrorcluster from dbt-adapters 1.4+ signature change). Left out of this PR to keep scope tight; same pattern would apply.🤖 Generated with Claude Code
Summary by CodeRabbit
Chores
Tests