Skip to content

feat(telemetry): add PostHog analytics for notebook usage events#375

Draft
tkislan wants to merge 13 commits into
mainfrom
feat/posthog-telemetry
Draft

feat(telemetry): add PostHog analytics for notebook usage events#375
tkislan wants to merge 13 commits into
mainfrom
feat/posthog-telemetry

Conversation

@tkislan

@tkislan tkislan commented Mar 29, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds a PostHogAnalyticsService (Node.js only) that lazily initializes on first event, persists an anonymous user ID in globalState, and respects both VSCode's telemetry.telemetryLevel and the new deepnote.telemetry.enabled setting (default: true)
  • Instruments 20 events across block additions, cell executions, environment/integration operations, and project/notebook lifecycle
  • Includes unit tests for the analytics service and updates existing tests for the new constructor parameters

Events Tracked

Event Properties Source
add_block blockType deepnoteNotebookCommandListener, notebookCommandListener
execute_cell cellType, integrationType? (SQL) deepnoteCellExecutionAnalytics
execute_notebook notebookCommandListener
select_environment deepnoteEnvironmentsView
create_environment hasPackages, hasDescription deepnoteEnvironmentsView
delete_environment deepnoteEnvironmentsView
create_project deepnoteExplorerView
create_notebook deepnoteExplorerView
open_notebook deepnoteExplorerView
import_notebook deepnoteExplorerView
export_notebook format deepnoteExplorerView
delete_project deepnoteExplorerView
delete_notebook deepnoteExplorerView
duplicate_notebook deepnoteExplorerView
configure_integration integrationWebview
save_integration integrationType integrationWebview
delete_integration integrationWebview
reset_integration integrationWebview
toggle_snapshots enabled deepnoteNotebookCommandListener
open_in_deepnote openInDeepnoteHandler

Test plan

  • npm run compile-tsc — no type errors
  • npm run format-fix — passes
  • npm run test:unittests — 2211 passing, 0 failures
  • Manual: verify events appear in PostHog dashboard (requires API key)
  • Manual: disable deepnote.telemetry.enabled → no events sent
  • Manual: set VSCode telemetry.telemetryLevel: off → no events sent

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added optional anonymous usage telemetry (enabled by default) via deepnote.telemetry.enabled, surfaced through the VS Code settings UI.
    • Expanded telemetry capture across notebook actions, environment workflows, cell execution, SQL integration switching, snapshot toggling, file splitting, and integration webview operations.
  • Chores
    • Introduced a centralized telemetry pipeline with web no-op behavior and test support for consistent event handling.
  • Bug Fixes
    • Improved end-to-end reliability for project rename by making dirty-cell focusing more resilient to re-rendering.
  • Tests
    • Updated unit tests to account for telemetry injection and validate enable/disable and anonymous user id handling.

…ents

Introduces an opt-out PostHog telemetry service that tracks user interactions
with Deepnote notebooks, including block additions, cell executions, environment
operations, integration management, and project/notebook lifecycle events.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Mar 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 588501d6-d2f4-440d-ab18-33b21621d6db

📥 Commits

Reviewing files that changed from the base of the PR and between 8c04f54 and fbd4a65.

📒 Files selected for processing (7)
  • .github/workflows/cd.yml
  • build/esbuild/build.ts
  • package.json
  • package.nls.json
  • src/notebooks/deepnote/deepnoteNotebookCommandListener.ts
  • src/notebooks/deepnote/integrations/integrationWebview.ts
  • src/platform/analytics/constants.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • package.json
  • src/platform/analytics/constants.ts
  • src/notebooks/deepnote/deepnoteNotebookCommandListener.ts
  • src/notebooks/deepnote/integrations/integrationWebview.ts

📝 Walkthrough

Walkthrough

Adds configurable PostHog telemetry with persisted anonymous identifiers and runtime configuration handling. Telemetry is injected through service registries and recorded for Deepnote environment, explorer, integration, notebook command, cell execution, SQL integration, notebook splitting, and open-in-Deepnote actions. Unit-test construction sites provide telemetry mocks, and an end-to-end editor-focus interaction retries stale clicks.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant DeepnoteComponent
    participant TelemetryService
    participant PersistentState
    participant PostHog

    User->>DeepnoteComponent: Perform Deepnote action
    DeepnoteComponent->>TelemetryService: trackEvent(eventName, properties)
    TelemetryService->>PersistentState: Read or create anonymous user id
    TelemetryService->>PostHog: Capture event with distinctId
Loading

Possibly related PRs

Suggested reviewers: dinohamzic

🚥 Pre-merge checks | ✅ 4 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Updates Docs ⚠️ Warning PR changes code/tests/package metadata only; no README/spec/docs files were updated in the OSS repo. Add/update the relevant OSS docs, and also update the roadmap in the private landing-page repo if applicable.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding PostHog telemetry for notebook usage events.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Mar 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 0%. Comparing base (652de06) to head (fbd4a65).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@     Coverage Diff     @@
##   main   #375   +/-   ##
===========================
===========================
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/notebooks/deepnote/deepnoteExplorerView.ts (1)

337-430: ⚠️ Potential issue | 🟠 Major

Emit completion events after successful actions, not before command execution.

Current placement records delete_*, export_notebook, etc. even when user cancels or operation fails, skewing analytics.

As per coding guidelines, "Extract duplicate logic into helper methods to prevent drift following DRY principle".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/notebooks/deepnote/deepnoteExplorerView.ts` around lines 337 - 430, The
analytics.trackEvent calls are being fired before command handlers run (e.g., in
the commands.registerCommand calls that reference this.analytics?.trackEvent for
events like 'delete_notebook', 'export_notebook', 'duplicate_notebook',
'create_project', etc.), which records events even if the user cancels or the
operation fails; change the pattern so events are emitted only after the command
promise resolves successfully by moving the this.analytics?.trackEvent
invocation into the success path of the handler (e.g., after awaiting
deleteNotebook(treeItem), exportProject(treeItem), exportNotebook(treeItem),
duplicateNotebook(treeItem), newProject(), newNotebook(), importNotebook(),
importJupyterNotebook(), etc.), and to follow DRY extract a helper like
registerTrackedCommand(commandName, eventName, handler) or a small wrapper that
calls the handler, awaits its result, and then calls this.analytics?.trackEvent
on success so all command registrations (the uses of commands.registerCommand)
can use that helper consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@package.json`:
- Around line 1653-1658: Replace the hard-coded description string for the
extension setting "deepnote.telemetry.enabled" with a localization token (e.g.
"%deepnote.configuration.deepnote.telemetry.enabled.description%") in the
manifest and add the matching entry
"deepnote.configuration.deepnote.telemetry.enabled.description" to
package.nls.json with the original English text so the setting description is
localized.

In `@src/extension.node.ts`:
- Around line 137-141: The telemetry shutdown is currently invoked without
awaiting and may be lost; update the extension deactivation flow so that when
activatedServiceContainer.tryGet<IPostHogAnalyticsService>(IPostHogAnalyticsService)
returns an analytics instance you await analytics.shutdown() (i.e., call await
analytics.shutdown()) and perform that awaited shutdown before you dispose the
async registry/dispose logic so shutdown completes prior to process teardown;
ensure the deactivation function is marked async if needed and that
analytics.shutdown() is awaited in the deactivation sequence.

In `@src/notebooks/deepnote/integrations/integrationWebview.ts`:
- Line 439: The analytics events for 'configure_integration',
'reset_integration', and 'delete_integration' are missing the integrationType
property that 'save_integration' includes; update each analytics?.trackEvent
call (for the events named 'configure_integration', 'reset_integration', and
'delete_integration') to pass the same integrationType payload used by
'save_integration' (e.g., integrationType: this.integration?.type or the same
integration variable/property used in save_integration) so all integration
actions send consistent event properties.

In `@src/notebooks/deepnote/openInDeepnoteHandler.node.ts`:
- Around line 24-27: The current command handler calls
this.analytics?.trackEvent('open_in_deepnote') before the operation completes,
so move the tracking into handleOpenInDeepnote and only call it after
env.openExternal succeeds; remove the pre-call from the Commands.OpenInDeepnote
registration and update handleOpenInDeepnote to invoke
this.analytics?.trackEvent('open_in_deepnote') immediately after the successful
await env.openExternal(...) (and do not call it in the error/throw path).

In `@src/platform/analytics/constants.ts`:
- Around line 1-2: Add a guard that prevents PostHog from initializing when the
API key is the placeholder; define a boolean like IS_POSTHOG_CONFIGURED
(computed from POSTHOG_API_KEY !== '__POSTHOG_API_KEY__' and non-empty) and
update posthogAnalyticsService.initialize() to early-return if
!IS_POSTHOG_CONFIGURED so PostHog is not initialized with the placeholder key
(reference POSTHOG_API_KEY, POSTHOG_HOST, and the initialize() method in
posthogAnalyticsService.ts).

In `@src/platform/analytics/posthogAnalyticsService.ts`:
- Around line 23-53: The two public methods in PostHogAnalyticsService are out
of alphabetical order; move the public async shutdown() method so it appears
before the public trackEvent(...) method while keeping both method bodies,
try/catch blocks and behavior (references to this.client, this.userIdState,
this.initialized, etc.) unchanged; ensure method accessibility remains public
and no other methods' order is altered.
- Around line 55-58: The initialize() method sets this.initialized = true before
performing setup (e.g., this.userIdState =
this.stateFactory.createGlobalPersistentState(...)), so if state creation throws
later the service will remain marked initialized and drop events; move the
this.initialized = true assignment to after all setup steps succeed and wrap the
setup (stateFactory.createGlobalPersistentState and any client creation) in a
try/catch so that on error you do not set initialized (and optionally log or
rethrow) — update initialize(), userIdState assignment, and any client creation
code paths to ensure initialized is only true after successful completion.
- Around line 71-79: The isTelemetryEnabled function currently permits telemetry
for telemetryLevel values other than 'off'; update isTelemetryEnabled to only
allow telemetry when
workspace.getConfiguration('telemetry').get<string>('telemetryLevel') === 'all'
and the deepnote setting
workspace.getConfiguration('deepnote').get<boolean>('telemetry.enabled', true)
is true; modify the logic in isTelemetryEnabled to return false unless
telemetryLevel === 'all', then read and return the deepnote.telemetry.enabled
flag (use the existing function name isTelemetryEnabled and the same
configuration keys).
- Around line 64-68: POSTHOG_API_KEY is left as the placeholder
'__POSTHOG_API_KEY__' and is passed into the PostHog constructor (this.client =
new PostHog(...)), so telemetry silently fails; fix by either configuring your
build (esbuild define) to replace POSTHOG_API_KEY at build-time or add a runtime
guard in the PostHog initialization path: check POSTHOG_API_KEY (e.g. equals
'__POSTHOG_API_KEY__' or startsWith('__')) before calling new PostHog, and if
invalid skip creating this.client and emit a clear warning/error via your logger
instead.

---

Outside diff comments:
In `@src/notebooks/deepnote/deepnoteExplorerView.ts`:
- Around line 337-430: The analytics.trackEvent calls are being fired before
command handlers run (e.g., in the commands.registerCommand calls that reference
this.analytics?.trackEvent for events like 'delete_notebook', 'export_notebook',
'duplicate_notebook', 'create_project', etc.), which records events even if the
user cancels or the operation fails; change the pattern so events are emitted
only after the command promise resolves successfully by moving the
this.analytics?.trackEvent invocation into the success path of the handler
(e.g., after awaiting deleteNotebook(treeItem), exportProject(treeItem),
exportNotebook(treeItem), duplicateNotebook(treeItem), newProject(),
newNotebook(), importNotebook(), importJupyterNotebook(), etc.), and to follow
DRY extract a helper like registerTrackedCommand(commandName, eventName,
handler) or a small wrapper that calls the handler, awaits its result, and then
calls this.analytics?.trackEvent on success so all command registrations (the
uses of commands.registerCommand) can use that helper consistently.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 0b27f28f-a694-4b50-86ed-512bcc176642

