From c6d210902b3717bd705434b8f361663419706aca Mon Sep 17 00:00:00 2001 From: Mathew Pareles Date: Sat, 21 Jun 2025 03:40:37 -0700 Subject: [PATCH] start adding toggle metrics --- .../void/browser/react/src/util/services.tsx | 31 +++++++++++++++- .../react/src/void-settings-tsx/Settings.tsx | 35 ++++++++++++++++--- .../contrib/void/common/metricsService.ts | 6 ++++ .../contrib/void/common/storageKeys.ts | 4 +++ .../contrib/void/common/voidSettingsTypes.ts | 3 -- .../void/electron-main/metricsMainService.ts | 23 +++++++++++- 6 files changed, 93 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx b/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx index dc67784c..a19abac4 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx @@ -5,7 +5,7 @@ import React, { useState, useEffect, useCallback } from 'react' import { MCPUserState, RefreshableProviderName, SettingsOfProvider } from '../../../../../../../workbench/contrib/void/common/voidSettingsTypes.js' -import { IDisposable } from '../../../../../../../base/common/lifecycle.js' +import { DisposableStore, IDisposable } from '../../../../../../../base/common/lifecycle.js' import { VoidSettingsState } from '../../../../../../../workbench/contrib/void/common/voidSettingsService.js' import { ColorScheme } from '../../../../../../../platform/theme/common/theme.js' import { RefreshModelStateOfProvider } from '../../../../../../../workbench/contrib/void/common/refreshModelService.js' @@ -52,6 +52,8 @@ import { ITerminalService } from '../../../../../terminal/browser/terminal.js' import { ISearchService } from '../../../../../../services/search/common/search.js' import { IExtensionManagementService } from '../../../../../../../platform/extensionManagement/common/extensionManagement.js' import { IMCPService } from '../../../../common/mcpService.js'; +import { IStorageService, StorageScope } from '../../../../../../../platform/storage/common/storage.js' +import { OPT_OUT_KEY } from '../../../../common/storageKeys.js' // normally to do this you'd use a useEffect that calls .onDidChangeState(), but useEffect mounts too late and misses initial state changes @@ -226,6 +228,8 @@ const getReactAccessor = (accessor: ServicesAccessor) => { IExtensionTransferService: accessor.get(IExtensionTransferService), IMCPService: accessor.get(IMCPService), + IStorageService: accessor.get(IStorageService), + } as const return reactAccessor } @@ -399,3 +403,28 @@ export const useMCPServiceState = () => { return s } + + +export const useIsOptedOut = () => { + const accessor = useAccessor() + const storageService = accessor.get('IStorageService') + + const getVal = useCallback(() => { + return storageService.getBoolean(OPT_OUT_KEY, StorageScope.APPLICATION, false) + }, [storageService]) + + const [s, ss] = useState(getVal()) + + useEffect(() => { + const disposables = new DisposableStore(); + const d = storageService.onDidChangeValue(StorageScope.APPLICATION, OPT_OUT_KEY, disposables)(e => { + console.log('VALUE!!!!!!!!', e.target, getVal()) + ss(getVal()) + + }) + disposables.add(d) + return () => disposables.clear() + }, [storageService, getVal]) + + return s +} diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx index c8ba4da4..9c6d9c3b 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx @@ -7,7 +7,7 @@ import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react' import { ProviderName, SettingName, displayInfoOfSettingName, providerNames, VoidStatefulModelInfo, customSettingNamesOfProvider, RefreshableProviderName, refreshableProviderNames, displayInfoOfProviderName, nonlocalProviderNames, localProviderNames, GlobalSettingName, featureNames, displayInfoOfFeatureName, isProviderNameDisabled, FeatureName, hasDownloadButtonsOnModelsProviderNames, subTextMdOfProviderName } from '../../../../common/voidSettingsTypes.js' import ErrorBoundary from '../sidebar-tsx/ErrorBoundary.js' import { VoidButtonBgDarken, VoidCustomDropdownBox, VoidInputBox2, VoidSimpleInputBox, VoidSwitch } from '../util/inputs.js' -import { useAccessor, useIsDark, useRefreshModelListener, useRefreshModelState, useSettingsState } from '../util/services.js' +import { useAccessor, useIsDark, useIsOptedOut, useRefreshModelListener, useRefreshModelState, useSettingsState } from '../util/services.js' import { X, RefreshCw, Loader2, Check, Asterisk, Plus } from 'lucide-react' import { URI } from '../../../../../../../base/common/uri.js' import { ModelDropdown } from './ModelDropdown.js' @@ -21,6 +21,8 @@ import { getModelCapabilities, modelOverrideKeys, ModelOverrides } from '../../. import { TransferEditorType, TransferFilesInfo } from '../../../extensionTransferTypes.js'; import { MCPServer } from '../../../../common/mcpServiceTypes.js'; import { useMCPServiceState } from '../util/services.js'; +import { OPT_OUT_KEY } from '../../../../common/storageKeys.js'; +import { StorageScope, StorageTarget } from '../../../../../../../platform/storage/common/storage.js'; type Tab = | 'models' @@ -215,10 +217,10 @@ const SimpleModelSettingsDialog = ({ if (!isOpen || !modelInfo) return null; const { modelName, providerName, type } = modelInfo; - const accessor = useAccessor(); - const settingsState = useSettingsState(); + const accessor = useAccessor() + const settingsState = useSettingsState() const mouseDownInsideModal = useRef(false); // Ref to track mousedown origin - const settingsStateService = accessor.get('IVoidSettingsService'); + const settingsStateService = accessor.get('IVoidSettingsService') // current overrides and defaults const defaultModelCapabilities = getModelCapabilities(providerName, modelName, undefined); @@ -1052,6 +1054,8 @@ export const Settings = () => { const chatThreadsService = accessor.get('IChatThreadService') const notificationService = accessor.get('INotificationService') const mcpService = accessor.get('IMCPService') + const storageService = accessor.get('IStorageService') + const isOptedOut = useIsOptedOut() const onDownload = (t: 'Chats' | 'Settings') => { let dataStr: string @@ -1387,6 +1391,29 @@ export const Settings = () => { {/* General section */}
+ {/* Telemetry section */} +
+

