From e5d616b0bfea2b3ae25c3147551dd1854aba77d3 Mon Sep 17 00:00:00 2001 From: Trevor Hudson Date: Tue, 25 Feb 2025 16:43:15 -0800 Subject: [PATCH] add optional telemetry (#1939) * add optional telemetry * move capture * add changeset * implement suggestions --- package-lock.json | 12 ++-- package.json | 2 +- src/core/Cline.ts | 19 +++++ src/core/webview/ClineProvider.ts | 3 +- src/services/telemetry/TelemetryService.ts | 3 +- src/shared/ExtensionMessage.ts | 1 + webview-ui/package-lock.json | 70 ++++++++++++++++++- webview-ui/package.json | 1 + webview-ui/src/App.tsx | 12 +++- webview-ui/src/components/chat/ChatView.tsx | 3 +- .../src/context/ExtensionStateContext.tsx | 1 + webview-ui/src/main.tsx | 9 ++- 12 files changed, 122 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0dae4ee453..9d6304649e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "claude-dev", - "version": "3.4.8", + "version": "3.4.9", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "claude-dev", - "version": "3.4.8", + "version": "3.4.9", "license": "Apache-2.0", "dependencies": { "@anthropic-ai/bedrock-sdk": "^0.10.2", @@ -41,7 +41,7 @@ "os-name": "^6.0.0", "p-wait-for": "^5.0.2", "pdf-parse": "^1.1.1", - "posthog-node": "^4.7.0", + "posthog-node": "^4.8.1", "puppeteer-chromium-resolver": "^23.0.0", "puppeteer-core": "^23.4.0", "serialize-error": "^11.0.3", @@ -11247,9 +11247,9 @@ } }, "node_modules/posthog-node": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-4.7.0.tgz", - "integrity": "sha512-RgdUKSW8MfMOkjUa8cYVqWndNjPePNuuxlGbrZC6z1WRBsVc6TdGl8caidmC10RW8mu/BOfmrGbP4cRTo2jARg==", + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-4.8.1.tgz", + "integrity": "sha512-ApMEC1+DbctP/88+VhaCl8SRKpIoReibMf7Mb3rxw3yMthr1rKaM4opbHdZJ0buLhwS5zX8B2ckqLjpwpSjRPg==", "license": "MIT", "dependencies": { "axios": "^1.7.4" diff --git a/package.json b/package.json index fc5d55130c..64c5ce2b5e 100644 --- a/package.json +++ b/package.json @@ -294,7 +294,7 @@ "os-name": "^6.0.0", "p-wait-for": "^5.0.2", "pdf-parse": "^1.1.1", - "posthog-node": "^4.7.0", + "posthog-node": "^4.8.1", "puppeteer-chromium-resolver": "^23.0.0", "puppeteer-core": "^23.4.0", "serialize-error": "^11.0.3", diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 1a5c058db9..9066a7fbf7 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -60,6 +60,7 @@ import { addUserInstructions, SYSTEM_PROMPT } from "./prompts/system" import { getNextTruncationRange, getTruncatedMessages } from "./sliding-window" import { ClineProvider, GlobalFileNames } from "./webview/ClineProvider" import { DEFAULT_LANGUAGE_SETTINGS, getLanguageKey, LanguageDisplay, LanguageKey } from "../shared/Languages" +import { telemetryService } from "../services/telemetry/TelemetryService" const cwd = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0) ?? path.join(os.homedir(), "Desktop") // may or may not exist but fs checking existence would immediately ask for permission which would be bad UX, need to come up with a better solution @@ -153,6 +154,16 @@ export class Cline { } else { throw new Error("Either historyItem or task/images must be provided") } + // capture start of thread with the state at the beginning + telemetryService.capture({ + event: "cline created", + properties: { + taskId: this.taskId, + isHistory: !!historyItem, + chatMode: this.chatSettings.mode, + hasImages: !!images, + }, + }) } updateBrowserSettings(browserSettings: BrowserSettings) { @@ -3284,6 +3295,14 @@ export class Cline { this.consecutiveMistakeCount++ } + telemetryService.capture({ + event: "message sent", + properties: { + taskId: this.taskId, + chatMode: this.chatSettings.mode, + }, + }) + const recDidEndLoop = await this.recursivelyMakeClineRequests(this.userMessageContent) didEndLoop = recDidEndLoop } else { diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index e0d8121763..77e3a6691c 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -387,9 +387,9 @@ export class ClineProvider implements vscode.WebviewViewProvider { - + Cline @@ -1745,6 +1745,7 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont userInfo, mcpMarketplaceEnabled, telemetrySetting, + vscMachineId: vscode.env.machineId, } } diff --git a/src/services/telemetry/TelemetryService.ts b/src/services/telemetry/TelemetryService.ts index 19dcf0829b..b4fbd063c8 100644 --- a/src/services/telemetry/TelemetryService.ts +++ b/src/services/telemetry/TelemetryService.ts @@ -26,9 +26,10 @@ class PostHogClient { this.telemetryEnabled = didUserOptIn } - // Update PostHog client state based on telemetry preference + // Update PostHog client state based on telemetry preference and use machineId to tie it to the webview if (this.telemetryEnabled) { this.client.optIn() + this.client.identify({ distinctId: this.distinctId }) // console.log("Telemetry enabled") } else { this.client.optOut() diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index bcad63b788..8924e7adad 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -95,6 +95,7 @@ export interface ExtensionState { } mcpMarketplaceEnabled?: boolean telemetrySetting: TelemetrySetting + vscMachineId: string } export interface ClineMessage { diff --git a/webview-ui/package-lock.json b/webview-ui/package-lock.json index 0a5b09fee8..b045f714fb 100644 --- a/webview-ui/package-lock.json +++ b/webview-ui/package-lock.json @@ -17,6 +17,7 @@ "fuse.js": "^7.0.0", "fzf": "^0.5.2", "mermaid": "^11.4.1", + "posthog-js": "^1.224.0", "pretty-bytes": "^6.1.1", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -1468,6 +1469,16 @@ "win32" ] }, + "node_modules/@rrweb/types": { + "version": "2.0.0-alpha.17", + "resolved": "https://registry.npmjs.org/@rrweb/types/-/types-2.0.0-alpha.17.tgz", + "integrity": "sha512-AfDTVUuCyCaIG0lTSqYtrZqJX39ZEYzs4fYKnexhQ+id+kbZIpIJtaut5cto6dWZbB3SEe4fW0o90Po3LvTmfg==", + "license": "MIT", + "peer": true, + "dependencies": { + "rrweb-snapshot": "^2.0.0-alpha.17" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -3024,6 +3035,17 @@ "toggle-selection": "^1.0.6" } }, + "node_modules/core-js": { + "version": "3.40.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.40.0.tgz", + "integrity": "sha512-7vsMc/Lty6AGnn7uFpYT56QesI5D2Y/UkgKounk87OP9Z2H9Z8kj6jzcSGAxFmUtDOS0ntK6lbQz+Nsa0Jj6mQ==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/cose-base": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", @@ -4201,6 +4223,12 @@ "reusify": "^1.0.4" } }, + "node_modules/fflate": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz", + "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==", + "license": "MIT" + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -5799,7 +5827,6 @@ "version": "8.5.3", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", - "dev": true, "funding": [ { "type": "opencollective", @@ -5830,6 +5857,37 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "license": "MIT" }, + "node_modules/posthog-js": { + "version": "1.224.0", + "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.224.0.tgz", + "integrity": "sha512-JT1XQQeYs0CKb4lU2vujmeLTDLWc61I5lT7d6oG/H/cnCpXAqBi5rMuCFFeotHeMy3hqJ/Tpu3eAPFE2p5ErHA==", + "license": "MIT", + "dependencies": { + "core-js": "^3.38.1", + "fflate": "^0.4.8", + "preact": "^10.19.3", + "web-vitals": "^4.2.0" + }, + "peerDependencies": { + "@rrweb/types": "2.0.0-alpha.17" + } + }, + "node_modules/posthog-js/node_modules/web-vitals": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz", + "integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==", + "license": "Apache-2.0" + }, + "node_modules/preact": { + "version": "10.26.2", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.26.2.tgz", + "integrity": "sha512-0gNmv4qpS9HaN3+40CLBAnKe0ZfyE4ZWo5xKlC1rVrr0ckkEvJvAQqKaHANdFKsGstoxrY4AItZ7kZSGVoVjgg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -6214,6 +6272,16 @@ "dev": true, "license": "MIT" }, + "node_modules/rrweb-snapshot": { + "version": "2.0.0-alpha.18", + "resolved": "https://registry.npmjs.org/rrweb-snapshot/-/rrweb-snapshot-2.0.0-alpha.18.tgz", + "integrity": "sha512-hBHZL/NfgQX6wO1D9mpwqFu1NJPpim+moIcKhFEjVTZVRUfCln+LOugRc4teVTCISYHN8Cw5e2iNTWCSm+SkoA==", + "license": "MIT", + "peer": true, + "dependencies": { + "postcss": "^8.4.38" + } + }, "node_modules/rtl-css-js": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/rtl-css-js/-/rtl-css-js-1.16.1.tgz", diff --git a/webview-ui/package.json b/webview-ui/package.json index e0d70cf37f..82055bb9db 100644 --- a/webview-ui/package.json +++ b/webview-ui/package.json @@ -21,6 +21,7 @@ "fuse.js": "^7.0.0", "fzf": "^0.5.2", "mermaid": "^11.4.1", + "posthog-js": "^1.224.0", "pretty-bytes": "^6.1.1", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/webview-ui/src/App.tsx b/webview-ui/src/App.tsx index 0043ef330b..a21e68b3db 100644 --- a/webview-ui/src/App.tsx +++ b/webview-ui/src/App.tsx @@ -9,9 +9,10 @@ import AccountView from "./components/account/AccountView" import { ExtensionStateContextProvider, useExtensionState } from "./context/ExtensionStateContext" import { vscode } from "./utils/vscode" import McpView from "./components/mcp/McpView" +import posthog from "posthog-js" const AppContent = () => { - const { didHydrateState, showWelcome, shouldShowAnnouncement } = useExtensionState() + const { didHydrateState, showWelcome, shouldShowAnnouncement, telemetrySetting, vscMachineId } = useExtensionState() const [showSettings, setShowSettings] = useState(false) const [showHistory, setShowHistory] = useState(false) const [showMcp, setShowMcp] = useState(false) @@ -60,6 +61,15 @@ const AppContent = () => { useEvent("message", handleMessage) + useEffect(() => { + if (telemetrySetting === "enabled") { + posthog.identify(vscMachineId) + posthog.opt_in_capturing() + } else { + posthog.opt_out_capturing() + } + }, [telemetrySetting, vscMachineId]) + useEffect(() => { if (shouldShowAnnouncement) { setShowAnnouncement(true) diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index afe84601ed..7a74d2c178 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -1,4 +1,4 @@ -import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react" +import { VSCodeButton } from "@vscode/webview-ui-toolkit/react" import debounce from "debounce" import { useCallback, useEffect, useMemo, useRef, useState } from "react" import { useDeepCompareEffect, useEvent, useMount } from "react-use" @@ -793,6 +793,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie {telemetrySetting === "unset" && } {showAnnouncement && } +

What can I do for you?

diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index f3d0619183..aba1836c54 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -42,6 +42,7 @@ export const ExtensionStateContextProvider: React.FC<{ isLoggedIn: false, platform: DEFAULT_PLATFORM, telemetrySetting: "unset", + vscMachineId: "", }) const [didHydrateState, setDidHydrateState] = useState(false) const [showWelcome, setShowWelcome] = useState(false) diff --git a/webview-ui/src/main.tsx b/webview-ui/src/main.tsx index b0094ef51c..a6cefc5bd2 100644 --- a/webview-ui/src/main.tsx +++ b/webview-ui/src/main.tsx @@ -1,12 +1,17 @@ import { StrictMode } from "react" import { createRoot } from "react-dom/client" - +import { PostHogProvider } from "posthog-js/react" import "./index.css" import App from "./App.tsx" import "../../node_modules/@vscode/codicons/dist/codicon.css" +const apiKey = "phc_5WnLHpYyC30Bsb7VSJ6DzcPXZ34JSF08DJLyM7svZ15" +const apiHost = "https://us.i.posthog.com" + createRoot(document.getElementById("root")!).render( - + + + , )