📥 Commits

Reviewing files that changed from the base of the PR and between 0ac38a0 and e89b6b6.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (19)
  • package.json
  • src/extension.node.ts
  • src/kernels/deepnote/environments/deepnoteEnvironmentsView.node.ts
  • src/kernels/deepnote/environments/deepnoteEnvironmentsView.unit.test.ts
  • src/notebooks/deepnote/deepnoteActivationService.ts
  • src/notebooks/deepnote/deepnoteCellExecutionAnalytics.ts
  • src/notebooks/deepnote/deepnoteExplorerView.ts
  • src/notebooks/deepnote/deepnoteNotebookCommandListener.ts
  • src/notebooks/deepnote/deepnoteNotebookCommandListener.unit.test.ts
  • src/notebooks/deepnote/integrations/integrationWebview.ts
  • src/notebooks/deepnote/openInDeepnoteHandler.node.ts
  • src/notebooks/deepnote/openInDeepnoteHandler.node.unit.test.ts
  • src/notebooks/notebookCommandListener.ts
  • src/notebooks/serviceRegistry.node.ts
  • src/platform/analytics/constants.ts
  • src/platform/analytics/posthogAnalyticsService.ts
  • src/platform/analytics/posthogAnalyticsService.unit.test.ts
  • src/platform/analytics/types.ts
  • src/platform/serviceRegistry.node.ts

Comment thread package.json
Comment thread src/extension.node.ts Outdated
Comment thread src/notebooks/deepnote/integrations/integrationWebview.ts Outdated
Comment thread src/notebooks/deepnote/openInDeepnoteHandler.node.ts Outdated
Comment thread src/platform/analytics/constants.ts Outdated
Comment on lines +1 to +2
export const POSTHOG_API_KEY = '__POSTHOG_API_KEY__';
export const POSTHOG_HOST = 'https://us.i.posthog.com';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "1) Placeholder occurrences:"
rg -n "__POSTHOG_API_KEY__" src build package.json

echo
echo "2) Build-time replacement/define hooks for PostHog key:"
rg -n "POSTHOG_API_KEY|__POSTHOG_API_KEY__|define|replace" build package.json

echo
echo "3) Runtime initialization sites:"
rg -n "new PostHog\\(|POSTHOG_API_KEY|initializePostHog|initialize\\(" src/platform/analytics

Repository: deepnote/vscode-deepnote

Length of output: 5327


🏁 Script executed:

cat -n build/esbuild/build.ts | head -280 | tail -80

Repository: deepnote/vscode-deepnote

Length of output: 4141


🏁 Script executed:

rg -A 5 "define\s*=" build/esbuild/build.ts | head -50

Repository: deepnote/vscode-deepnote

Length of output: 217


🏁 Script executed:

grep -r "POSTHOG\|posthog\|telemetry.*key\|analytics.*key" build/ package.json --include="*.ts" --include="*.js" --include="*.json" 2>/dev/null | grep -v node_modules | head -20

Repository: deepnote/vscode-deepnote

Length of output: 115


🏁 Script executed:

cat -n src/platform/analytics/posthogAnalyticsService.ts | head -80

Repository: deepnote/vscode-deepnote

Length of output: 3054


🏁 Script executed:

grep -r "process.env\|POSTHOG" src/platform/analytics/ -A 2 -B 2

Repository: deepnote/vscode-deepnote

Length of output: 1275


🏁 Script executed:

rg -r "POSTHOG_API_KEY|__POSTHOG_API_KEY__" . --type json --type js --type ts -g "!node_modules" -g "!.git" | grep -v "src/platform/analytics"

Repository: deepnote/vscode-deepnote

Length of output: 50445


🏁 Script executed:

grep -r "process.env" build/esbuild/ --include="*.ts" --include="*.js" | grep -i "api\|key\|secret\|token" | head -10

Repository: deepnote/vscode-deepnote

Length of output: 50


Add guard to prevent PostHog initialization with placeholder API key.

The placeholder __POSTHOG_API_KEY__ has no build-time replacement and ships unchanged. PostHog initializes with an invalid key, silently dropping telemetry. Add a configuration flag to skip initialization when the key is unset:

Suggested fix
 export const POSTHOG_API_KEY = '__POSTHOG_API_KEY__';
 export const POSTHOG_HOST = 'https://us.i.posthog.com';
+export const IS_POSTHOG_CONFIGURED = POSTHOG_API_KEY !== '__POSTHOG_API_KEY__';
// In initialize() method of posthogAnalyticsService.ts
private initialize(): void {
    if (!IS_POSTHOG_CONFIGURED) {
        return;
    }
    // ... rest of initialization
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const POSTHOG_API_KEY = '__POSTHOG_API_KEY__';
export const POSTHOG_HOST = 'https://us.i.posthog.com';
export const POSTHOG_API_KEY = '__POSTHOG_API_KEY__';
export const POSTHOG_HOST = 'https://us.i.posthog.com';
export const IS_POSTHOG_CONFIGURED = POSTHOG_API_KEY !== '__POSTHOG_API_KEY__';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/platform/analytics/constants.ts` around lines 1 - 2, Add a guard that
prevents PostHog from initializing when the API key is the placeholder; define a
boolean like IS_POSTHOG_CONFIGURED (computed from POSTHOG_API_KEY !==
'__POSTHOG_API_KEY__' and non-empty) and update
posthogAnalyticsService.initialize() to early-return if !IS_POSTHOG_CONFIGURED
so PostHog is not initialized with the placeholder key (reference
POSTHOG_API_KEY, POSTHOG_HOST, and the initialize() method in
posthogAnalyticsService.ts).

Comment thread src/platform/analytics/telemetryService.ts Outdated
Comment thread src/platform/analytics/telemetryService.ts Outdated
Comment on lines +64 to +68
this.client = new PostHog(POSTHOG_API_KEY, {
flushAt: 20,
flushInterval: 30000,
host: POSTHOG_HOST
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Locate PostHog API key placeholder usage =="
rg -n -C3 '__POSTHOG_API_KEY__|POSTHOG_API_KEY' --type ts --type js --type json

echo "== Look for build-time define/replace configuration =="
rg -n -C3 'DefinePlugin|define:|replace\\(|esbuild|webpack|rollup|vite|__POSTHOG_API_KEY__' --type ts --type js --type json

Repository: deepnote/vscode-deepnote

Length of output: 1825


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Check for build configuration files =="
find . -maxdepth 2 -type f \( -name "webpack*.js" -o -name "vite*.config.*" -o -name "esbuild*.js" -o -name "rollup*.js" -o -name "tsconfig*.json" \) | head -20

echo "== Search for DefinePlugin or define configuration (simpler pattern) =="
rg -n 'DefinePlugin|define' --type ts --type js | head -30

echo "== Check package.json for build scripts =="
rg -n 'scripts|build' --type json | grep -E 'package.json|build' | head -20

echo "== Verify if API key has validation before PostHog initialization =="
rg -n -B5 -A5 'new PostHog' --type ts

echo "== Check for environment variable usage =="
rg -n 'process.env|process\.env\.POSTHOG|import\.meta' --type ts --type js | grep -i posthog | head -20

Repository: deepnote/vscode-deepnote

Length of output: 6390


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Read esbuild build configuration =="
cat build/esbuild/build.ts | head -100

echo "== Search for any POSTHOG or API key replacement in build files =="
rg -n 'POSTHOG|apiKey|api.key|secret' build/ --type ts --type js

echo "== Full PostHog service initialization context =="
cat -n src/platform/analytics/posthogAnalyticsService.ts | head -80

echo "== Check if there's error handling for invalid API keys =="
rg -n -A10 -B2 'new PostHog' src/platform/analytics/posthogAnalyticsService.ts

Repository: deepnote/vscode-deepnote

Length of output: 3863


🏁 Script executed:

#!/bin/bash
set -euo pipefmt

echo "== Search for 'define' in esbuild build config =="
grep -n "define" build/esbuild/build.ts | head -20

echo "== Check for environment variables or process.env in build config =="
grep -n "process.env\|process\.env" build/esbuild/build.ts | head -20

echo "== Search for POSTHOG in entire codebase =="
rg -n 'POSTHOG' --type ts --type js

echo "== Check what happens when PostHog client is created with invalid key =="
cat -n src/platform/analytics/posthogAnalyticsService.ts | sed -n '50,75p'

echo "== Check PostHog documentation or package.json version =="
grep -n "posthog" package.json

Repository: deepnote/vscode-deepnote

Length of output: 120


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Search for 'define' in esbuild build config =="
grep -n "define" build/esbuild/build.ts | head -20

echo "== Check for environment variables or process.env in build config =="
grep -n "process.env" build/esbuild/build.ts | head -20

echo "== Search for POSTHOG in entire codebase =="
rg -n 'POSTHOG' --type ts --type js

echo "== Check what happens when PostHog client is created with invalid key =="
cat -n src/platform/analytics/posthogAnalyticsService.ts | sed -n '50,75p'

echo "== Check PostHog package version =="
grep -n "posthog" package.json

Repository: deepnote/vscode-deepnote

Length of output: 2310


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Check full PostHog service to see if API key is validated =="
cat -n src/platform/analytics/posthogAnalyticsService.ts

echo "== Check how PostHog service is used - maybe validated at usage time =="
rg -n 'PostHogAnalyticsService\|isTelemetryEnabled' src/ --type ts | head -30

echo "== Verify if __POSTHOG_API_KEY__ placeholder is mentioned anywhere else =="
rg -n '__POSTHOG_API_KEY__' --type ts --type js --type json

Repository: deepnote/vscode-deepnote

Length of output: 3190


Add build-time replacement for PostHog API key placeholder or validate at runtime.

The POSTHOG_API_KEY constant is hardcoded as '__POSTHOG_API_KEY__' with no build-time replacement configured in esbuild. The PostHog client initializes with this invalid placeholder (line 64), causing telemetry to silently fail without error reporting.

Either replace the placeholder during build using esbuild's define option, or validate the API key at runtime and skip initialization with a logged warning when it remains a placeholder.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/platform/analytics/posthogAnalyticsService.ts` around lines 64 - 68,
POSTHOG_API_KEY is left as the placeholder '__POSTHOG_API_KEY__' and is passed
into the PostHog constructor (this.client = new PostHog(...)), so telemetry
silently fails; fix by either configuring your build (esbuild define) to replace
POSTHOG_API_KEY at build-time or add a runtime guard in the PostHog
initialization path: check POSTHOG_API_KEY (e.g. equals '__POSTHOG_API_KEY__' or
startsWith('__')) before calling new PostHog, and if invalid skip creating
this.client and emit a clear warning/error via your logger instead.

Comment thread src/platform/analytics/telemetryService.ts
tkislan and others added 2 commits March 29, 2026 21:17
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Removes PostHog-specific naming from the interface and implementation
to keep the telemetry abstraction provider-agnostic.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

♻️ Duplicate comments (1)
src/extension.node.ts (1)

132-140: ⚠️ Potential issue | 🟠 Major

Await telemetry shutdown before disposing registry.

This remains unresolved: deactivate() disposes immediately and can still drop final telemetry. At minimum, ensure shutdown is awaited before registry.dispose() (same concern as earlier review around this block). See Line 132 onward.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/extension.node.ts` around lines 132 - 140, deactivate() currently sets
Exiting.isExiting and immediately returns registry.dispose(), which can abort
final telemetry; modify deactivate to first await the telemetry shutdown routine
(call the telemetry shutdown/flush method used in your codebase) before calling
registry.dispose(), ensuring you still handle the case where
activatedServiceContainer or registry is missing; specifically update the
deactivate function (symbols: deactivate, Exiting.isExiting,
activatedServiceContainer, IAsyncDisposableRegistry, registry.dispose) to await
telemetry shutdown/flush and then await/return registry.dispose().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/notebooks/deepnote/deepnoteCellExecutionAnalytics.ts`:
- Around line 15-19: Constructor parameter order violates alphabetical coding
guidelines; reorder the constructor parameters so they appear as analytics,
disposables, notebookManager (keeping their decorators and types:
`@inject`(ITelemetryService) `@optional`() analytics: ITelemetryService | undefined,
`@inject`(IDisposableRegistry) disposables: Disposable[],
`@inject`(IDeepnoteNotebookManager) notebookManager: IDeepnoteNotebookManager) to
match required alphabetical ordering for the constructor signature and the
corresponding private readonly fields.
- Around line 10-11: Update the top-file JSDoc that currently references PostHog
to a provider-agnostic description mentioning TelemetryService; locate the
comment in src/notebooks/deepnote/deepnoteCellExecutionAnalytics.ts (the header
above functions like any exported tracker or init function in this module) and
replace "Tracks cell execution events for PostHog analytics" with a neutral line
such as "Tracks cell execution events for telemetry via TelemetryService" so it
aligns with the renamed types and doesn't reference PostHog specifically.
- Line 59: Remove the redundant optional chaining on analytics: replace the call
using this.analytics?.trackEvent('execute_cell', properties) with a direct
this.analytics.trackEvent('execute_cell', properties) because analytics is
already guaranteed non-null by the earlier early-return in the
DeepnoteCellExecutionAnalytics flow; update the invocation in the same spot to
remove the question mark.

In `@src/notebooks/deepnote/deepnoteExplorerView.ts`:
- Around line 387-390: The telemetry is fired unconditionally after awaiting the
operations even when the user cancels; change the operations deleteProject,
deleteNotebook, and duplicateNotebook to return a boolean (true if the action
completed, false if cancelled) and then in each command handler (e.g. the
Commands.DeleteProject/Commands.DeleteNotebook/Commands.DuplicateNotebook
handlers that call
this.deleteProject/this.deleteNotebook/this.duplicateNotebook) only call
this.analytics?.trackEvent(...) when the returned boolean is true; update the
handlers to await the method, check the result, and skip trackEvent on false so
telemetry only fires on completed actions.
- Around line 420-423: The telemetry is being sent even when users cancel export
flows because exportProject/exportNotebook return early on cancellations; update
the call sites (the Commands.ExportProject and Commands.ExportNotebook handlers)
to only call this.analytics?.trackEvent after confirming the export actually
completed (e.g., have exportProject/exportNotebook return a success boolean or
resolved value indicating completion and check it), or alternatively move the
analytics tracking into exportProject/exportNotebook to execute only on
successful completion; ensure the early returns for format/folder/overwrite
cancellation do not trigger trackEvent.
- Around line 352-355: Telemetry is fired unconditionally after awaiting the
command handlers (e.g., the registerCommand callbacks that call newProject(),
importProject(), openFromDeepnote(), openFromFile()), which causes events to be
tracked even when the operation early-returns (no workspace or user cancelled).
Fix by making each handler only emit analytics?.trackEvent when the operation
actually succeeded: either (A) have the underlying methods
(newProject/importProject/openFromDeepnote/openFromFile) return a boolean/result
indicating success and conditionally call analytics?.trackEvent based on that
result, or (B) move the tracking into those methods at the point they complete
successfully; update the registerCommand callbacks to check the returned success
value (or rely on the internal tracking) before calling analytics?.trackEvent.
- Around line 29-30: Reorder the constructor parameters so that analytics
(ITelemetryService) appears before logger (ILogger) to match the alphabetical
ordering rule: change the parameter list in the DeepnoteExplorerView constructor
so "analytics?: ITelemetryService" comes before "@inject(ILogger) logger:
ILogger"; optionally add a matching `@inject` decorator for analytics if you want
consistent injection style, but the primary fix is to swap the parameter order
to keep accessibility/alphabetical ordering for properties.

In `@src/platform/analytics/telemetryService.ts`:
- Around line 13-26: Reorder the private fields inside the TelemetryService
class so they follow the accessibility-then-alphabetical guideline: place
private client, then private initialized, then private userIdState (i.e., ensure
the fields named client, initialized, and userIdState in class TelemetryService
are declared in that alphabetical order under the private accessibility level).

In `@src/platform/analytics/telemetryService.unit.test.ts`:
- Around line 104-127: The test currently checks state creation and user ID
persistence but doesn't assert the PostHog client actually recorded the event;
update the test for TelemetryService.trackEvent to also assert that the mocked
PostHog client's capture method was called (e.g., that mockPosthogClient.capture
was calledOnce) and that its first call includes the event name 'test_event' and
the distinct_id equals the generatedId from mockUserIdState.updateValue; if the
PostHog client isn't already mocked in this test, stub or inject a
mockPosthogClient with a Sinon stub for capture before calling
analyticsService.trackEvent so you can assert on its calls.
- Around line 148-152: The test uses assert.isFulfilled() but chai-as-promised
isn't registered; import and register the plugin in the test suite setup so
promise assertions work: add an import for chai-as-promised and call
use(chaiAsPromised) in the suite setup (same pattern used in other tests) so
assert.isFulfilled(analyticsService.dispose()) succeeds; update
telemetryService.unit.test.ts where TelemetryService and assert.isFulfilled are
used.