Telemetry

+
Control what usage data Void collects.
+ +
+ {/* Disable All Telemetry Switch */} + +
+ storageService.store(OPT_OUT_KEY, newVal, StorageScope.APPLICATION, StorageTarget.MACHINE)} + /> + {storageService.getBoolean(OPT_OUT_KEY, StorageScope.APPLICATION, false) ? 'Disabled' : 'Enabled'} +
+
+ Void only tracks basic usage like whether you've opened the app today so we can understand usage. Opting out is optional (requires a restart), but without this we cannot see how many people are using Void. Regardless of this setting, Void never sees your code, messages, or API keys. +
+
+
+
+ {/* One-Click Switch section */}
diff --git a/src/vs/workbench/contrib/void/common/metricsService.ts b/src/vs/workbench/contrib/void/common/metricsService.ts index f51b64eb..853ca955 100644 --- a/src/vs/workbench/contrib/void/common/metricsService.ts +++ b/src/vs/workbench/contrib/void/common/metricsService.ts @@ -14,6 +14,7 @@ import { INotificationService } from '../../../../platform/notification/common/n export interface IMetricsService { readonly _serviceBrand: undefined; capture(event: string, params: Record): void; + setOptOut(val: boolean): void; getDebuggingProperties(): Promise; } @@ -38,6 +39,11 @@ export class MetricsService implements IMetricsService { this.metricsService.capture(...params); } + setOptOut(...params: Parameters) { + this.metricsService.setOptOut(...params); + } + + // anything transmitted over a channel must be async even if it looks like it doesn't have to be async getDebuggingProperties(): Promise { return this.metricsService.getDebuggingProperties() diff --git a/src/vs/workbench/contrib/void/common/storageKeys.ts b/src/vs/workbench/contrib/void/common/storageKeys.ts index 476c05b1..b23d7ffb 100644 --- a/src/vs/workbench/contrib/void/common/storageKeys.ts +++ b/src/vs/workbench/contrib/void/common/storageKeys.ts @@ -17,3 +17,7 @@ export const VOID_SETTINGS_STORAGE_KEY = 'void.settingsServiceStorageII' // 1.0.3 export const THREAD_STORAGE_KEY = 'void.chatThreadStorageII' + + + +export const OPT_OUT_KEY = 'void.app.optOutAll' diff --git a/src/vs/workbench/contrib/void/common/voidSettingsTypes.ts b/src/vs/workbench/contrib/void/common/voidSettingsTypes.ts index 549b6534..38497c60 100644 --- a/src/vs/workbench/contrib/void/common/voidSettingsTypes.ts +++ b/src/vs/workbench/contrib/void/common/voidSettingsTypes.ts @@ -231,12 +231,9 @@ export const displayInfoOfSettingName = (providerName: ProviderName, settingName } throw new Error(`displayInfo: Unknown setting name: "${settingName}"`) - } - - const defaultCustomSettings: Record = { apiKey: undefined, endpoint: undefined, diff --git a/src/vs/workbench/contrib/void/electron-main/metricsMainService.ts b/src/vs/workbench/contrib/void/electron-main/metricsMainService.ts index a7d5e559..bfac3f63 100644 --- a/src/vs/workbench/contrib/void/electron-main/metricsMainService.ts +++ b/src/vs/workbench/contrib/void/electron-main/metricsMainService.ts @@ -13,6 +13,7 @@ import { IApplicationStorageMainService } from '../../../../platform/storage/ele import { IMetricsService } from '../common/metricsService.js'; import { PostHog } from 'posthog-node' +import { OPT_OUT_KEY } from '../common/storageKeys.js'; const os = isWindows ? 'windows' : isMacintosh ? 'mac' : isLinux ? 'linux' : null @@ -29,6 +30,8 @@ const osInfo = _getOSInfo() // we'd like to use devDeviceId on telemetryService, but that gets sanitized by the time it gets here as 'someValue.devDeviceId' + + export class MetricsMainService extends Disposable implements IMetricsService { _serviceBrand: undefined; @@ -119,7 +122,17 @@ export class MetricsMainService extends Disposable implements IMetricsService { distinctId: this.distinctId, properties: this._initProperties, } - this.client.identify(identifyMessage) + + const didOptOut = this._appStorage.get(OPT_OUT_KEY, StorageScope.APPLICATION) !== undefined + + if (didOptOut) { + this.client.optOut() + } + else { + this.client.identify(identifyMessage) + this.client.optIn() + } + console.log('Void posthog metrics info:', JSON.stringify(identifyMessage, null, 2)) } @@ -131,6 +144,14 @@ export class MetricsMainService extends Disposable implements IMetricsService { this.client.capture(capture) } + setOptOut: IMetricsService['setOptOut'] = (newVal: boolean) => { + if (newVal) { + this._appStorage.store(OPT_OUT_KEY, 'true', StorageScope.APPLICATION, StorageTarget.MACHINE) + } + else { + this._appStorage.remove(OPT_OUT_KEY, StorageScope.APPLICATION) + } + } async getDebuggingProperties() { return this._initProperties