diff --git a/lib/shared/src/telemetry-v2/index.ts b/lib/shared/src/telemetry-v2/index.ts index 5e3e7650d90..726b4d14303 100644 --- a/lib/shared/src/telemetry-v2/index.ts +++ b/lib/shared/src/telemetry-v2/index.ts @@ -2,10 +2,10 @@ * Events accept billing metadata for ease of categorization in analytics * pipelines - this type enumerates known categories. */ -export type BillingCategory = 'exampleBillingCategory' +export type BillingCategory = 'core' | 'billable' /** * Events accept billing metadata for ease of categorization in analytics * pipelines - this type enumerates known products. */ -export type BillingProduct = 'exampleBillingProduct' +export type BillingProduct = 'cody' diff --git a/vscode/src/auth/auth.ts b/vscode/src/auth/auth.ts index 928f0aa6e8b..267e41a0df1 100644 --- a/vscode/src/auth/auth.ts +++ b/vscode/src/auth/auth.ts @@ -34,6 +34,10 @@ export async function showSignInMenu( const menuID = type || item?.id telemetryRecorder.recordEvent('cody.auth.signin.menu', 'clicked', { privateMetadata: { menuID }, + billingMetadata: { + product: 'cody', + category: 'billable', + }, }) switch (menuID) { case 'enterprise': { @@ -222,6 +226,10 @@ async function signinMenuForInstanceUrl(instanceUrl: string): Promise { metadata: { success: authState.authenticated ? 1 : 0, }, + billingMetadata: { + product: 'cody', + category: 'billable', + }, }) await showAuthResultMessage(instanceUrl, authState) } @@ -292,6 +300,10 @@ export async function tokenCallbackHandler( metadata: { success: authState?.authenticated ? 1 : 0, }, + billingMetadata: { + product: 'cody', + category: 'billable', + }, }) if (authState?.authenticated) { await vscode.window.showInformationMessage(`Signed in to ${endpoint}`) @@ -326,7 +338,12 @@ export function formatURL(uri: string): string | null { } export async function showSignOutMenu(): Promise { - telemetryRecorder.recordEvent('cody.auth.logout', 'clicked') + telemetryRecorder.recordEvent('cody.auth.logout', 'clicked', { + billingMetadata: { + product: 'cody', + category: 'billable', + }, + }) const { endpoint } = authProvider.instance!.status if (endpoint) { diff --git a/vscode/src/chat/chat-view/ChatController.ts b/vscode/src/chat/chat-view/ChatController.ts index 08c6956d93b..e474dcc40c4 100644 --- a/vscode/src/chat/chat-view/ChatController.ts +++ b/vscode/src/chat/chat-view/ChatController.ts @@ -460,6 +460,10 @@ export class ChatController implements vscode.Disposable, vscode.WebviewViewProv metadata: { success: authStatus?.authenticated ? 1 : 0, }, + billingMetadata: { + product: 'cody', + category: 'billable', + }, } ) if (!authStatus?.authenticated) { @@ -801,6 +805,10 @@ export class ChatController implements vscode.Disposable, vscode.WebviewViewProv promptText: isDotCom(authStatus) && truncatePromptString(inputText, CHAT_INPUT_TOKEN_BUDGET), }, + billingMetadata: { + product: 'cody', + category: 'core', + }, }) } @@ -922,7 +930,12 @@ export class ChatController implements vscode.Disposable, vscode.WebviewViewProv ): Promise { const abortSignal = this.startNewSubmitOrEditOperation() - telemetryRecorder.recordEvent('cody.editChatButton', 'clicked') + telemetryRecorder.recordEvent('cody.editChatButton', 'clicked', { + billingMetadata: { + product: 'cody', + category: 'billable', + }, + }) try { const humanMessage = index ?? this.chatModel.getLastSpeakerMessageIndex('human') @@ -949,7 +962,12 @@ export class ChatController implements vscode.Disposable, vscode.WebviewViewProv this.cancelSubmitOrEditOperation() // Notify the webview there is no message in progress. this.postViewTranscript() - telemetryRecorder.recordEvent('cody.sidebar.abortButton', 'clicked') + telemetryRecorder.recordEvent('cody.sidebar.abortButton', 'clicked', { + billingMetadata: { + category: 'billable', + product: 'cody', + }, + }) } public async handleGetUserEditorContext(uri?: URI): Promise { @@ -1302,6 +1320,10 @@ export class ChatController implements vscode.Disposable, vscode.WebviewViewProv isDotCom(authStatus) && truncatePromptString(messageText, CHAT_OUTPUT_TOKEN_BUDGET), chatModel: this.chatModel.modelID, }, + billingMetadata: { + product: 'cody', + category: 'billable', + }, }) } @@ -1613,6 +1635,10 @@ export class ChatController implements vscode.Disposable, vscode.WebviewViewProv isDotCom(authStatus) && truncatePromptString(inputText, CHAT_INPUT_TOKEN_BUDGET), gitMetadata, }, + billingMetadata: { + product: 'cody', + category: 'billable', + }, }) } } diff --git a/vscode/src/chat/chat-view/ChatsController.ts b/vscode/src/chat/chat-view/ChatsController.ts index cde8ef89013..002b8ded0ab 100644 --- a/vscode/src/chat/chat-view/ChatsController.ts +++ b/vscode/src/chat/chat-view/ChatsController.ts @@ -250,7 +250,12 @@ export class ChatsController implements vscode.Disposable { } private async sendEditorContextToChat(uri?: URI): Promise { - telemetryRecorder.recordEvent('cody.addChatContext', 'clicked') + telemetryRecorder.recordEvent('cody.addChatContext', 'clicked', { + billingMetadata: { + category: 'billable', + product: 'cody', + }, + }) const provider = await this.getActiveChatController() if (provider === this.panel) { await vscode.commands.executeCommand('cody.chat.focus') @@ -345,7 +350,12 @@ export class ChatsController implements vscode.Disposable { * Export chat history to file system */ private async exportHistory(): Promise { - telemetryRecorder.recordEvent('cody.exportChatHistoryButton', 'clicked') + telemetryRecorder.recordEvent('cody.exportChatHistoryButton', 'clicked', { + billingMetadata: { + product: 'cody', + category: 'billable', + }, + }) const authStatus = authProvider.instance!.status if (authStatus.authenticated) { try { diff --git a/vscode/src/chat/context/chatContext.ts b/vscode/src/chat/context/chatContext.ts index 06cc11565a9..9b6fc2e5996 100644 --- a/vscode/src/chat/context/chatContext.ts +++ b/vscode/src/chat/context/chatContext.ts @@ -56,12 +56,20 @@ export function getMentionMenuData( source: atMentionSourceTelemetryMetadataMapping[source], }, privateMetadata: { source }, + billingMetadata: { + product: 'cody', + category: 'core', + }, }) }, withProvider: (provider, providerMetadata) => { telemetryRecorder.recordEvent(`cody.at-mention.${provider}`, 'executed', { metadata: { source: atMentionSourceTelemetryMetadataMapping[source] }, privateMetadata: { source, providerMetadata }, + billingMetadata: { + product: 'cody', + category: 'core', + }, }) }, } diff --git a/vscode/src/commands/execute/explain-history.ts b/vscode/src/commands/execute/explain-history.ts index 05d05844fe7..155bcc82824 100644 --- a/vscode/src/commands/execute/explain-history.ts +++ b/vscode/src/commands/execute/explain-history.ts @@ -82,6 +82,10 @@ export async function executeExplainHistoryCommand( source: args?.source, traceId: span.spanContext().traceId, }, + billingMetadata: { + product: 'cody', + category: 'core', + }, }) const sessionArgs = await explainHistoryCommand(span, commandsProvider, args) diff --git a/vscode/src/commands/execute/explain.ts b/vscode/src/commands/execute/explain.ts index fef66fac22f..f502c824fd4 100644 --- a/vscode/src/commands/execute/explain.ts +++ b/vscode/src/commands/execute/explain.ts @@ -86,6 +86,10 @@ export async function executeExplainCommand( source: args?.source, traceId: span.spanContext().traceId, }, + billingMetadata: { + product: 'cody', + category: 'core', + }, }) const chatArguments = await explainCommand(span, args) diff --git a/vscode/src/commands/execute/smell.ts b/vscode/src/commands/execute/smell.ts index d3c6e93317b..b699acc3fa0 100644 --- a/vscode/src/commands/execute/smell.ts +++ b/vscode/src/commands/execute/smell.ts @@ -86,6 +86,10 @@ export async function executeSmellCommand( source: args?.source, traceId: span.spanContext().traceId, }, + billingMetadata: { + product: 'cody', + category: 'core', + }, }) const chatArguments = await smellCommand(span, args) diff --git a/vscode/src/commands/execute/terminal.ts b/vscode/src/commands/execute/terminal.ts index db1b097dd47..229eca99ca5 100644 --- a/vscode/src/commands/execute/terminal.ts +++ b/vscode/src/commands/execute/terminal.ts @@ -32,6 +32,10 @@ export async function executeExplainOutput( source, traceId: span.spanContext().traceId, }, + billingMetadata: { + product: 'cody', + category: 'core', + }, }) const promptArgs = PromptString.fromTerminalOutputArguments(args) diff --git a/vscode/src/commands/execute/test-chat.ts b/vscode/src/commands/execute/test-chat.ts index 4dc22ae3145..1aab6827901 100644 --- a/vscode/src/commands/execute/test-chat.ts +++ b/vscode/src/commands/execute/test-chat.ts @@ -86,6 +86,10 @@ export async function executeTestChatCommand( source: args?.source, traceId: span.spanContext().traceId, }, + billingMetadata: { + product: 'cody', + category: 'core', + }, }) return { diff --git a/vscode/src/commands/menus/command-builder.ts b/vscode/src/commands/menus/command-builder.ts index dad932fbcf4..72067c43716 100644 --- a/vscode/src/commands/menus/command-builder.ts +++ b/vscode/src/commands/menus/command-builder.ts @@ -30,7 +30,12 @@ export class CustomCommandsBuilderMenu { const type = prompt && (await this.selectCommandType()) if (key && mode && prompt && type) { - telemetryRecorder.recordEvent('cody.command.custom.build', 'executed') + telemetryRecorder.recordEvent('cody.command.custom.build', 'executed', { + billingMetadata: { + product: 'cody', + category: 'core', + }, + }) return { key, prompt: { ...prompt, key, mode }, type } } diff --git a/vscode/src/commands/menus/index.ts b/vscode/src/commands/menus/index.ts index db516ca2515..09c1f19cfd4 100644 --- a/vscode/src/commands/menus/index.ts +++ b/vscode/src/commands/menus/index.ts @@ -27,6 +27,10 @@ export async function showCommandMenu( const source = args?.source telemetryRecorder.recordEvent(`cody.menu.command.${type}`, 'clicked', { privateMetadata: { source }, + billingMetadata: { + product: 'cody', + category: 'billable', + }, }) // Add items to menus accordingly: @@ -219,7 +223,12 @@ function normalize(input: string): string { export async function showNewCustomCommandMenu( commands: string[] ): Promise { - telemetryRecorder.recordEvent('cody.menu.custom.build', 'clicked') + telemetryRecorder.recordEvent('cody.menu.custom.build', 'clicked', { + billingMetadata: { + product: 'cody', + category: 'billable', + }, + }) const builder = new CustomCommandsBuilderMenu() return builder.start(commands) } diff --git a/vscode/src/commands/scm/source-control.ts b/vscode/src/commands/scm/source-control.ts index aa19d81e591..210c2203909 100644 --- a/vscode/src/commands/scm/source-control.ts +++ b/vscode/src/commands/scm/source-control.ts @@ -70,7 +70,12 @@ export class CodySourceControl implements vscode.Disposable { * @param scm - The source control instance to use for the commit message generation. */ public async generate(scm?: vscode.SourceControl): Promise { - telemetryRecorder.recordEvent('cody.command.generate-commit', 'executed') + telemetryRecorder.recordEvent('cody.command.generate-commit', 'executed', { + billingMetadata: { + product: 'cody', + category: 'core', + }, + }) const currentWorkspaceUri = scm?.rootUri ?? vscode.workspace.workspaceFolders?.[0]?.uri if (!this.gitAPI || !currentWorkspaceUri) { diff --git a/vscode/src/commands/services/code-lenses.ts b/vscode/src/commands/services/code-lenses.ts index abafc1778de..b3b34d6e464 100644 --- a/vscode/src/commands/services/code-lenses.ts +++ b/vscode/src/commands/services/code-lenses.ts @@ -40,7 +40,12 @@ export class CommandCodeLenses implements vscode.CodeLensProvider { this._disposables.push(vscode.languages.registerCodeLensProvider({ scheme: 'file' }, this)) this._disposables.push( vscode.commands.registerCommand('cody.editor.codelens.click', async lens => { - telemetryRecorder.recordEvent('cody.command.codelens', 'clicked') + telemetryRecorder.recordEvent('cody.command.codelens', 'clicked', { + billingMetadata: { + product: 'cody', + category: 'core', + }, + }) const clickedLens = lens as EditorCodeLens await this.onCodeLensClick(clickedLens) }) diff --git a/vscode/src/commands/services/runner.ts b/vscode/src/commands/services/runner.ts index 6bcc702c022..8573e4aec21 100644 --- a/vscode/src/commands/services/runner.ts +++ b/vscode/src/commands/services/runner.ts @@ -72,6 +72,10 @@ export class CommandRunner implements vscode.Disposable { source: this.args.source, traceId: this.span.spanContext().traceId, }, + billingMetadata: { + product: 'cody', + category: 'core', + }, }) // Conditions checks diff --git a/vscode/src/completions/logger.test.ts b/vscode/src/completions/logger.test.ts index 60c45656dca..8e680447b7a 100644 --- a/vscode/src/completions/logger.test.ts +++ b/vscode/src/completions/logger.test.ts @@ -83,6 +83,7 @@ describe('logger', () => { interactionID: expect.any(String), metadata: expect.anything(), privateMetadata: expect.anything(), + billingMetadata: expect.anything(), }) expect(recordSpy).toHaveBeenCalledWith('cody.completion', 'accepted', { @@ -90,6 +91,7 @@ describe('logger', () => { interactionID: expect.any(String), metadata: expect.anything(), privateMetadata: expect.anything(), + billingMetadata: expect.anything(), }) }) diff --git a/vscode/src/completions/logger.ts b/vscode/src/completions/logger.ts index 246f4136cc0..587f362d89b 100644 --- a/vscode/src/completions/logger.ts +++ b/vscode/src/completions/logger.ts @@ -506,6 +506,16 @@ function writeCompletionEvent { - telemetryRecorder.recordEvent('cody.walkthrough', 'clicked') + telemetryRecorder.recordEvent('cody.walkthrough', 'clicked', { + billingMetadata: { + category: 'billable', + product: 'cody', + }, + }) // Hack: We have to run this twice to force VS Code to register the walkthrough // Open issue: https://github.com/microsoft/vscode/issues/186165 await vscode.commands.executeCommand('workbench.action.openWalkthrough') diff --git a/vscode/src/non-stop/FixupController.ts b/vscode/src/non-stop/FixupController.ts index f464a0ac0c5..39300d5f532 100644 --- a/vscode/src/non-stop/FixupController.ts +++ b/vscode/src/non-stop/FixupController.ts @@ -285,6 +285,10 @@ export class FixupController ...privateMetadata, model: task.model, }, + billingMetadata: { + category: 'billable', + product: 'cody', + }, }) } } @@ -612,6 +616,10 @@ export class FixupController telemetryRecorder.recordEvent('cody.fixup.apply', 'succeeded', { metadata, privateMetadata, + billingMetadata: { + category: 'core', + product: 'cody', + }, }) /** @@ -658,6 +666,10 @@ export class FixupController telemetryRecorder.recordEvent('cody.fixup.user', acceptance, { metadata, privateMetadata, + billingMetadata: { + category: acceptance === 'rejected' ? 'billable' : 'core', + product: 'cody', + }, }) } diff --git a/vscode/src/non-stop/codelenses/provider.ts b/vscode/src/non-stop/codelenses/provider.ts index b7e26b66a5c..b3637b97d71 100644 --- a/vscode/src/non-stop/codelenses/provider.ts +++ b/vscode/src/non-stop/codelenses/provider.ts @@ -30,14 +30,24 @@ export class FixupCodeLenses implements vscode.CodeLensProvider, FixupControlApp vscode.languages.registerCodeLensProvider('*', this), vscode.workspace.registerTextDocumentContentProvider('cody-fixup', this.contentStore), vscode.commands.registerCommand('cody.fixup.codelens.cancel', id => { - telemetryRecorder.recordEvent('cody.fixup.codeLens', 'cancel') + telemetryRecorder.recordEvent('cody.fixup.codeLens', 'cancel', { + billingMetadata: { + product: 'cody', + category: 'billable', + }, + }) const task = this.controller.taskForId(id) if (task) { this.controller.cancel(task) } }), vscode.commands.registerCommand('cody.fixup.codelens.diff', id => { - telemetryRecorder.recordEvent('cody.fixup.codeLens', 'diff') + telemetryRecorder.recordEvent('cody.fixup.codeLens', 'diff', { + billingMetadata: { + product: 'cody', + category: 'core', + }, + }) return this.diff(id) }), vscode.commands.registerCommand('cody.fixup.codelens.retry', async id => { @@ -46,26 +56,46 @@ export class FixupCodeLenses implements vscode.CodeLensProvider, FixupControlApp return task ? this.controller.retry(task, 'code-lens') : Promise.resolve() }), vscode.commands.registerCommand('cody.fixup.codelens.undo', id => { - telemetryRecorder.recordEvent('cody.fixup.codeLens', 'undo') + telemetryRecorder.recordEvent('cody.fixup.codeLens', 'undo', { + billingMetadata: { + product: 'cody', + category: 'billable', + }, + }) const task = this.controller.taskForId(id) return task ? this.controller.undo(task) : Promise.resolve() }), vscode.commands.registerCommand('cody.fixup.codelens.accept', id => { - telemetryRecorder.recordEvent('cody.fixup.codeLens', 'accept') + telemetryRecorder.recordEvent('cody.fixup.codeLens', 'accept', { + billingMetadata: { + product: 'cody', + category: 'core', + }, + }) const task = this.controller.taskForId(id) if (task) { this.controller.accept(task) } }), vscode.commands.registerCommand('cody.fixup.codelens.acceptChange', (id, range) => { - telemetryRecorder.recordEvent('cody.fixup.codeLens', 'acceptChange') + telemetryRecorder.recordEvent('cody.fixup.codeLens', 'acceptChange', { + billingMetadata: { + product: 'cody', + category: 'core', + }, + }) const task = this.controller.taskForId(id) if (task) { this.controller.acceptChange(task, range) } }), vscode.commands.registerCommand('cody.fixup.codelens.rejectChange', (id, range) => { - telemetryRecorder.recordEvent('cody.fixup.codeLens', 'rejectChange') + telemetryRecorder.recordEvent('cody.fixup.codeLens', 'rejectChange', { + billingMetadata: { + product: 'cody', + category: 'billable', + }, + }) const task = this.controller.taskForId(id) if (task) { this.controller.rejectChange(task, range) @@ -80,7 +110,7 @@ export class FixupCodeLenses implements vscode.CodeLensProvider, FixupControlApp if (!nearestTask) { return } - return vscode.commands.executeCommand('cody.fixup.codelens.cancel', nearestTask.id) + return vscode.commands.executeCommand('cody.fixup.codelens.cancel', nearestTask.id, {}) }), vscode.commands.registerCommand('cody.fixup.acceptNearest', () => { const nearestTask = this.getNearestTask({ filter: { states: ACTIONABLE_TASK_STATES } }) diff --git a/vscode/src/notifications/setup-notification.ts b/vscode/src/notifications/setup-notification.ts index f4ce056b46e..91d720c82c8 100644 --- a/vscode/src/notifications/setup-notification.ts +++ b/vscode/src/notifications/setup-notification.ts @@ -44,7 +44,12 @@ export const showSetupNotification = async ( label: 'Do not show again', onClick: async () => { localStorage.set('notification.setupDismissed', 'true') - telemetryRecorder.recordEvent('cody.signInNotification.doNotShow', 'clicked') + telemetryRecorder.recordEvent('cody.signInNotification.doNotShow', 'clicked', { + billingMetadata: { + category: 'billable', + product: 'cody', + }, + }) }, }, ], diff --git a/vscode/src/services/AuthProvider.ts b/vscode/src/services/AuthProvider.ts index e0cacfabb2f..cb2f27df525 100644 --- a/vscode/src/services/AuthProvider.ts +++ b/vscode/src/services/AuthProvider.ts @@ -299,7 +299,12 @@ export class AuthProvider implements AuthStatusProvider, vscode.Disposable { } else { eventValue = 'disconnected' } - telemetryRecorder.recordEvent('cody.auth', eventValue) + telemetryRecorder.recordEvent('cody.auth', eventValue, { + billingMetadata: { + product: 'cody', + category: 'billable', + }, + }) } } @@ -328,7 +333,12 @@ export class AuthProvider implements AuthStatusProvider, vscode.Disposable { // User has authenticated before, noop return } - telemetryRecorder.recordEvent('cody.auth.login', 'firstEver') + telemetryRecorder.recordEvent('cody.auth.login', 'firstEver', { + billingMetadata: { + product: 'cody', + category: 'billable', + }, + }) this.setHasAuthenticatedBefore() void maybeStartInteractiveTutorial() } diff --git a/vscode/src/services/SidebarCommands.ts b/vscode/src/services/SidebarCommands.ts index b9152d89ffc..a088d4e0502 100644 --- a/vscode/src/services/SidebarCommands.ts +++ b/vscode/src/services/SidebarCommands.ts @@ -1,6 +1,6 @@ import * as vscode from 'vscode' -import { CodyIDE, telemetryRecorder } from '@sourcegraph/cody-shared' +import { type BillingCategory, CodyIDE, telemetryRecorder } from '@sourcegraph/cody-shared' import { ACCOUNT_LIMITS_INFO_URL, ACCOUNT_UPGRADE_URL, @@ -13,8 +13,19 @@ import { import { getReleaseNotesURLByIDE } from '../release' import { version } from '../version' -export function logSidebarClick(feature: string) { - telemetryRecorder.recordEvent(`cody.sidebar.${feature}`, 'clicked') +export function logSidebarClick(feature: string, billingCategory?: BillingCategory) { + telemetryRecorder.recordEvent( + `cody.sidebar.${feature}`, + 'clicked', + billingCategory + ? { + billingMetadata: { + category: billingCategory, + product: 'cody', + }, + } + : {} + ) } export function registerSidebarCommands(): vscode.Disposable[] { @@ -22,15 +33,15 @@ export function registerSidebarCommands(): vscode.Disposable[] { vscode.commands.registerCommand('cody.sidebar.commands', (feature: string, command: string) => { // For Custom Commands if (command === 'cody.action.command') { - logSidebarClick('custom') + logSidebarClick('custom', 'core') void vscode.commands.executeCommand(command, feature, { source: 'sidebar' }) return } - logSidebarClick(feature) + logSidebarClick(feature, 'core') void vscode.commands.executeCommand(command, { source: 'sidebar' }) }), vscode.commands.registerCommand('cody.show-page', (page: string) => { - logSidebarClick(page) + logSidebarClick(page, 'billable') let url: URL switch (page) { case 'upgrade': @@ -49,11 +60,11 @@ export function registerSidebarCommands(): vscode.Disposable[] { void vscode.env.openExternal(vscode.Uri.parse(url.toString())) }), vscode.commands.registerCommand('cody.sidebar.settings', () => { - logSidebarClick('settings') + logSidebarClick('settings', 'billable') void vscode.commands.executeCommand('cody.status-bar.interacted') }), vscode.commands.registerCommand('cody.sidebar.keyboardShortcuts', () => { - logSidebarClick('keyboardShortcuts') + logSidebarClick('keyboardShortcuts', 'billable') void vscode.commands.executeCommand( 'workbench.action.openGlobalKeybindings', '@ext:sourcegraph.cody-ai' @@ -67,7 +78,7 @@ export function registerSidebarCommands(): vscode.Disposable[] { ) }), vscode.commands.registerCommand('cody.sidebar.documentation', () => { - logSidebarClick('documentation') + logSidebarClick('documentation', 'billable') void vscode.commands.executeCommand('vscode.open', CODY_DOC_URL.href) }), vscode.commands.registerCommand('cody.sidebar.support', () => { @@ -83,7 +94,7 @@ export function registerSidebarCommands(): vscode.Disposable[] { void vscode.commands.executeCommand('vscode.open', DISCORD_URL.href) }), vscode.commands.registerCommand('cody.sidebar.account', () => { - logSidebarClick('account') + logSidebarClick('account', 'billable') void vscode.commands.executeCommand('cody.auth.account') }), vscode.commands.registerCommand('cody.sidebar.logs', () => { diff --git a/vscode/src/services/StatusBar.ts b/vscode/src/services/StatusBar.ts index e4f9c52b272..3391aeda085 100644 --- a/vscode/src/services/StatusBar.ts +++ b/vscode/src/services/StatusBar.ts @@ -101,6 +101,10 @@ export function createStatusBar(): CodyStatusBar { const command = vscode.commands.registerCommand(STATUS_BAR_INTERACTION_COMMAND, async () => { telemetryRecorder.recordEvent('cody.statusbarIcon', 'clicked', { privateMetadata: { loggedIn: Boolean(authStatus?.authenticated) }, + billingMetadata: { + category: 'billable', + product: 'cody', + }, }) if (!authStatus?.authenticated) { diff --git a/vscode/src/services/cody-ignore.ts b/vscode/src/services/cody-ignore.ts index e168a9bca44..0fb38313056 100644 --- a/vscode/src/services/cody-ignore.ts +++ b/vscode/src/services/cody-ignore.ts @@ -177,7 +177,12 @@ async function refresh(uri: vscode.Uri): Promise { function setCodyIgnoreFiles(ws: vscode.Uri, files: IgnoreFileContent[]): void { ignores.setIgnoreFiles(ws, files) if (files.length) { - telemetryRecorder.recordEvent('cody.codyIgnore', 'hasFile') + telemetryRecorder.recordEvent('cody.codyIgnore', 'hasFile', { + billingMetadata: { + product: 'cody', + category: 'billable', + }, + }) } } diff --git a/vscode/src/services/telemetry-v2.ts b/vscode/src/services/telemetry-v2.ts index 62b347584d2..98834f86579 100644 --- a/vscode/src/services/telemetry-v2.ts +++ b/vscode/src/services/telemetry-v2.ts @@ -115,7 +115,12 @@ export async function createOrUpdateTelemetryRecorderProvider( /** * New user */ - telemetryRecorder.recordEvent('cody.extension', 'installed') + telemetryRecorder.recordEvent('cody.extension', 'installed', { + billingMetadata: { + product: 'cody', + category: 'billable', + }, + }) } else if (!config.isRunningInsideAgent || config.agentHasPersistentStorage) { /** * Repeat user diff --git a/vscode/src/services/utils/codeblock-action-tracker.ts b/vscode/src/services/utils/codeblock-action-tracker.ts index 4ae68a83c81..914edaf5f22 100644 --- a/vscode/src/services/utils/codeblock-action-tracker.ts +++ b/vscode/src/services/utils/codeblock-action-tracker.ts @@ -56,6 +56,10 @@ function setLastStoredCode( lastStoredCode = codeCount let operation: string + + // 🚨 [Telemetry] if any new event names/types are added, check that those actions qualify as core events + //(https://sourcegraph.notion.site/Cody-analytics-6b77a2cb2373466fae4797b6529a0e3d#2ca9035287854de48877a7cef2b3d4b4). + // If not, the event recorded below this switch statement needs to be updated. switch (eventName) { case 'copyButton': case 'keyDown.Copy': @@ -83,6 +87,11 @@ function setLastStoredCode( source, op: operation, }, + billingMetadata: { + product: 'cody', + // 🚨 ensure that any new event names added qualify as core events, or update this parameter. + category: 'core', + }, }) } @@ -231,6 +240,10 @@ export async function onTextDocumentChange(newCode: string): Promise { source, op, }, + billingMetadata: { + product: 'cody', + category: 'core', + }, }) } } diff --git a/vscode/src/tutorial/commands.ts b/vscode/src/tutorial/commands.ts index 1a168ca33c9..d02422e14b7 100644 --- a/vscode/src/tutorial/commands.ts +++ b/vscode/src/tutorial/commands.ts @@ -51,6 +51,10 @@ export const registerEditTutorialCommand = ( async (_document: vscode.TextDocument, source: TutorialSource = 'editor') => { telemetryRecorder.recordEvent('cody.interactiveTutorial', 'edit', { privateMetadata: { source }, + billingMetadata: { + product: 'cody', + category: 'billable', + }, }) // Clear the existing decoration, the user has actioned this step, @@ -88,6 +92,10 @@ export const registerChatTutorialCommand = (onComplete: () => void): vscode.Disp async (_document: vscode.TextDocument, source: TutorialSource = 'editor') => { telemetryRecorder.recordEvent('cody.interactiveTutorial', 'chat', { privateMetadata: { source }, + billingMetadata: { + product: 'cody', + category: 'billable', + }, }) await vscode.commands.executeCommand('cody.chat.newEditorPanel') onComplete() diff --git a/vscode/src/tutorial/helpers.ts b/vscode/src/tutorial/helpers.ts index 3c55e356b81..b38b38501b9 100644 --- a/vscode/src/tutorial/helpers.ts +++ b/vscode/src/tutorial/helpers.ts @@ -26,7 +26,12 @@ export const isInTutorial = (document: vscode.TextDocument): boolean => { // and then trigger the tutorial. // This will either noop or open the tutorial depending on the feature flag. export const maybeStartInteractiveTutorial = async () => { - telemetryRecorder.recordEvent('cody.interactiveTutorial', 'attemptingStart') + telemetryRecorder.recordEvent('cody.interactiveTutorial', 'attemptingStart', { + billingMetadata: { + product: 'cody', + category: 'billable', + }, + }) await featureFlagProvider.instance!.refresh() const enabled = await featureFlagProvider.instance!.evaluateFeatureFlag( FeatureFlag.CodyInteractiveTutorial diff --git a/vscode/src/tutorial/index.ts b/vscode/src/tutorial/index.ts index 61600eb69d8..59857552a37 100644 --- a/vscode/src/tutorial/index.ts +++ b/vscode/src/tutorial/index.ts @@ -26,7 +26,12 @@ const startTutorial = async (document: vscode.TextDocument): Promise { - telemetryRecorder.recordEvent('cody.interactiveTutorial', 'reset') + telemetryRecorder.recordEvent('cody.interactiveTutorial', 'reset', { + billingMetadata: { + product: 'cody', + category: 'billable', + }, + }) stop() document = await resetDocument(documentUri) return start() diff --git a/vscode/src/uninstall/post-uninstall.ts b/vscode/src/uninstall/post-uninstall.ts index d34de42e9fb..2235ac6a19b 100644 --- a/vscode/src/uninstall/post-uninstall.ts +++ b/vscode/src/uninstall/post-uninstall.ts @@ -30,7 +30,12 @@ async function main() { 'connected-instance-only' ) const recorder = provider.getRecorder() - recorder.recordEvent('cody.extension', 'uninstalled') + recorder.recordEvent('cody.extension', 'uninstalled', { + billingMetadata: { + product: 'cody', + category: 'billable', + }, + }) // cleanup the uninstaller config deleteUninstallerDirectory() diff --git a/vscode/webviews/Chat.tsx b/vscode/webviews/Chat.tsx index 6db3a2266d7..0449740fc6c 100644 --- a/vscode/webviews/Chat.tsx +++ b/vscode/webviews/Chat.tsx @@ -80,6 +80,10 @@ export const Chat: React.FunctionComponent ? truncateTextStart(transcriptRef.current.toString(), CHAT_INPUT_TOKEN_BUDGET) : '', }, + billingMetadata: { + product: 'cody', + category: 'billable', + }, }) }, [userInfo, telemetryRecorder] diff --git a/vscode/webviews/chat/cells/messageCell/assistant/ContextFocusActions.tsx b/vscode/webviews/chat/cells/messageCell/assistant/ContextFocusActions.tsx index aaad74ac328..ad6e8cdbdfd 100644 --- a/vscode/webviews/chat/cells/messageCell/assistant/ContextFocusActions.tsx +++ b/vscode/webviews/chat/cells/messageCell/assistant/ContextFocusActions.tsx @@ -28,6 +28,10 @@ export const ContextFocusActions: FunctionComponent<{ rerunWithInitialContextRepositories: rerunWith.repositories ? 1 : 0, rerunWithInitialContextFiles: rerunWith.files ? 1 : 0, }, + billingMetadata: { + product: 'cody', + category: 'billable', + }, }) }, [telemetryRecorder, initialContextEventMetadata] @@ -71,6 +75,10 @@ export const ContextFocusActions: FunctionComponent<{ onClick: () => { telemetryRecorder.recordEvent('cody.contextSelection', 'addFile', { metadata: initialContextEventMetadata, + billingMetadata: { + product: 'cody', + category: 'core', + }, }) humanMessage.appendAtMention() }, diff --git a/vscode/webviews/chat/cells/messageCell/human/editor/HumanMessageEditor.tsx b/vscode/webviews/chat/cells/messageCell/human/editor/HumanMessageEditor.tsx index ed6ccbfb2ef..0d314f47315 100644 --- a/vscode/webviews/chat/cells/messageCell/human/editor/HumanMessageEditor.tsx +++ b/vscode/webviews/chat/cells/messageCell/human/editor/HumanMessageEditor.tsx @@ -130,6 +130,10 @@ export const HumanMessageEditor: FunctionComponent<{ messageLength: value.text.length, contextItems: value.contextItems.length, }, + billingMetadata: { + product: 'cody', + category: 'billable', + }, }) }, [submitState, parentOnSubmit, onStop, telemetryRecorder.recordEvent, isFirstMessage, isSent]) @@ -223,6 +227,10 @@ export const HumanMessageEditor: FunctionComponent<{ messageLength: value.text.length, contextItems: value.contextItems.length, }, + billingMetadata: { + product: 'cody', + category: 'billable', + }, }) }, [telemetryRecorder.recordEvent, isFirstMessage, isSent]) diff --git a/vscode/webviews/components/modelSelectField/ModelSelectField.tsx b/vscode/webviews/components/modelSelectField/ModelSelectField.tsx index 076c31ccfd7..2f52e46cb80 100644 --- a/vscode/webviews/components/modelSelectField/ModelSelectField.tsx +++ b/vscode/webviews/components/modelSelectField/ModelSelectField.tsx @@ -65,6 +65,10 @@ export const ModelSelectField: React.FunctionComponent<{ modelProvider: model.provider, modelTitle: model.title, }, + billingMetadata: { + product: 'cody', + category: 'billable', + }, }) if (showCodyProBadge && isCodyProModel(model)) { @@ -107,6 +111,10 @@ export const ModelSelectField: React.FunctionComponent<{ isCodyProUser: isCodyProUser ? 1 : 0, totalModels: models.length, }, + billingMetadata: { + product: 'cody', + category: 'billable', + }, }) } }, @@ -227,7 +235,13 @@ export const ModelSelectField: React.FunctionComponent<{ onSelect={() => { telemetryRecorder.recordEvent( 'cody.modelSelector', - 'clickEnterpriseModelOption' + 'clickEnterpriseModelOption', + { + billingMetadata: { + product: 'cody', + category: 'billable', + }, + } ) }} className={styles.modelTitleWithIcon}