In `@src/platform/serviceRegistry.node.ts`:
- Around line 9-10: The two imports ITelemetryService and TelemetryService are
placed separately; move the lines "import { ITelemetryService } from
'./analytics/types';" and "import { TelemetryService } from
'./analytics/telemetryService';" so they are grouped with the other relative
'./' imports (i.e., alongside the imports that include registerTypes) to keep
import ordering consistent; ensure you preserve existing import semantics and
update ordering only.

---

Duplicate comments:
In `@src/extension.node.ts`:
- Around line 132-140: deactivate() currently sets Exiting.isExiting and
immediately returns registry.dispose(), which can abort final telemetry; modify
deactivate to first await the telemetry shutdown routine (call the telemetry
shutdown/flush method used in your codebase) before calling registry.dispose(),
ensuring you still handle the case where activatedServiceContainer or registry
is missing; specifically update the deactivate function (symbols: deactivate,
Exiting.isExiting, activatedServiceContainer, IAsyncDisposableRegistry,
registry.dispose) to await telemetry shutdown/flush and then await/return
registry.dispose().
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 15c38932-a92a-42ce-a197-15e34e626d84

📥 Commits

Reviewing files that changed from the base of the PR and between e89b6b6 and 01b41d0.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (13)
  • src/extension.node.ts
  • src/kernels/deepnote/environments/deepnoteEnvironmentsView.node.ts
  • src/notebooks/deepnote/deepnoteActivationService.ts
  • src/notebooks/deepnote/deepnoteCellExecutionAnalytics.ts
  • src/notebooks/deepnote/deepnoteExplorerView.ts
  • src/notebooks/deepnote/deepnoteNotebookCommandListener.ts
  • src/notebooks/deepnote/integrations/integrationWebview.ts
  • src/notebooks/deepnote/openInDeepnoteHandler.node.ts
  • src/notebooks/notebookCommandListener.ts
  • src/platform/analytics/telemetryService.ts
  • src/platform/analytics/telemetryService.unit.test.ts
  • src/platform/analytics/types.ts
  • src/platform/serviceRegistry.node.ts

Comment thread src/notebooks/deepnote/deepnoteCellExecutionAnalytics.ts Outdated
Comment thread src/notebooks/deepnote/deepnoteCellExecutionAnalytics.ts
Comment thread src/notebooks/deepnote/deepnoteCellExecutionAnalytics.ts Outdated
Comment thread src/notebooks/deepnote/deepnoteExplorerView.ts Outdated
Comment thread src/notebooks/deepnote/deepnoteExplorerView.ts
Comment thread src/notebooks/deepnote/deepnoteExplorerView.ts Outdated
Comment thread src/platform/analytics/telemetryService.ts
Comment thread src/platform/analytics/telemetryService.unit.test.ts Outdated
Comment thread src/platform/analytics/telemetryService.unit.test.ts
Comment thread src/platform/serviceRegistry.node.ts
…vent

Replace untyped string event names with a TelemetryEventName literal union
type for static checking. Change trackEvent signature to accept a single
TelemetryEvent object ({ eventName, properties? }) instead of positional args.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (13)
src/platform/analytics/telemetryService.ts (3)

60-74: ⚠️ Potential issue | 🟠 Major

Initialization is non-atomic and can lock telemetry into a broken state.

Line 61 sets initialized before setup completes. If state/client creation fails once, future calls skip re-init. Also, first capture can use an empty distinctId because updateValue() is async and not awaited.

