Skip to content

Commit e14fd78

Browse files
committed
Use GUI telemetry toggle to more clearly show user if they're opted in or not
1 parent 8c5f8b2 commit e14fd78

File tree

10 files changed

+90
-45
lines changed

10 files changed

+90
-45
lines changed

package.json

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -186,11 +186,6 @@
186186
"type": "boolean",
187187
"default": true,
188188
"description": "Controls whether the MCP Marketplace is enabled."
189-
},
190-
"cline.enableTelemetry": {
191-
"type": "boolean",
192-
"default": null,
193-
"markdownDescription": "Allow anonymous usage and error reporting to help improve Cline. No code, prompts, or personal information is ever sent. See our [privacy policy](https://github.com/cline/cline/blob/main/docs/PRIVACY.md) for details."
194189
}
195190
}
196191
}

src/core/webview/ClineProvider.ts

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import { openMention } from "../mentions"
3232
import { getNonce } from "./getNonce"
3333
import { getUri } from "./getUri"
3434
import { telemetryService } from "../../services/telemetry/TelemetryService"
35+
import { TelemetrySetting } from "../../shared/TelemetrySetting"
3536

3637
/*
3738
https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
@@ -93,6 +94,7 @@ type GlobalStateKey =
9394
| "requestyModelId"
9495
| "togetherModelId"
9596
| "mcpMarketplaceCatalog"
97+
| "telemetrySetting"
9698

9799
export const GlobalFileNames = {
98100
apiConversationHistory: "api_conversation_history.json",
@@ -454,6 +456,13 @@ export class ClineProvider implements vscode.WebviewViewProvider {
454456
}
455457
})
456458

459+
// If user already opted in to telemetry, enable telemetry service
460+
this.getStateToPostToWebview().then((state) => {
461+
const { telemetrySetting } = state
462+
const isOptedIn = telemetrySetting === "enabled"
463+
telemetryService.updateTelemetryState(isOptedIn)
464+
})
465+
457466
break
458467
case "newTask":
459468
// Code that should run in response to the hello message command
@@ -849,18 +858,19 @@ export class ClineProvider implements vscode.WebviewViewProvider {
849858
break
850859
}
851860
// telemetry
852-
case "openTelemetrySettings": {
853-
await vscode.commands.executeCommand(
854-
"workbench.action.openSettings",
855-
"@ext:saoudrizwan.claude-dev cline.telemetryOptIn",
856-
)
861+
case "openSettings": {
862+
await this.postMessageToWebview({
863+
type: "action",
864+
action: "settingsButtonClicked",
865+
})
857866
break
858867
}
859-
case "telemetryOptIn": {
860-
if (message.bool !== undefined) {
861-
await vscode.workspace.getConfiguration("cline").update("enableTelemetry", message.bool, true)
862-
await this.postStateToWebview()
863-
}
868+
case "telemetrySetting": {
869+
const telemetrySetting = message.text as TelemetrySetting
870+
await this.updateGlobalState("telemetrySetting", telemetrySetting)
871+
const isOptedIn = telemetrySetting === "enabled"
872+
telemetryService.updateTelemetryState(isOptedIn)
873+
await this.postStateToWebview()
864874
break
865875
}
866876
// Add more switch case statements here as more webview message commands
@@ -1628,7 +1638,7 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
16281638
userInfo,
16291639
authToken,
16301640
mcpMarketplaceEnabled,
1631-
telemetryOptIn,
1641+
telemetrySetting,
16321642
} = await this.getState()
16331643

16341644
return {
@@ -1648,7 +1658,7 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
16481658
isLoggedIn: !!authToken,
16491659
userInfo,
16501660
mcpMarketplaceEnabled,
1651-
telemetryOptIn,
1661+
telemetrySetting,
16521662
}
16531663
}
16541664

@@ -1755,6 +1765,7 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
17551765
previousModeModelInfo,
17561766
qwenApiLine,
17571767
liteLlmApiKey,
1768+
telemetrySetting,
17581769
] = await Promise.all([
17591770
this.getGlobalState("apiProvider") as Promise<ApiProvider | undefined>,
17601771
this.getGlobalState("apiModelId") as Promise<string | undefined>,
@@ -1806,6 +1817,7 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
18061817
this.getGlobalState("previousModeModelInfo") as Promise<ModelInfo | undefined>,
18071818
this.getGlobalState("qwenApiLine") as Promise<string | undefined>,
18081819
this.getSecret("liteLlmApiKey") as Promise<string | undefined>,
1820+
this.getGlobalState("telemetrySetting") as Promise<TelemetrySetting | undefined>,
18091821
])
18101822

18111823
let apiProvider: ApiProvider
@@ -1827,7 +1839,6 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
18271839
.get("reasoningEffort", "medium")
18281840

18291841
const mcpMarketplaceEnabled = vscode.workspace.getConfiguration("cline").get<boolean>("mcpMarketplace.enabled", true)
1830-
const telemetryOptIn = vscode.workspace.getConfiguration("cline").get<boolean | null>("enableTelemetry", null)
18311842

18321843
return {
18331844
apiConfiguration: {
@@ -1884,7 +1895,7 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
18841895
previousModeModelId,
18851896
previousModeModelInfo,
18861897
mcpMarketplaceEnabled,
1887-
telemetryOptIn,
1898+
telemetrySetting: telemetrySetting || "unset",
18881899
}
18891900
}
18901901

src/services/telemetry/TelemetryService.ts

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,29 +12,18 @@ class PostHogClient {
1212
host: "https://us.i.posthog.com",
1313
enableExceptionAutocapture: false,
1414
})
15-
16-
// Initialize telemetry state based on user settings
17-
this.updateTelemetryState()
18-
19-
// Listen for settings changes
20-
vscode.workspace.onDidChangeConfiguration((e) => {
21-
if (e.affectsConfiguration("cline.enableTelemetry") || e.affectsConfiguration("telemetry.telemetryLevel")) {
22-
this.updateTelemetryState()
23-
}
24-
})
2515
}
2616

27-
private updateTelemetryState(): void {
17+
public updateTelemetryState(didUserOptIn: boolean): void {
2818
this.telemetryEnabled = false
2919

3020
// First check global telemetry level - telemetry should only be enabled when level is "all"
3121
const telemetryLevel = vscode.workspace.getConfiguration("telemetry").get<string>("telemetryLevel", "all")
3222
const globalTelemetryEnabled = telemetryLevel === "all"
3323

34-
// Only check Cline setting if global telemetry is enabled
24+
// We only enable telemetry if global vscode telemetry is enabled
3525
if (globalTelemetryEnabled) {
36-
const clineOptIn = vscode.workspace.getConfiguration("cline").get<boolean | null>("enableTelemetry", null)
37-
this.telemetryEnabled = clineOptIn === true
26+
this.telemetryEnabled = didUserOptIn
3827
}
3928

4029
// Update PostHog client state based on telemetry preference

src/shared/ExtensionMessage.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { BrowserSettings } from "./BrowserSettings"
77
import { ChatSettings } from "./ChatSettings"
88
import { HistoryItem } from "./HistoryItem"
99
import { McpServer, McpMarketplaceCatalog, McpMarketplaceItem, McpDownloadResponse } from "./mcp"
10+
import { TelemetrySetting } from "./TelemetrySetting"
1011

1112
// webview will hold state
1213
export interface ExtensionMessage {
@@ -81,7 +82,7 @@ export interface ExtensionState {
8182
photoURL: string | null
8283
}
8384
mcpMarketplaceEnabled?: boolean
84-
telemetryOptIn: boolean | null
85+
telemetrySetting: TelemetrySetting
8586
}
8687

8788
export interface ClineMessage {

src/shared/TelemetrySetting.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export type TelemetrySetting = "unset" | "enabled" | "disabled"

src/shared/WebviewMessage.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ export interface WebviewMessage {
5050
| "searchCommits"
5151
| "showMcpView"
5252
| "fetchLatestMcpServersFromHub"
53-
| "telemetryOptIn"
54-
| "openTelemetrySettings"
53+
| "telemetrySetting"
54+
| "openSettings"
5555
// | "relaunchChromeDebugMode"
5656
text?: string
5757
disabled?: boolean

webview-ui/src/components/chat/ChatView.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ interface ChatViewProps {
3838
export const MAX_IMAGES_PER_MESSAGE = 20 // Anthropic limits to 20 images
3939

4040
const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryView }: ChatViewProps) => {
41-
const { version, clineMessages: messages, taskHistory, apiConfiguration, telemetryOptIn } = useExtensionState()
41+
const { version, clineMessages: messages, taskHistory, apiConfiguration, telemetrySetting } = useExtensionState()
4242

4343
//const task = messages.length > 0 ? (messages[0].say === "task" ? messages[0] : undefined) : undefined) : undefined
4444
const task = useMemo(() => messages.at(0), [messages]) // leaving this less safe version here since if the first message is not a task, then the extension is in a bad state and needs to be debugged (see Cline.abort)
@@ -790,7 +790,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
790790
flexDirection: "column",
791791
paddingBottom: "10px",
792792
}}>
793-
{telemetryOptIn === null && <TelemetryBanner />}
793+
{telemetrySetting === "unset" && <TelemetryBanner />}
794794

795795
{showAnnouncement && <Announcement version={version} hideAnnouncement={hideAnnouncement} />}
796796
<div style={{ padding: "0 20px", flexShrink: 0 }}>

webview-ui/src/components/common/TelemetryBanner.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
22
import { memo, useState } from "react"
33
import styled from "styled-components"
44
import { vscode } from "../../utils/vscode"
5+
import { TelemetrySetting } from "../../../../src/shared/TelemetrySetting"
56

67
const BannerContainer = styled.div`
78
background-color: var(--vscode-banner-background);
@@ -10,6 +11,7 @@ const BannerContainer = styled.div`
1011
flex-direction: column;
1112
gap: 10px;
1213
flex-shrink: 0;
14+
margin-bottom: 6px;
1315
`
1416

1517
const ButtonContainer = styled.div`
@@ -27,16 +29,16 @@ const TelemetryBanner = () => {
2729

2830
const handleAllow = () => {
2931
setHasChosen(true)
30-
vscode.postMessage({ type: "telemetryOptIn", bool: true })
32+
vscode.postMessage({ type: "telemetrySetting", text: "enabled" satisfies TelemetrySetting })
3133
}
3234

3335
const handleDeny = () => {
3436
setHasChosen(true)
35-
vscode.postMessage({ type: "telemetryOptIn", bool: false })
37+
vscode.postMessage({ type: "telemetrySetting", text: "disabled" satisfies TelemetrySetting })
3638
}
3739

3840
const handleOpenSettings = () => {
39-
vscode.postMessage({ type: "openTelemetrySettings" })
41+
vscode.postMessage({ type: "openSettings" })
4042
}
4143

4244
return (

webview-ui/src/components/settings/SettingsView.tsx

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,26 @@
1-
import { VSCodeButton, VSCodeLink, VSCodeTextArea } from "@vscode/webview-ui-toolkit/react"
1+
import { VSCodeButton, VSCodeCheckbox, VSCodeLink, VSCodeTextArea } from "@vscode/webview-ui-toolkit/react"
22
import { memo, useEffect, useState } from "react"
33
import { useExtensionState } from "../../context/ExtensionStateContext"
44
import { validateApiConfiguration, validateModelId } from "../../utils/validate"
55
import { vscode } from "../../utils/vscode"
6-
import ApiOptions from "./ApiOptions"
76
import SettingsButton from "../common/SettingsButton"
7+
import ApiOptions from "./ApiOptions"
88
const { IS_DEV } = process.env
99

1010
type SettingsViewProps = {
1111
onDone: () => void
1212
}
1313

1414
const SettingsView = ({ onDone }: SettingsViewProps) => {
15-
const { apiConfiguration, version, customInstructions, setCustomInstructions, openRouterModels } = useExtensionState()
15+
const {
16+
apiConfiguration,
17+
version,
18+
customInstructions,
19+
setCustomInstructions,
20+
openRouterModels,
21+
telemetrySetting,
22+
setTelemetrySetting,
23+
} = useExtensionState()
1624
const [apiErrorMessage, setApiErrorMessage] = useState<string | undefined>(undefined)
1725
const [modelIdErrorMessage, setModelIdErrorMessage] = useState<string | undefined>(undefined)
1826

@@ -29,6 +37,10 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
2937
type: "customInstructions",
3038
text: customInstructions,
3139
})
40+
vscode.postMessage({
41+
type: "telemetrySetting",
42+
text: telemetrySetting,
43+
})
3244
onDone()
3345
}
3446
}
@@ -114,6 +126,33 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
114126
</p>
115127
</div>
116128

129+
<div style={{ marginBottom: 5 }}>
130+
<VSCodeCheckbox
131+
style={{ marginBottom: "5px" }}
132+
checked={telemetrySetting === "enabled"}
133+
onChange={(e: any) => {
134+
const checked = e.target.checked === true
135+
setTelemetrySetting(checked ? "enabled" : "disabled")
136+
}}>
137+
Allow anonymous error and usage reporting
138+
</VSCodeCheckbox>
139+
<p
140+
style={{
141+
fontSize: "12px",
142+
marginTop: "5px",
143+
color: "var(--vscode-descriptionForeground)",
144+
}}>
145+
Help improve Cline by sending anonymous usage data and error reports. No code, prompts, or personal
146+
information is ever sent. See our{" "}
147+
<VSCodeLink
148+
href="https://github.com/cline/cline/blob/main/docs/PRIVACY.md"
149+
style={{ fontSize: "inherit" }}>
150+
privacy policy
151+
</VSCodeLink>{" "}
152+
for more details.
153+
</p>
154+
</div>
155+
117156
{IS_DEV && (
118157
<>
119158
<div style={{ marginTop: "10px", marginBottom: "4px" }}>Debug</div>

webview-ui/src/context/ExtensionStateContext.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { convertTextMateToHljs } from "../utils/textMateToHljs"
99
import { vscode } from "../utils/vscode"
1010
import { DEFAULT_BROWSER_SETTINGS } from "../../../src/shared/BrowserSettings"
1111
import { DEFAULT_CHAT_SETTINGS } from "../../../src/shared/ChatSettings"
12+
import { TelemetrySetting } from "../../../src/shared/TelemetrySetting"
1213

1314
interface ExtensionStateContextType extends ExtensionState {
1415
didHydrateState: boolean
@@ -21,6 +22,7 @@ interface ExtensionStateContextType extends ExtensionState {
2122
filePaths: string[]
2223
setApiConfiguration: (config: ApiConfiguration) => void
2324
setCustomInstructions: (value?: string) => void
25+
setTelemetrySetting: (value: TelemetrySetting) => void
2426
setShowAnnouncement: (value: boolean) => void
2527
}
2628

@@ -39,7 +41,7 @@ export const ExtensionStateContextProvider: React.FC<{
3941
chatSettings: DEFAULT_CHAT_SETTINGS,
4042
isLoggedIn: false,
4143
platform: DEFAULT_PLATFORM,
42-
telemetryOptIn: null,
44+
telemetrySetting: "unset",
4345
})
4446
const [didHydrateState, setDidHydrateState] = useState(false)
4547
const [showWelcome, setShowWelcome] = useState(false)
@@ -158,6 +160,11 @@ export const ExtensionStateContextProvider: React.FC<{
158160
...prevState,
159161
customInstructions: value,
160162
})),
163+
setTelemetrySetting: (value) =>
164+
setState((prevState) => ({
165+
...prevState,
166+
telemetrySetting: value,
167+
})),
161168
setShowAnnouncement: (value) =>
162169
setState((prevState) => ({
163170
...prevState,

0 commit comments

Comments
 (0)