Proposed fix
 private initialize(): void {
-    this.initialized = true;
-
-    this.userIdState = this.stateFactory.createGlobalPersistentState<string>(USER_ID_STORAGE_KEY, '');
-
-    if (!this.userIdState.value) {
-        void this.userIdState.updateValue(generateUuid());
-    }
-
-    this.client = new PostHog(POSTHOG_API_KEY, {
+    const userIdState = this.stateFactory.createGlobalPersistentState<string>(USER_ID_STORAGE_KEY, '');
+    const distinctId = userIdState.value || generateUuid();
+    if (!userIdState.value) {
+        void userIdState.updateValue(distinctId);
+    }
+    const client = new PostHog(POSTHOG_API_KEY, {
         flushAt: 20,
         flushInterval: 30000,
         host: POSTHOG_HOST
     });
+    this.userIdState = userIdState;
+    this.client = client;
+    this.initialized = true;
 }

Based on learnings: "Verify state after async setup operations - methods can return early without throwing, so check expected state was created".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/platform/analytics/telemetryService.ts` around lines 60 - 74, The
initialize() method marks this.initialized too early and fires async updateValue
without awaiting, which can leave telemetry in a broken state; change
initialize() so it performs stateFactory.createGlobalPersistentState and awaits
userIdState.updateValue(generateUuid()) (or otherwise ensures the value is set)
before constructing the PostHog client and only set this.initialized = true at
the end; also wrap state/client creation in try/catch and ensure on any failure
this.initialized remains false so future calls will retry, referencing
initialize(), userIdState, stateFactory.createGlobalPersistentState,
userIdState.updateValue(generateUuid()), and PostHog client construction.

76-84: ⚠️ Potential issue | 🟠 Major

Usage telemetry should only run when telemetryLevel is all.

Line 79 only blocks off, so usage events still send on error/crash. For VS Code usage telemetry semantics, gate this to telemetryLevel === 'all'.

In VS Code telemetry settings, are usage/feature analytics events allowed for telemetry.telemetryLevel values "error" or "crash", or only when set to "all"?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/platform/analytics/telemetryService.ts` around lines 76 - 84, The
isTelemetryEnabled() function currently allows telemetry for any telemetryLevel
except 'off', but usage telemetry must only run when telemetryLevel === 'all';
change the gate so telemetryLevel is compared strictly to 'all' and only then
return the deepnote.telemetry.enabled setting (i.e., replace the current
if-check that returns false for 'off' with a check that returns false unless
telemetryLevel === 'all'); update references inside isTelemetryEnabled, which
reads telemetryLevel from
workspace.getConfiguration('telemetry').get<string>('telemetryLevel', 'all') and
the deepnote setting via
workspace.getConfiguration('deepnote').get<boolean>('telemetry.enabled', true).

69-73: ⚠️ Potential issue | 🟠 Major

Guard unresolved PostHog key before client creation.

If POSTHOG_API_KEY is still a placeholder at runtime, initialization should short-circuit with a warning instead of creating a non-functional client.

#!/bin/bash
set -euo pipefail

echo "== Placeholder usage =="
rg -n "__POSTHOG_API_KEY__|POSTHOG_API_KEY"

echo "== Build-time replacement hooks (define/replace) =="
rg -n "define:|DefinePlugin|replace\\(|POSTHOG_API_KEY|__POSTHOG_API_KEY__" --type ts --type js --type json
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/platform/analytics/telemetryService.ts` around lines 69 - 73, Guard
PostHog initialization by checking POSTHOG_API_KEY for unresolved placeholders
before calling new PostHog: in the constructor or where this.client is set,
detect empty or placeholder values like "__POSTHOG_API_KEY__" (or a literal
"POSTHOG_API_KEY") and short-circuit by leaving this.client null/undefined and
emitting a warning via the logger instead of constructing a non-functional
client; keep the new PostHog(...) call only when the key is valid, and ensure
other TelemetryService methods that use this.client handle the null/undefined
client safely.
src/notebooks/deepnote/openInDeepnoteHandler.node.ts (1)

24-27: ⚠️ Potential issue | 🟠 Major

open_in_deepnote is emitted even when the operation fails.

handleOpenInDeepnote() swallows errors, so Line 26 runs for both success and failure. Track only on confirmed success (e.g., after env.openExternal(...)) or have the handler return a success boolean.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/notebooks/deepnote/openInDeepnoteHandler.node.ts` around lines 24 - 27,
The analytics event is emitted regardless of failures because
handleOpenInDeepnote() swallows errors; update the flow so tracking only happens
on confirmed success: change handleOpenInDeepnote (or create a small wrapper) to
return a boolean or throw on failure, and only call this.analytics?.trackEvent({
eventName: 'open_in_deepnote' }) after a successful env.openExternal(...)
completes (or when the handler returns true). Reference Commands.OpenInDeepnote
and the handleOpenInDeepnote function to locate the call site and ensure errors
from env.openExternal are propagated or translated into a success flag before
tracking.
src/platform/analytics/telemetryService.unit.test.ts (1)

104-127: 🧹 Nitpick | 🔵 Trivial

Add an assertion that capture() was called with the expected payload.

This test validates ID persistence but not that an event was actually sent with the generated distinctId.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/platform/analytics/telemetryService.unit.test.ts` around lines 104 - 127,
Add an assertion that the analytics client's capture was invoked with the
expected payload: after calling TelemetryService.trackEvent({ eventName:
'open_notebook' }) and extracting generatedId from mockUserIdState.updateValue,
assert that the mocked capture method (e.g., analyticsClient.capture or
mockAnalyticsClient.capture) was called once and that its first call args
include an object with event equal to 'open_notebook' and distinctId equal to
generatedId (or contains distinctId property matching generatedId); use the
existing mocks/stubs for the analytics client used by TelemetryService to locate
the capture call.
src/notebooks/deepnote/integrations/integrationWebview.ts (1)

439-460: 🧹 Nitpick | 🔵 Trivial

Normalize integration event payloads.

save_integration sends integrationType, but configure_integration, reset_integration, and delete_integration do not. Add the same property for consistent analytics dimensions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/notebooks/deepnote/integrations/integrationWebview.ts` around lines 439 -
460, Analytics events for configure_integration, reset_integration, and
delete_integration lack the integrationType property, causing inconsistent
dimensions; update the handlers that call this.showConfigurationForm,
this.resetConfiguration, and the delete handler to include properties: {
integrationType: <type> } similar to save_integration; obtain the type by
resolving the integrationId (e.g., via an existing helper or registry like
getIntegrationType/integrationRegistry.lookup by id) and fall back to 'unknown'
if resolution fails, then pass that value in the this.analytics?.trackEvent call
for configure_integration, reset_integration, and delete_integration.
src/notebooks/deepnote/deepnoteExplorerView.ts (5)

420-423: ⚠️ Potential issue | 🟡 Minor

Export telemetry fires on format/folder/overwrite cancellation.

exportProject and exportNotebook have multiple early-return points (format selection, folder selection, overwrite confirmation). Tracking unconditional.

Also applies to: 427-430

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/notebooks/deepnote/deepnoteExplorerView.ts` around lines 420 - 423, The
telemetry is emitted unconditionally after calling exportProject/exportNotebook
even when those methods return early (format/folder/overwrite cancel); change
exportProject(treeItem) and exportNotebook(treeItem) to return a success
boolean/enum (e.g. true on completed export, false on cancel/failure) or throw
on fatal errors, then only call analytics?.trackEvent({ eventName: ...,
properties: {...} }) when the call indicates success; update the
Commands.ExportProject and Commands.ExportNotebook handlers to await the result
and guard the analytics call based on that returned value so canceled flows do
not fire telemetry.

337-340: ⚠️ Potential issue | 🟡 Minor

Telemetry fires regardless of outcome.

openNotebook can fail (catch block) or early-return (missing notebookId). Event still emits. Consider tracking only on success.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/notebooks/deepnote/deepnoteExplorerView.ts` around lines 337 - 340,
Telemetry is being sent unconditionally after calling openNotebook; update the
command handler and/or openNotebook to only emit analytics on success—either
have openNotebook return a success boolean (or throw on failure) and in the
commands.registerCommand callback await the result and call
this.analytics.trackEvent({ eventName: 'open_notebook' }) only when success is
true, or move the trackEvent call into the successful path inside openNotebook
(e.g., after the notebookId check and after any try/catch success branch) so
events are not fired on early-return or failure; reference the
commands.registerCommand callback, openNotebook, and analytics.trackEvent to
locate and implement the change.

387-390: ⚠️ Potential issue | 🟡 Minor

Delete/duplicate telemetry fires on user cancellation.

deleteProject, deleteNotebook, duplicateNotebook return early when user cancels confirmation. Tracking still emits.

Also applies to: 400-403, 407-410

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/notebooks/deepnote/deepnoteExplorerView.ts` around lines 387 - 390, The
telemetry is fired even when the user cancels because the command handlers call
this.analytics?.trackEvent unconditionally after calling
deleteProject/deleteNotebook/duplicateNotebook; update the handlers for
Commands.DeleteProject, Commands.DeleteNotebook and Commands.DuplicateNotebook
to only call this.analytics?.trackEvent when the operation actually completed
(e.g., have deleteProject/deleteNotebook/duplicateNotebook return a boolean or a
result object indicating success vs. cancellation and check that before
tracking). If the methods don't currently return a success flag, change
deleteProject/deleteNotebook/duplicateNotebook to return true on successful
completion and false on user cancellation, and update the registerCommand
callbacks to await the call and conditionally emit telemetry only when the
returned value indicates the action was performed.

352-355: ⚠️ Potential issue | 🟡 Minor

Telemetry fires on user cancellation or early-return.

newProject, importNotebook, importJupyterNotebook, newNotebook all have early-return paths (no workspace, user cancelled input). Tracking happens unconditionally after await.

Option: return boolean from operations
-    private async newProject(): Promise<void> {
+    private async newProject(): Promise<boolean> {
         if (!workspace.workspaceFolders || workspace.workspaceFolders.length === 0) {
             // ...
-            return;
+            return false;
         }
         // ... on success ...
+        return true;
     }

Then in handler:

     commands.registerCommand(Commands.NewProject, async () => {
-        await this.newProject();
-        this.analytics?.trackEvent({ eventName: 'create_project' });
+        const created = await this.newProject();
+        if (created) {
+            this.analytics?.trackEvent({ eventName: 'create_project' });
+        }
     })

Also applies to: 359-362, 366-369, 373-376

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/notebooks/deepnote/deepnoteExplorerView.ts` around lines 352 - 355, The
telemetry is fired unconditionally after awaiting command handlers (e.g.,
Commands.NewProject, Commands.ImportNotebook, Commands.ImportJupyterNotebook,
Commands.NewNotebook) even when the underlying methods (newProject,
importNotebook, importJupyterNotebook, newNotebook) early-return or are
cancelled; change those methods to return a boolean success flag (true on
completed action, false on cancel/early-return) and update each command handler
to only call this.analytics?.trackEvent(...) when the awaited call returns true,
so telemetry is only recorded on actual successful operations.

29-30: 🧹 Nitpick | 🔵 Trivial

Constructor parameter order: analytics should precede logger alphabetically.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/notebooks/deepnote/deepnoteExplorerView.ts` around lines 29 - 30, The
constructor parameter order is out of alphabetical order; move the
ITelemetryService parameter before ILogger and update the injection decorators
accordingly: change the parameter list from "@inject(ILogger) logger: ILogger,
private readonly analytics?: ITelemetryService" to "private readonly analytics?:
ITelemetryService, `@inject`(ILogger) logger: ILogger" (or preferably annotate
analytics with `@inject`(ITelemetryService) and then logger with
`@inject`(ILogger)), ensuring the symbols analytics and logger and their types
(ITelemetryService, ILogger) are updated in the DeepnoteExplorerView constructor
signature so analytics appears before logger alphabetically.
src/notebooks/deepnote/deepnoteCellExecutionAnalytics.ts (2)

59-59: 🧹 Nitpick | 🔵 Trivial

Optional chaining redundant.

analytics guaranteed non-null after early return at line 22-24.

Minor simplification
-                this.analytics?.trackEvent({ eventName: 'execute_cell', properties });
+                this.analytics.trackEvent({ eventName: 'execute_cell', properties });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/notebooks/deepnote/deepnoteCellExecutionAnalytics.ts` at line 59, The
optional chaining on this.analytics in the call to trackEvent is unnecessary
because analytics is guaranteed non-null after the early return; change the line
that calls this.analytics?.trackEvent({ eventName: 'execute_cell', properties })
to use a direct call this.analytics.trackEvent({ eventName: 'execute_cell',
properties }) so the call is simplified and consistent with the non-null
guarantee.

15-19: 🧹 Nitpick | 🔵 Trivial

Constructor parameter order: should be analytics, disposables, notebookManager.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/notebooks/deepnote/deepnoteCellExecutionAnalytics.ts` around lines 15 -
19, Constructor parameter order is wrong—reorder the injected constructor
parameters to analytics, disposables, notebookManager. Modify the constructor
signature so the parameters are declared as: `@inject`(ITelemetryService)
`@optional`() private readonly analytics: ITelemetryService | undefined,
`@inject`(IDisposableRegistry) private readonly disposables: Disposable[],
`@inject`(IDeepnoteNotebookManager) private readonly notebookManager:
IDeepnoteNotebookManager; keep the existing decorators and private readonly
declarations intact so DI tokens (ITelemetryService, IDisposableRegistry,
IDeepnoteNotebookManager) still match.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/platform/analytics/telemetryService.ts`:
- Around line 28-58: Move the public async dispose(): Promise<void> method to
appear before the public trackEvent({ eventName, properties }: TelemetryEvent):
void method so public methods follow the repository rule (ordered by
accessibility then alphabetically); locate the dispose and trackEvent methods in
telemetryService.ts and reorder them accordingly while keeping their bodies
unchanged (ensure any references between them still compile).

In `@src/platform/analytics/telemetryService.unit.test.ts`:
- Around line 78-102: Add two unit tests mirroring the existing "trackEvent
should not throw when VSCode telemetry level is off" test but set the stubbed
configuration's telemetryLevel to 'error' and 'crash' respectively; for each
test, stub vscode.workspace.getConfiguration to return get(...) => 'error' (or
'crash') when section === 'telemetry' and _key === 'telemetryLevel', instantiate
TelemetryService (TelemetryService), call analyticsService.trackEvent({
eventName: 'open_notebook' }) inside assert.doesNotThrow, and assert that
mockStateFactory.createGlobalPersistentState was not called to verify telemetry
is gated for those levels.

---

Duplicate comments:
In `@src/notebooks/deepnote/deepnoteCellExecutionAnalytics.ts`:
- Line 59: The optional chaining on this.analytics in the call to trackEvent is
unnecessary because analytics is guaranteed non-null after the early return;
change the line that calls this.analytics?.trackEvent({ eventName:
'execute_cell', properties }) to use a direct call this.analytics.trackEvent({
eventName: 'execute_cell', properties }) so the call is simplified and
consistent with the non-null guarantee.
- Around line 15-19: Constructor parameter order is wrong—reorder the injected
constructor parameters to analytics, disposables, notebookManager. Modify the
constructor signature so the parameters are declared as:
`@inject`(ITelemetryService) `@optional`() private readonly analytics:
ITelemetryService | undefined, `@inject`(IDisposableRegistry) private readonly
disposables: Disposable[], `@inject`(IDeepnoteNotebookManager) private readonly
notebookManager: IDeepnoteNotebookManager; keep the existing decorators and
private readonly declarations intact so DI tokens (ITelemetryService,
IDisposableRegistry, IDeepnoteNotebookManager) still match.

In `@src/notebooks/deepnote/deepnoteExplorerView.ts`:
- Around line 420-423: The telemetry is emitted unconditionally after calling
exportProject/exportNotebook even when those methods return early
(format/folder/overwrite cancel); change exportProject(treeItem) and
exportNotebook(treeItem) to return a success boolean/enum (e.g. true on
completed export, false on cancel/failure) or throw on fatal errors, then only
call analytics?.trackEvent({ eventName: ..., properties: {...} }) when the call
indicates success; update the Commands.ExportProject and Commands.ExportNotebook
handlers to await the result and guard the analytics call based on that returned
value so canceled flows do not fire telemetry.
- Around line 337-340: Telemetry is being sent unconditionally after calling
openNotebook; update the command handler and/or openNotebook to only emit
analytics on success—either have openNotebook return a success boolean (or throw
on failure) and in the commands.registerCommand callback await the result and
call this.analytics.trackEvent({ eventName: 'open_notebook' }) only when success
is true, or move the trackEvent call into the successful path inside
openNotebook (e.g., after the notebookId check and after any try/catch success
branch) so events are not fired on early-return or failure; reference the
commands.registerCommand callback, openNotebook, and analytics.trackEvent to
locate and implement the change.
- Around line 387-390: The telemetry is fired even when the user cancels because
the command handlers call this.analytics?.trackEvent unconditionally after
calling deleteProject/deleteNotebook/duplicateNotebook; update the handlers for
Commands.DeleteProject, Commands.DeleteNotebook and Commands.DuplicateNotebook
to only call this.analytics?.trackEvent when the operation actually completed
(e.g., have deleteProject/deleteNotebook/duplicateNotebook return a boolean or a
result object indicating success vs. cancellation and check that before
tracking). If the methods don't currently return a success flag, change
deleteProject/deleteNotebook/duplicateNotebook to return true on successful
completion and false on user cancellation, and update the registerCommand
callbacks to await the call and conditionally emit telemetry only when the
returned value indicates the action was performed.
- Around line 352-355: The telemetry is fired unconditionally after awaiting
command handlers (e.g., Commands.NewProject, Commands.ImportNotebook,
Commands.ImportJupyterNotebook, Commands.NewNotebook) even when the underlying
methods (newProject, importNotebook, importJupyterNotebook, newNotebook)
early-return or are cancelled; change those methods to return a boolean success
flag (true on completed action, false on cancel/early-return) and update each
command handler to only call this.analytics?.trackEvent(...) when the awaited
call returns true, so telemetry is only recorded on actual successful
operations.
- Around line 29-30: The constructor parameter order is out of alphabetical
order; move the ITelemetryService parameter before ILogger and update the
injection decorators accordingly: change the parameter list from
"@inject(ILogger) logger: ILogger, private readonly analytics?:
ITelemetryService" to "private readonly analytics?: ITelemetryService,
`@inject`(ILogger) logger: ILogger" (or preferably annotate analytics with
`@inject`(ITelemetryService) and then logger with `@inject`(ILogger)), ensuring the
symbols analytics and logger and their types (ITelemetryService, ILogger) are
updated in the DeepnoteExplorerView constructor signature so analytics appears
before logger alphabetically.

In `@src/notebooks/deepnote/integrations/integrationWebview.ts`:
- Around line 439-460: Analytics events for configure_integration,
reset_integration, and delete_integration lack the integrationType property,
causing inconsistent dimensions; update the handlers that call
this.showConfigurationForm, this.resetConfiguration, and the delete handler to
include properties: { integrationType: <type> } similar to save_integration;
obtain the type by resolving the integrationId (e.g., via an existing helper or
registry like getIntegrationType/integrationRegistry.lookup by id) and fall back
to 'unknown' if resolution fails, then pass that value in the
this.analytics?.trackEvent call for configure_integration, reset_integration,
and delete_integration.

In `@src/notebooks/deepnote/openInDeepnoteHandler.node.ts`:
- Around line 24-27: The analytics event is emitted regardless of failures
because handleOpenInDeepnote() swallows errors; update the flow so tracking only
happens on confirmed success: change handleOpenInDeepnote (or create a small
wrapper) to return a boolean or throw on failure, and only call
this.analytics?.trackEvent({ eventName: 'open_in_deepnote' }) after a successful
env.openExternal(...) completes (or when the handler returns true). Reference
Commands.OpenInDeepnote and the handleOpenInDeepnote function to locate the call
site and ensure errors from env.openExternal are propagated or translated into a
success flag before tracking.

In `@src/platform/analytics/telemetryService.ts`:
- Around line 60-74: The initialize() method marks this.initialized too early
and fires async updateValue without awaiting, which can leave telemetry in a
broken state; change initialize() so it performs
stateFactory.createGlobalPersistentState and awaits
userIdState.updateValue(generateUuid()) (or otherwise ensures the value is set)
before constructing the PostHog client and only set this.initialized = true at
the end; also wrap state/client creation in try/catch and ensure on any failure
this.initialized remains false so future calls will retry, referencing
initialize(), userIdState, stateFactory.createGlobalPersistentState,
userIdState.updateValue(generateUuid()), and PostHog client construction.
- Around line 76-84: The isTelemetryEnabled() function currently allows
telemetry for any telemetryLevel except 'off', but usage telemetry must only run
when telemetryLevel === 'all'; change the gate so telemetryLevel is compared
strictly to 'all' and only then return the deepnote.telemetry.enabled setting
(i.e., replace the current if-check that returns false for 'off' with a check
that returns false unless telemetryLevel === 'all'); update references inside
isTelemetryEnabled, which reads telemetryLevel from
workspace.getConfiguration('telemetry').get<string>('telemetryLevel', 'all') and
the deepnote setting via
workspace.getConfiguration('deepnote').get<boolean>('telemetry.enabled', true).
- Around line 69-73: Guard PostHog initialization by checking POSTHOG_API_KEY
for unresolved placeholders before calling new PostHog: in the constructor or
where this.client is set, detect empty or placeholder values like
"__POSTHOG_API_KEY__" (or a literal "POSTHOG_API_KEY") and short-circuit by
leaving this.client null/undefined and emitting a warning via the logger instead
of constructing a non-functional client; keep the new PostHog(...) call only
when the key is valid, and ensure other TelemetryService methods that use
this.client handle the null/undefined client safely.

In `@src/platform/analytics/telemetryService.unit.test.ts`:
- Around line 104-127: Add an assertion that the analytics client's capture was
invoked with the expected payload: after calling TelemetryService.trackEvent({
eventName: 'open_notebook' }) and extracting generatedId from
mockUserIdState.updateValue, assert that the mocked capture method (e.g.,
analyticsClient.capture or mockAnalyticsClient.capture) was called once and that
its first call args include an object with event equal to 'open_notebook' and
distinctId equal to generatedId (or contains distinctId property matching
generatedId); use the existing mocks/stubs for the analytics client used by
TelemetryService to locate the capture call.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 8174ddd7-996d-43f4-89b0-71497dfe9a56

📥 Commits

Reviewing files that changed from the base of the PR and between 01b41d0 and c1181bd.

📒 Files selected for processing (10)
  • src/kernels/deepnote/environments/deepnoteEnvironmentsView.node.ts
  • src/notebooks/deepnote/deepnoteCellExecutionAnalytics.ts
  • src/notebooks/deepnote/deepnoteExplorerView.ts
  • src/notebooks/deepnote/deepnoteNotebookCommandListener.ts
  • src/notebooks/deepnote/integrations/integrationWebview.ts
  • src/notebooks/deepnote/openInDeepnoteHandler.node.ts
  • src/notebooks/notebookCommandListener.ts
  • src/platform/analytics/telemetryService.ts
  • src/platform/analytics/telemetryService.unit.test.ts
  • src/platform/analytics/types.ts

Comment thread src/platform/analytics/telemetryService.ts Outdated
Comment thread src/platform/analytics/telemetryService.unit.test.ts Outdated
Add TelemetryWebService as a no-op implementation registered in the web
service registry, allowing ITelemetryService to be injected as required
everywhere instead of optional.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

♻️ Duplicate comments (3)
src/notebooks/deepnote/openInDeepnoteHandler.node.ts (1)

24-27: ⚠️ Potential issue | 🟠 Major

open_in_deepnote is still emitted on failed operations.

handleOpenInDeepnote() handles errors internally, so the callback always reaches trackEvent. Gate tracking on an explicit success result instead.

Proposed fix
-            commands.registerCommand(Commands.OpenInDeepnote, async () => {
-                await this.handleOpenInDeepnote();
-                this.analytics.trackEvent({ eventName: 'open_in_deepnote' });
-            })
+            commands.registerCommand(Commands.OpenInDeepnote, async () => {
+                const success = await this.handleOpenInDeepnote();
+                if (success) {
+                    this.analytics.trackEvent({ eventName: 'open_in_deepnote' });
+                }
+            })
         );
     }

-    private async handleOpenInDeepnote(): Promise<void> {
+    private async handleOpenInDeepnote(): Promise<boolean> {
         try {
@@
-                    return;
+                    return false;
@@
-                return;
+                return false;
@@
-                        await env.openExternal(Uri.parse(deepnoteUrl));
+                        const opened = await env.openExternal(Uri.parse(deepnoteUrl));
+                        if (!opened) {
+                            return false;
+                        }
@@
-                        logger.info('Successfully opened file in Deepnote');
+                        logger.info('Successfully opened file in Deepnote');
+                        return true;
                     } catch (error) {
@@
+                        return false;
                     }
                 }
             );
         } catch (error) {
@@
+            return false;
         }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/notebooks/deepnote/openInDeepnoteHandler.node.ts` around lines 24 - 27,
The command callback always calls analytics.trackEvent despite failures because
handleOpenInDeepnote swallows errors; change handleOpenInDeepnote to return an
explicit success boolean (or throw on fatal failure) and update the
Commands.OpenInDeepnote registration so it awaits the result and only calls
analytics.trackEvent({ eventName: 'open_in_deepnote' }) when the returned value
indicates success (or no exception was thrown). Refer to handleOpenInDeepnote
and analytics.trackEvent in your changes and ensure the registerCommand callback
checks the success flag before tracking.
src/notebooks/deepnote/deepnoteCellExecutionAnalytics.ts (1)

15-19: 🛠️ Refactor suggestion | 🟠 Major

Reorder the constructor-promoted dependencies.

disposables should sort before notebookManager here.

↕️ Suggested reorder
     constructor(
         `@inject`(ITelemetryService) private readonly analytics: ITelemetryService,
-        `@inject`(IDeepnoteNotebookManager) private readonly notebookManager: IDeepnoteNotebookManager,
-        `@inject`(IDisposableRegistry) private readonly disposables: Disposable[]
+        `@inject`(IDisposableRegistry) private readonly disposables: Disposable[],
+        `@inject`(IDeepnoteNotebookManager) private readonly notebookManager: IDeepnoteNotebookManager
     ) {}
As per coding guidelines: `**/*.{ts,tsx}`: Order method, fields and properties, first by accessibility and then by alphabetical order.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/notebooks/deepnote/deepnoteCellExecutionAnalytics.ts` around lines 15 -
19, Constructor-promoted dependencies in deepnoteCellExecutionAnalytics.ts are
out of the required alphabetical order: move the disposables parameter (type
Disposable[] / token IDisposableRegistry) before notebookManager
(IDeepnoteNotebookManager) so the constructor signature reads analytics,
disposables, notebookManager; update the parameter order in the constructor
declaration and any corresponding property uses to match this new ordering
(constructor, analytics, disposables, notebookManager).
src/notebooks/deepnote/deepnoteExplorerView.ts (1)

337-340: ⚠️ Potential issue | 🟡 Minor

Only track completed actions.

Each helper awaited here returns void, and several of them early-return or swallow failures. These handlers will therefore count cancelled or failed opens, creates, imports, deletes, duplicates, and exports as successful usage. Return a success flag/result, or move trackEvent into the helper’s success path.

✅ One pattern to apply
-    private async newProject(): Promise<void> {
+    private async newProject(): Promise<boolean> {
         if (!workspace.workspaceFolders || workspace.workspaceFolders.length === 0) {
             ...
-            return;
+            return false;
         }
         ...
         if (!projectName) {
-            return;
+            return false;
         }
         try {
             ...
+            return true;
         } catch (error) {
             ...
+            return false;
         }
     }

-            commands.registerCommand(Commands.NewProject, async () => {
-                await this.newProject();
-                this.analytics.trackEvent({ eventName: 'create_project' });
-            })
+            commands.registerCommand(Commands.NewProject, async () => {
+                if (await this.newProject()) {
+                    this.analytics.trackEvent({ eventName: 'create_project' });
+                }
+            })
Based on learnings: `Applies to **/*.{ts,tsx} : Verify state after async setup operations - methods can return early without throwing, so check expected state was created`

Also applies to: 352-355, 359-362, 366-369, 373-376, 387-390, 400-403, 407-410, 420-423, 427-430

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/notebooks/deepnote/deepnoteExplorerView.ts` around lines 337 - 340, The
event is tracked unconditionally after awaiting helpers that may early-return or
swallow failures; update the handlers (e.g., the Commands.OpenDeepnoteNotebook
registration) to only call this.analytics.trackEvent when the helper actually
succeeded by changing the helper signature (e.g., openNotebook) to return a
success boolean/result or by inspecting a success state it sets, then await the
helper into a variable and call trackEvent only if that variable indicates
success; apply the same pattern to the other affected command handlers
referenced in this file (the registrations around the listed ranges) so tracking
happens on confirmed success paths only.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/kernels/deepnote/environments/deepnoteEnvironmentsView.unit.test.ts`:
- Around line 65-67: The tests currently inject telemetry as an inline stub and
never assert telemetry side effects; extract the telemetry object into a
variable (e.g., const telemetryStub = { trackEvent: sinon.stub(), dispose:
sinon.stub().resolves() } as unknown as ITelemetryService) and pass that into
the DeepnoteEnvironmentsView constructor, then in the success-path unit tests
for create/delete/select flows (the tests exercising the methods that
createEnvironment, deleteEnvironment, and selectEnvironment or whatever test
helpers trigger those flows) add assertions that telemetryStub.trackEvent was
called with the expected event names and payloads (use sinon.assert.calledWith
or similar) to verify telemetry side effects. Ensure you reset/restore the stub
between tests.

In `@src/notebooks/deepnote/deepnoteExplorerView.unit.test.ts`:
- Around line 18-19: The telemetry mock (mockAnalytics) is currently a no-op
which prevents verifying analytics paths; replace the no-op methods with
observable spies/mocks so tests can assert calls: change
mockAnalytics.trackEvent from () => undefined to a spy/mock function (e.g.,
jest.fn or sinon.spy) and make dispose an async mock (e.g.,
jest.fn().mockResolvedValue(undefined)), keeping the ITelemetryService cast;
update tests to assert on mockAnalytics.trackEvent and mockAnalytics.dispose
where cancel/early-return behavior should trigger telemetry.

In `@src/notebooks/deepnote/deepnoteNotebookCommandListener.ts`:
- Line 269: Extract the repeated tracking call into a single helper to avoid
duplicated payloads: create a method (e.g., trackAddBlock(blockType) or
addBlockAnalytics) that wraps this.analytics.trackEvent({ eventName:
'add_block', properties: { blockType } }) and replace the five inline usages
(the current direct calls to this.analytics.trackEvent with eventName
'add_block' at the five success paths) with calls to that helper; update any
callers to pass the literal 'sql' (or other blockType) as needed so all
locations (the existing occurrences including the one shown) use the central
helper.

In `@src/notebooks/deepnote/openInDeepnoteHandler.node.unit.test.ts`:
- Around line 53-56: The tests create OpenInDeepnoteHandlerClass with a stubbed
ITelemetryService but never assert telemetry calls; update the unit tests for
the registered command callback on the handler (the code path invoked by
handler.registerCommand or the exported command callback) to assert that the
telemetryService.trackEvent stub is called exactly when the open flow succeeds
and not called on failure. Specifically, locate the handler instance (handler)
and the injected telemetry stub (trackEvent) and add two test cases: one
simulating a successful open (stub any dependencies to return success) that
expects trackEvent.calledOnceWithMatch(...) and one simulating a failure or
early return that expects trackEvent.notCalled; keep assertions focused on
trackEvent and use the existing sinon stub on dispose/trackEvent to verify
behavior.

In `@src/platform/analytics/telemetryWebService.ts`:
- Around line 6-13: Reorder the public methods in the TelemetryWebService class
so they follow alphabetical class-member ordering: move the public async
dispose(): Promise<void> method to appear before the public trackEvent(_event:
TelemetryEvent): void method; ensure method signatures and no-op bodies remain
unchanged and that the class still implements ITelemetryService.

---

Duplicate comments:
In `@src/notebooks/deepnote/deepnoteCellExecutionAnalytics.ts`:
- Around line 15-19: Constructor-promoted dependencies in
deepnoteCellExecutionAnalytics.ts are out of the required alphabetical order:
move the disposables parameter (type Disposable[] / token IDisposableRegistry)
before notebookManager (IDeepnoteNotebookManager) so the constructor signature
reads analytics, disposables, notebookManager; update the parameter order in the
constructor declaration and any corresponding property uses to match this new
ordering (constructor, analytics, disposables, notebookManager).

In `@src/notebooks/deepnote/deepnoteExplorerView.ts`:
- Around line 337-340: The event is tracked unconditionally after awaiting
helpers that may early-return or swallow failures; update the handlers (e.g.,
the Commands.OpenDeepnoteNotebook registration) to only call
this.analytics.trackEvent when the helper actually succeeded by changing the
helper signature (e.g., openNotebook) to return a success boolean/result or by
inspecting a success state it sets, then await the helper into a variable and
call trackEvent only if that variable indicates success; apply the same pattern
to the other affected command handlers referenced in this file (the
registrations around the listed ranges) so tracking happens on confirmed success
paths only.

In `@src/notebooks/deepnote/openInDeepnoteHandler.node.ts`:
- Around line 24-27: The command callback always calls analytics.trackEvent
despite failures because handleOpenInDeepnote swallows errors; change
handleOpenInDeepnote to return an explicit success boolean (or throw on fatal
failure) and update the Commands.OpenInDeepnote registration so it awaits the
result and only calls analytics.trackEvent({ eventName: 'open_in_deepnote' })
when the returned value indicates success (or no exception was thrown). Refer to
handleOpenInDeepnote and analytics.trackEvent in your changes and ensure the
registerCommand callback checks the success flag before tracking.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: c78fe551-eaf3-43b4-abc6-158409c1dc1e

📥 Commits

Reviewing files that changed from the base of the PR and between c1181bd and 2cd604d.

📒 Files selected for processing (15)
  • src/kernels/deepnote/environments/deepnoteEnvironmentsView.node.ts
  • src/kernels/deepnote/environments/deepnoteEnvironmentsView.unit.test.ts
  • src/notebooks/deepnote/deepnoteActivationService.ts
  • src/notebooks/deepnote/deepnoteActivationService.unit.test.ts
  • src/notebooks/deepnote/deepnoteCellExecutionAnalytics.ts
  • src/notebooks/deepnote/deepnoteExplorerView.ts
  • src/notebooks/deepnote/deepnoteExplorerView.unit.test.ts
  • src/notebooks/deepnote/deepnoteNotebookCommandListener.ts
  • src/notebooks/deepnote/deepnoteNotebookCommandListener.unit.test.ts
  • src/notebooks/deepnote/integrations/integrationWebview.ts
  • src/notebooks/deepnote/openInDeepnoteHandler.node.ts
  • src/notebooks/deepnote/openInDeepnoteHandler.node.unit.test.ts
  • src/notebooks/notebookCommandListener.ts
  • src/platform/analytics/telemetryWebService.ts
  • src/platform/serviceRegistry.web.ts

Comment thread src/notebooks/deepnote/deepnoteExplorerView.unit.test.ts Outdated
Comment thread src/notebooks/deepnote/deepnoteNotebookCommandListener.ts Outdated
Comment thread src/notebooks/deepnote/openInDeepnoteHandler.node.unit.test.ts Outdated
Comment thread src/platform/analytics/telemetryWebService.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (1)
src/notebooks/deepnote/deepnoteExplorerView.unit.test.ts (1)

18-19: ⚠️ Potential issue | 🟠 Major

Use an observable telemetry test double, not NoOpTelemetryService.

This wires the new constructor but still can’t verify telemetry side effects (including early-return/cancel branches), so the telemetry feature remains largely untested in this suite.

Suggested change
-import { NoOpTelemetryService } from '../../platform/analytics/noOpTelemetryService';
+import type { ITelemetryService } from '../../platform/analytics/types';

-const mockAnalytics = new NoOpTelemetryService();
+const mockAnalytics = {
+    trackEvent: sinon.stub(),
+    dispose: sinon.stub().resolves()
+} as unknown as ITelemetryService;

Then assert expected calls in relevant tests, e.g. cancel paths vs success paths.

Also applies to: 58-59, 196-197, 240-241

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/notebooks/deepnote/deepnoteExplorerView.unit.test.ts` around lines 18 -
19, Replace the NoOpTelemetryService test double (the const mockAnalytics = new
NoOpTelemetryService()) with an observable/mock telemetry implementation that
records calls (e.g., MockObservableTelemetryService or ObservableTelemetrySpy)
and pass that into the DeepnoteExplorerView constructor so tests can assert
telemetry side effects; update tests that exercise cancel/early-return and
success paths to assert expected telemetry method invocations (or
counts/arguments) instead of using the no-op, and reuse the same mockTelemetry
instance across the tests that currently declare mockAnalytics to verify cancel
vs success branches.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/notebooks/deepnote/deepnoteExplorerView.ts`:
- Around line 377-380: The telemetry calls for Commands.NewNotebook
(analytics.trackEvent) are inconsistent because newNotebook and
duplicateNotebook fire events unconditionally; change both to follow the `{
completed }` pattern used elsewhere: call analytics.trackEvent with `{
eventName: 'create_notebook', completed: true }` only after the awaited
operation succeeds, and in a catch/finally ensure you send `{ completed: false
}` (or `{ completed }` value) on failure; update the handlers for
Commands.NewNotebook and the duplicate command to wrap await
this.newNotebook()/this.duplicateNotebook() in try/catch and report completed
accordingly so telemetry matches the other commands.
- Around line 411-414: The duplicateNotebook command handler is inconsistent:
unlike delete/export/import it does not check a completion status from
duplicateNotebook before tracking analytics. Update duplicateNotebook (and its
callers) to return a boolean or result indicating success/failure, then change
the commands.registerCommand handler for Commands.DuplicateNotebook to await the
result and only call this.analytics.trackEvent({ eventName: 'duplicate_notebook'
}) when the returned value indicates success; reference the duplicateNotebook
method and the Commands.DuplicateNotebook registration to locate where to change
the behavior.

In `@src/platform/analytics/noOpTelemetryService.ts`:
- Around line 7-13: Reorder the methods in the NoOp telemetry implementation so
that dispose comes before trackEvent to satisfy the alphabetical method ordering
rule: move the public async dispose(): Promise<void> method so it appears before
public trackEvent(_event: TelemetryEvent): void (references: dispose,
trackEvent) while preserving their no-op bodies and signatures.

---

Duplicate comments:
In `@src/notebooks/deepnote/deepnoteExplorerView.unit.test.ts`:
- Around line 18-19: Replace the NoOpTelemetryService test double (the const
mockAnalytics = new NoOpTelemetryService()) with an observable/mock telemetry
implementation that records calls (e.g., MockObservableTelemetryService or
ObservableTelemetrySpy) and pass that into the DeepnoteExplorerView constructor
so tests can assert telemetry side effects; update tests that exercise
cancel/early-return and success paths to assert expected telemetry method
invocations (or counts/arguments) instead of using the no-op, and reuse the same
mockTelemetry instance across the tests that currently declare mockAnalytics to
verify cancel vs success branches.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 050a7bcf-bceb-4b18-859f-81e9ede78ceb

📥 Commits

Reviewing files that changed from the base of the PR and between 2cd604d and 8ca48b3.

📒 Files selected for processing (7)
  • src/kernels/deepnote/environments/deepnoteEnvironmentsView.unit.test.ts
  • src/notebooks/deepnote/deepnoteActivationService.unit.test.ts
  • src/notebooks/deepnote/deepnoteExplorerView.ts
  • src/notebooks/deepnote/deepnoteExplorerView.unit.test.ts
  • src/notebooks/deepnote/deepnoteNotebookCommandListener.unit.test.ts
  • src/notebooks/deepnote/openInDeepnoteHandler.node.unit.test.ts
  • src/platform/analytics/noOpTelemetryService.ts

Comment thread src/notebooks/deepnote/deepnoteExplorerView.ts
Comment thread src/notebooks/deepnote/deepnoteExplorerView.ts
Comment thread src/platform/analytics/noOpTelemetryService.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/notebooks/deepnote/openInDeepnoteHandler.node.unit.test.ts (1)

53-53: ⚠️ Potential issue | 🟠 Major

Telemetry is injected but still not asserted.

Line 53 adds ITelemetryService, but this suite still doesn’t verify trackEvent behavior. Add one success-path assertion (trackEvent called) and one failure/early-return assertion (trackEvent not called) around the command callback flow.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/notebooks/deepnote/openInDeepnoteHandler.node.unit.test.ts` at line 53,
The test injects a mocked ITelemetryService into OpenInDeepnoteHandlerClass but
never asserts telemetry calls; update the suite to (1) add a success-path test
that executes the command callback on handler (or calls the method that triggers
the command) with valid inputs and assert the mocked
ITelemetryService.trackEvent was called (with expected event name/props) and (2)
add a failure/early-return test that triggers the early-return branch (e.g.,
invalid input or stubbed dependency to force failure) and assert trackEvent was
not called; reference OpenInDeepnoteHandlerClass, handler, and
ITelemetryService.trackEvent when locating where to add these two assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/notebooks/deepnote/deepnoteActivationService.unit.test.ts`:
- Line 29: Move the suite-level mock creation for mockAnalytics into the test
setup to avoid shared state between tests: instead of const mockAnalytics =
instance(mock<ITelemetryService>()) declared at top-level, initialize a fresh
mock in the beforeEach/setup block (e.g., recreate the mock object and its
instance there) so tests get isolated instances and future verify() calls won't
be affected by accumulated interactions.

In `@src/notebooks/deepnote/deepnoteExplorerView.unit.test.ts`:
- Around line 18-19: The module-global mockAnalytics (created with
instance(mock<ITelemetryService>())) can leak interactions across tests; move
its creation into the test suite setup so each test gets a fresh mock and reset
interactions before each test. Specifically, remove the top-level const
mockAnalytics and instead instantiate or reassign mockAnalytics inside the
beforeEach/setup block (or call a reset/mockClear helper) where the tests
reference mockAnalytics, ensuring any spies/stubs on ITelemetryService are
isolated per test; apply the same change for the other module-global telemetry
mocks referenced in this file.

---

Duplicate comments:
In `@src/notebooks/deepnote/openInDeepnoteHandler.node.unit.test.ts`:
- Line 53: The test injects a mocked ITelemetryService into
OpenInDeepnoteHandlerClass but never asserts telemetry calls; update the suite
to (1) add a success-path test that executes the command callback on handler (or
calls the method that triggers the command) with valid inputs and assert the
mocked ITelemetryService.trackEvent was called (with expected event name/props)
and (2) add a failure/early-return test that triggers the early-return branch
(e.g., invalid input or stubbed dependency to force failure) and assert
trackEvent was not called; reference OpenInDeepnoteHandlerClass, handler, and
ITelemetryService.trackEvent when locating where to add these two assertions.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 81dca3df-aa35-47d0-9a06-534158f2b831

📥 Commits

Reviewing files that changed from the base of the PR and between 8ca48b3 and 282cad6.

📒 Files selected for processing (5)
  • src/kernels/deepnote/environments/deepnoteEnvironmentsView.unit.test.ts
  • src/notebooks/deepnote/deepnoteActivationService.unit.test.ts
  • src/notebooks/deepnote/deepnoteExplorerView.unit.test.ts
  • src/notebooks/deepnote/deepnoteNotebookCommandListener.unit.test.ts
  • src/notebooks/deepnote/openInDeepnoteHandler.node.unit.test.ts

Comment thread src/notebooks/deepnote/deepnoteActivationService.unit.test.ts Outdated
Comment thread src/notebooks/deepnote/deepnoteExplorerView.unit.test.ts Outdated
tkislan added 2 commits April 1, 2026 21:30
- Replaced instances of ITelemetryService with a properly initialized mock in DeepnoteActivationService and DeepnoteExplorerView tests.
- Adjusted NoOpTelemetryService methods to align with the new telemetry service structure.
- Enhanced TelemetryService to ensure proper initialization and event tracking based on telemetry settings.
…ent management

- Updated TelemetryService to implement IExtensionSyncActivationService, allowing for better integration with extension lifecycle events.
- Introduced methods for client creation and destruction based on telemetry settings, improving resource management.
- Adjusted tests to validate the new activation logic and client handling, ensuring proper behavior when telemetry settings change.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
src/platform/analytics/telemetryService.ts (1)

99-118: 🛠️ Refactor suggestion | 🟠 Major

Reorder private methods to match repository ordering rule.

handleConfigChanged should appear before isTelemetryEnabled (same accessibility, alphabetical order).

Suggested reorder
-    private isTelemetryEnabled(): boolean {
-        const telemetryLevel = workspace.getConfiguration('telemetry').get<string>('telemetryLevel', 'all');
-
-        if (telemetryLevel !== 'all') {
-            return false;
-        }
-
-        return workspace.getConfiguration('deepnote').get<boolean>('telemetry.enabled', true);
-    }
-
     private handleConfigChanged(): void {
         if (this.isTelemetryEnabled()) {
             this.createClient();
         } else {
             this.destroyClient().catch((error) => {
                 logger.error(`Failed to destroy PostHog client: ${error}`);
                 this.client = null;
             });
         }
     }
+
+    private isTelemetryEnabled(): boolean {
+        const telemetryLevel = workspace.getConfiguration('telemetry').get<string>('telemetryLevel', 'all');
+
+        if (telemetryLevel !== 'all') {
+            return false;
+        }
+
+        return workspace.getConfiguration('deepnote').get<boolean>('telemetry.enabled', true);
+    }

As per coding guidelines: **/*.{ts,tsx}: Order method, fields and properties, first by accessibility and then by alphabetical order.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/platform/analytics/telemetryService.ts` around lines 99 - 118, Reorder
the two private methods so they follow the repository rule (same accessibility
then alphabetical): move handleConfigChanged to appear before
isTelemetryEnabled; update their order in the class where the methods postHog
client lifecycle is handled (methods: handleConfigChanged and
isTelemetryEnabled) without changing their bodies or behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/platform/analytics/telemetryService.ts`:
- Around line 99-118: Reorder the two private methods so they follow the
repository rule (same accessibility then alphabetical): move handleConfigChanged
to appear before isTelemetryEnabled; update their order in the class where the
methods postHog client lifecycle is handled (methods: handleConfigChanged and
isTelemetryEnabled) without changing their bodies or behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d4951c09-64af-40b4-9784-4dff43fc1eae

📥 Commits

Reviewing files that changed from the base of the PR and between 282cad6 and 21aab9a.

📒 Files selected for processing (6)
  • src/notebooks/deepnote/deepnoteActivationService.unit.test.ts
  • src/notebooks/deepnote/deepnoteExplorerView.unit.test.ts
  • src/platform/analytics/noOpTelemetryService.ts
  • src/platform/analytics/telemetryService.ts
  • src/platform/analytics/telemetryService.unit.test.ts
  • src/platform/serviceRegistry.node.ts

Resolves conflicts from the single-notebook refactor (#429) against the
telemetry PR:

- Explorer/environments/integration/activation constructors: keep both
  sides' injected params (added `analytics: ITelemetryService`).
- DeepnoteExplorerView adopts main's refactored command handlers and
  re-applies telemetry: methods return `boolean` so the command wrappers
  record `{ completed }` (open/create/import/delete/duplicate/export).
- Dropped `delete_project` and project-level `export_notebook` telemetry:
  #429 removed the deleteProject/exportProject commands, methods, and
  package.json menus entirely. Removed the now-unused `delete_project`
  from the TelemetryEventName union.
- deepnoteCellExecutionAnalytics: use main's
  `getProjectForNotebook(projectId, notebookId)` (was `getOriginalProject`).
- package-lock.json: main's lockfile + posthog-node@4.18.0.

typecheck 0, lint 0, compile-tsc 0, unit tests 2502 passing / 0 failing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGod1izfU9yA8ZQfxD5yeZ

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/notebooks/deepnote/deepnoteExplorerView.ts (3)

508-514: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Report the actual export format.

The operation writes an .ipynb, but telemetry records format: 'notebook'.

Proposed fix
-                    properties: { completed, format: 'notebook' }
+                    properties: { completed, format: 'jupyter' }
🤖 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/notebooks/deepnote/deepnoteExplorerView.ts` around lines 508 - 514,
Update the telemetry properties in the Commands.ExportNotebook handler to report
the actual .ipynb export format instead of the generic 'notebook' value, while
preserving the existing completed status and event name.

432-435: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Preserve the failed-open outcome in telemetry.

openNotebook catches errors and resolves normally, so failed opens are always recorded without a failure indicator. Return a boolean and include completed, as with the other commands.

Proposed fix
-            commands.registerCommand(Commands.OpenDeepnoteNotebook, async (context: DeepnoteTreeItemContext) => {
-                await this.openNotebook(context);
-                this.analytics.trackEvent({ eventName: 'open_notebook' });
+            commands.registerCommand(Commands.OpenDeepnoteNotebook, async (context: DeepnoteTreeItemContext) => {
+                const completed = await this.openNotebook(context);
+                this.analytics.trackEvent({ eventName: 'open_notebook', properties: { completed } });
             })
-    private async openNotebook(context: DeepnoteTreeItemContext): Promise<void> {
+    private async openNotebook(context: DeepnoteTreeItemContext): Promise<boolean> {
         try {
             // ...
+            return true;
         } catch (error) {
             // ...
+            return false;
         }
     }
🤖 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/notebooks/deepnote/deepnoteExplorerView.ts` around lines 432 - 435,
Update the OpenDeepnoteNotebook command registration and
DeepnoteExplorerView.openNotebook to return a boolean indicating whether opening
completed successfully; preserve false when the method catches an error, and
pass the returned value as completed in the analytics.trackEvent call while
keeping successful opens true.

1050-1062: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Do not report completion when every import failed.

Both methods return true even when failedCount equals the number selected and zero notebooks were imported.

Proposed fix
-            return true;
+            return numberOfNotebooks > 0;

Also applies to: 1108-1122

🤖 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/notebooks/deepnote/deepnoteExplorerView.ts` around lines 1050 - 1062,
Update the import completion logic around convertJupyterUrisToDeepnoteFiles and
its corresponding flow near the second reported location so it returns false
when failedCount equals the total selected notebooks and no imports succeeded.
Only display success messages, refresh the tree, and return true when at least
one notebook was imported; preserve the existing plural and singular messages
for successful partial or complete imports.
🤖 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.

Outside diff comments:
In `@src/notebooks/deepnote/deepnoteExplorerView.ts`:
- Around line 508-514: Update the telemetry properties in the
Commands.ExportNotebook handler to report the actual .ipynb export format
instead of the generic 'notebook' value, while preserving the existing completed
status and event name.
- Around line 432-435: Update the OpenDeepnoteNotebook command registration and
DeepnoteExplorerView.openNotebook to return a boolean indicating whether opening
completed successfully; preserve false when the method catches an error, and
pass the returned value as completed in the analytics.trackEvent call while
keeping successful opens true.
- Around line 1050-1062: Update the import completion logic around
convertJupyterUrisToDeepnoteFiles and its corresponding flow near the second
reported location so it returns false when failedCount equals the total selected
notebooks and no imports succeeded. Only display success messages, refresh the
tree, and return true when at least one notebook was imported; preserve the
existing plural and singular messages for successful partial or complete
imports.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 580c461d-e8c2-458c-a70f-74a2adb3dcee

📥 Commits

Reviewing files that changed from the base of the PR and between 21aab9a and 8d4d7c4.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (12)
  • package.json
  • src/kernels/deepnote/environments/deepnoteEnvironmentsView.node.ts
  • src/kernels/deepnote/environments/deepnoteEnvironmentsView.unit.test.ts
  • src/notebooks/deepnote/deepnoteActivationService.ts
  • src/notebooks/deepnote/deepnoteCellExecutionAnalytics.ts
  • src/notebooks/deepnote/deepnoteExplorerView.ts
  • src/notebooks/deepnote/deepnoteExplorerView.unit.test.ts
  • src/notebooks/deepnote/integrations/integrationWebview.ts
  • src/notebooks/deepnote/integrations/integrationWebview.unit.test.ts
  • src/notebooks/deepnote/openInDeepnoteHandler.node.ts
  • src/notebooks/serviceRegistry.node.ts
  • src/platform/analytics/types.ts
🚧 Files skipped from review as they are similar to previous changes (9)
  • package.json
  • src/platform/analytics/types.ts
  • src/notebooks/deepnote/deepnoteActivationService.ts
  • src/notebooks/deepnote/openInDeepnoteHandler.node.ts
  • src/notebooks/deepnote/integrations/integrationWebview.ts
  • src/notebooks/serviceRegistry.node.ts
  • src/kernels/deepnote/environments/deepnoteEnvironmentsView.node.ts
  • src/notebooks/deepnote/deepnoteCellExecutionAnalytics.ts
  • src/notebooks/deepnote/deepnoteExplorerView.unit.test.ts

Addresses the coverage gaps and correctness issues found by auditing the
telemetry PR against the merged single-notebook refactor.

Coverage (new events):
- rename_notebook, rename_project (context-menu renames)
- create_notebook now also fires for Add-notebook-to-project, previously an
  untracked path of the same metric
- split_notebook {completed, notebookCount} for the legacy multi-notebook split
- authenticate_integration for the federated-auth (BigQuery OAuth) flow
- switch_sql_integration {integrationType} from the SQL cell status bar
- update_environment {field} for environment rename / package edits
- select_environment now also fires on the first-run kernel picker

Correctness:
- Gate open_notebook / create_notebook / duplicate_notebook / open_in_deepnote
  on success ({completed}) instead of firing on cancel/guard/error paths.
- import_notebook completed now reflects whether anything imported
  (numberOfNotebooks > 0) rather than an unconditional true.
- save/reset/delete_integration fire only after the persist succeeds.
- execute_notebook / add_block(code) fire only for deepnote notebooks.
- export_notebook format label 'notebook' -> 'jupyter'.

Adds the new names to the TelemetryEventName union and injects
ITelemetryService into the splitter, SQL status bar, and kernel auto-selector.

typecheck 0, lint 0, compile-tsc 0, unit tests 2502 passing / 0 failing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGod1izfU9yA8ZQfxD5yeZ

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/notebooks/deepnote/deepnoteExplorerView.ts (1)

421-429: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Do not mark a partial project rename as completed.

Any failedCount still reaches return true, so telemetry reports completion even if every sibling update failed.

Proposed fix
-            return true;
+            return failedCount === 0;
🤖 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/notebooks/deepnote/deepnoteExplorerView.ts` around lines 421 - 429,
Update the project rename result handling around the failedCount check in the
rename method so partial or failed sibling updates do not reach return true.
Return success only when failedCount is zero, while preserving the existing
warning message for failures and information message for a fully successful
rename.
src/notebooks/deepnote/openInDeepnoteHandler.node.ts (1)

108-116: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Handle openExternal() returning false. env.openExternal() can resolve false; treat that as failure so handleOpenInDeepnote() doesn’t report completed: true when the launch is refused.

🤖 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/notebooks/deepnote/openInDeepnoteHandler.node.ts` around lines 108 - 116,
Update handleOpenInDeepnote around the env.openExternal call to inspect its
boolean result and treat false as a failed launch. Only show the success
notification, log successful opening, and return true when openExternal resolves
true; otherwise follow the existing failure path so the handler does not report
completion.

Source: Coding guidelines

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

Outside diff comments:
In `@src/notebooks/deepnote/deepnoteExplorerView.ts`:
- Around line 421-429: Update the project rename result handling around the
failedCount check in the rename method so partial or failed sibling updates do
not reach return true. Return success only when failedCount is zero, while
preserving the existing warning message for failures and information message for
a fully successful rename.

In `@src/notebooks/deepnote/openInDeepnoteHandler.node.ts`:
- Around line 108-116: Update handleOpenInDeepnote around the env.openExternal
call to inspect its boolean result and treat false as a failed launch. Only show
the success notification, log successful opening, and return true when
openExternal resolves true; otherwise follow the existing failure path so the
handler does not report completion.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0a60a750-2f99-4caa-97b3-6f89156297ef

📥 Commits

Reviewing files that changed from the base of the PR and between 8d4d7c4 and f4cc7ac.

📒 Files selected for processing (13)
  • src/kernels/deepnote/environments/deepnoteEnvironmentsView.node.ts
  • src/notebooks/deepnote/deepnoteActivationService.ts
  • src/notebooks/deepnote/deepnoteExplorerView.ts
  • src/notebooks/deepnote/deepnoteKernelAutoSelector.node.ts
  • src/notebooks/deepnote/deepnoteKernelAutoSelector.node.unit.test.ts
  • src/notebooks/deepnote/deepnoteMultiNotebookSplitter.ts
  • src/notebooks/deepnote/deepnoteMultiNotebookSplitter.unit.test.ts
  • src/notebooks/deepnote/integrations/integrationWebview.ts
  • src/notebooks/deepnote/openInDeepnoteHandler.node.ts
  • src/notebooks/deepnote/sqlCellStatusBarProvider.ts
  • src/notebooks/deepnote/sqlCellStatusBarProvider.unit.test.ts
  • src/notebooks/notebookCommandListener.ts
  • src/platform/analytics/types.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/notebooks/deepnote/deepnoteActivationService.ts
  • src/kernels/deepnote/environments/deepnoteEnvironmentsView.node.ts

tkislan and others added 2 commits July 17, 2026 14:35
leaveUnsavedCellEdit located the first code cell's view-line, then clicked
it in a separate step. A freshly opened Deepnote notebook re-renders its
cells for a beat (status-bar items, kernel wiring), which can invalidate
that element reference before the click lands, failing the before-all hook
with StaleElementReferenceError. Locate and click in one waited step and
retry on failure, so a re-render between locating and clicking no longer
breaks it.

Verified: the projectRename suite passes 3/3 locally against the packaged
VSIX built from the current source.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGod1izfU9yA8ZQfxD5yeZ
The __POSTHOG_API_KEY__ placeholder shipped unreplaced, so PostHog
initialized with an invalid key and dropped all events. CD now injects
the POSTHOG_API_KEY secret at build time via an esbuild define, falling
back to the inert placeholder for local builds.

Also address review feedback:
- extract add_block and integration tracking into helpers; all
  integration events now send a consistent integrationType
- localize the deepnote.telemetry.enabled setting description

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGod1izfU9yA8ZQfxD5yeZ
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant