From 8275054987d6e539c81626deabee219b4261f727 Mon Sep 17 00:00:00 2001 From: Dan Adler Date: Mon, 3 Jun 2024 16:34:28 -0700 Subject: [PATCH] v2t: add v2 telemetry to the client/shared folder (#62586) --- .../code-hosts/shared/codeHost.test.tsx | 2 + .../src/shared/code-hosts/shared/codeHost.tsx | 15 ++- .../src/shared/components/CodeViewToolbar.tsx | 2 + client/client-api/src/contribution.ts | 18 +++ client/shared/package.json | 2 +- .../shared/src/actions/ActionItem.story.tsx | 11 +- client/shared/src/actions/ActionItem.test.tsx | 122 ++++++++++++++++-- client/shared/src/actions/ActionItem.tsx | 25 +++- .../src/actions/ActionsNavItems.test.tsx | 5 + client/shared/src/actions/ActionsNavItems.tsx | 2 + client/shared/src/api/client/connection.ts | 8 +- .../src/api/client/mainthread-api.test.ts | 41 +++++- .../shared/src/api/client/mainthread-api.ts | 2 + client/shared/src/api/contract.ts | 8 ++ client/shared/src/api/extension/activation.ts | 6 +- .../api/extension/api/contribution.test.ts | 54 +++++--- .../src/api/extension/test/activation.test.ts | 4 +- .../src/codeintel/legacy-extensions/api.ts | 8 +- .../codeintel/legacy-extensions/providers.ts | 16 +-- .../codeintel/legacy-extensions/telemetry.ts | 26 +++- client/shared/src/commands/commands.ts | 7 +- .../src/contributions/contributions.test.ts | 12 +- .../extensions/createSyncLoadedController.ts | 1 + .../shared/src/hover/HoverOverlay.fixtures.ts | 8 ++ client/shared/src/hover/HoverOverlay.test.tsx | 21 ++- client/shared/src/hover/HoverOverlay.tsx | 6 +- client/shared/src/hover/actions.ts | 13 ++ .../shared/src/hover/useLogTelemetryEvent.ts | 7 +- client/shared/src/search/searchQueryState.tsx | 2 +- client/shared/src/testing/testHelpers.ts | 9 +- .../(code)/-/blob/[...path]/FileView.svelte | 3 + .../src/routes/search/PreviewPanel.svelte | 3 + client/web/package.json | 4 +- client/web/src/repo/blob/CodeMirrorBlob.tsx | 10 +- .../src/repo/blob/codemirror/codeintel/api.ts | 18 +++ .../codemirror/tooltips/HovercardView.tsx | 1 + .../new-search-content/NewSearchContent.tsx | 2 +- pnpm-lock.yaml | 12 +- 38 files changed, 426 insertions(+), 90 deletions(-) diff --git a/client/browser/src/shared/code-hosts/shared/codeHost.test.tsx b/client/browser/src/shared/code-hosts/shared/codeHost.test.tsx index a35482a4dbd..2fcb325c5ba 100644 --- a/client/browser/src/shared/code-hosts/shared/codeHost.test.tsx +++ b/client/browser/src/shared/code-hosts/shared/codeHost.test.tsx @@ -17,6 +17,7 @@ import { wrapRemoteObservable } from '@sourcegraph/shared/src/api/client/api/com import type { FlatExtensionHostAPI } from '@sourcegraph/shared/src/api/contract' import type { ExtensionCodeEditor } from '@sourcegraph/shared/src/api/extension/api/codeEditor' import type { Controller } from '@sourcegraph/shared/src/extensions/controller' +import { noOpTelemetryRecorder } from '@sourcegraph/shared/src/telemetry' import { NOOP_TELEMETRY_SERVICE } from '@sourcegraph/shared/src/telemetry/telemetryService' import { MockIntersectionObserver } from '@sourcegraph/shared/src/testing/MockIntersectionObserver' import { integrationTestContext } from '@sourcegraph/shared/src/testing/testHelpers' @@ -82,6 +83,7 @@ const commonArguments = () => platformContext: createMockPlatformContext(), sourcegraphURL: DEFAULT_SOURCEGRAPH_URL, telemetryService: NOOP_TELEMETRY_SERVICE, + telemetryRecorder: noOpTelemetryRecorder, render: RENDER, userSignedIn: true, minimalUI: false, diff --git a/client/browser/src/shared/code-hosts/shared/codeHost.tsx b/client/browser/src/shared/code-hosts/shared/codeHost.tsx index 8bc0e09b135..306daeeedd6 100644 --- a/client/browser/src/shared/code-hosts/shared/codeHost.tsx +++ b/client/browser/src/shared/code-hosts/shared/codeHost.tsx @@ -72,6 +72,7 @@ import { } from '@sourcegraph/shared/src/hover/HoverOverlay' import { getModeFromPath } from '@sourcegraph/shared/src/languages' import type { PlatformContext, URLToFileContext } from '@sourcegraph/shared/src/platform/context' +import { TelemetryV2Props } from '@sourcegraph/shared/src/telemetry' import type { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService' import { createURLWithUTM } from '@sourcegraph/shared/src/tracking/utm' import { @@ -289,7 +290,7 @@ export interface FileInfoWithContent extends FileInfoWithRepoName { content?: string } -export interface CodeIntelligenceProps extends TelemetryProps { +export interface CodeIntelligenceProps extends TelemetryProps, TelemetryV2Props { platformContext: Pick< BrowserPlatformContext, 'urlToFile' | 'requestGraphQL' | 'settings' | 'refreshSettings' | 'sourcegraphURL' | 'clientApplication' @@ -321,8 +322,12 @@ function initCodeIntelligence({ extensionsController, render, telemetryService, + telemetryRecorder, repoSyncErrors, -}: Pick & { +}: Pick< + CodeIntelligenceProps, + 'codeHost' | 'platformContext' | 'extensionsController' | 'telemetryService' | 'telemetryRecorder' +> & { render: Renderer mutations: Observable repoSyncErrors: Observable @@ -470,6 +475,7 @@ function initCodeIntelligence({ {...codeHost.hoverOverlayClassProps} className={classNames(styles.hoverOverlay, codeHost.hoverOverlayClassProps?.className)} telemetryService={telemetryService} + telemetryRecorder={telemetryRecorder} hoverRef={this.nextOverlayElement} extensionsController={extensionsController} location={H.createLocation(window.location)} @@ -711,6 +717,7 @@ export async function handleCodeHost({ extensionsController, platformContext, telemetryService, + telemetryRecorder, render, minimalUI, hideActions, @@ -778,6 +785,7 @@ export async function handleCodeHost({ extensionsController, platformContext, telemetryService, + telemetryRecorder, render, mutations, repoSyncErrors, @@ -930,6 +938,7 @@ export async function handleCodeHost({ fileInfoOrError={error} sourcegraphURL={sourcegraphURL} telemetryService={telemetryService} + telemetryRecorder={telemetryRecorder} platformContext={platformContext} extensionsController={extensionsController} buttonProps={codeViewEvent.toolbarButtonProps} @@ -1229,6 +1238,7 @@ export async function handleCodeHost({ fileInfoOrError={diffOrBlobInfo} sourcegraphURL={sourcegraphURL} telemetryService={telemetryService} + telemetryRecorder={telemetryRecorder} platformContext={platformContext} extensionsController={extensionsController} buttonProps={toolbarButtonProps} @@ -1392,6 +1402,7 @@ export function injectCodeIntelligenceToCodeHost( extensionsController, platformContext, telemetryService, + telemetryRecorder: platformContext.telemetryRecorder, render: renderWithThemeProvider as Renderer, minimalUI, hideActions, diff --git a/client/browser/src/shared/components/CodeViewToolbar.tsx b/client/browser/src/shared/components/CodeViewToolbar.tsx index 7c101553648..710a461426d 100644 --- a/client/browser/src/shared/components/CodeViewToolbar.tsx +++ b/client/browser/src/shared/components/CodeViewToolbar.tsx @@ -10,6 +10,7 @@ import { type ActionNavItemsClassProps, ActionsNavItems } from '@sourcegraph/sha import type { ContributionScope } from '@sourcegraph/shared/src/api/extension/api/context/context' import type { ExtensionsControllerProps } from '@sourcegraph/shared/src/extensions/controller' import type { PlatformContextProps } from '@sourcegraph/shared/src/platform/context' +import type { TelemetryV2Props } from '@sourcegraph/shared/src/telemetry' import type { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService' import type { DiffOrBlobInfo, FileInfoWithContent } from '../code-hosts/shared/codeHost' @@ -42,6 +43,7 @@ export interface CodeViewToolbarProps extends PlatformContextProps<'settings' | 'requestGraphQL'>, ExtensionsControllerProps, TelemetryProps, + TelemetryV2Props, CodeViewToolbarClassProps { sourcegraphURL: string diff --git a/client/client-api/src/contribution.ts b/client/client-api/src/contribution.ts index 2c7de0cbcf6..f9a79cfc13e 100644 --- a/client/client-api/src/contribution.ts +++ b/client/client-api/src/contribution.ts @@ -126,6 +126,24 @@ export interface ActionContribution { * (e.g., because the client is not graphical), then the client may hide the item from the toolbar. */ actionItem?: ActionItem + + /** + * Properties to enable event telemetry to be recorded when an action is executed. + */ + telemetryProps: { + /** + * feature must be camelCase and '.'-delimited, e.g. 'myFeature.subFeature'. + * + * Most ActionContribution features should be prefixed with 'blob.' to indicate that they are actions + * that occur on text blobs. + */ + feature: string + + // No `action` prop is provided, because action items only log telemetry when executed (and thus use an + // 'executed' action. + + privateMetadata?: { [key: string]: any } + } } /** diff --git a/client/shared/package.json b/client/shared/package.json index 3365d57c118..052f8e8caec 100644 --- a/client/shared/package.json +++ b/client/shared/package.json @@ -25,7 +25,7 @@ "@sourcegraph/codeintellify": "workspace:*", "@sourcegraph/common": "workspace:*", "@sourcegraph/http-client": "workspace:*", - "@sourcegraph/telemetry": "^0.11.0", + "@sourcegraph/telemetry": "^0.16.0", "@sourcegraph/template-parser": "workspace:*", "@sourcegraph/wildcard": "workspace:*" }, diff --git a/client/shared/src/actions/ActionItem.story.tsx b/client/shared/src/actions/ActionItem.story.tsx index 14f01f62052..48fa8b1b33e 100644 --- a/client/shared/src/actions/ActionItem.story.tsx +++ b/client/shared/src/actions/ActionItem.story.tsx @@ -5,6 +5,7 @@ import type * as H from 'history' import { subtypeOf } from '@sourcegraph/common' import { BrandedStory } from '@sourcegraph/wildcard/src/stories' +import { noOpTelemetryRecorder } from '../telemetry' import { NOOP_TELEMETRY_SERVICE } from '../telemetry/telemetryService' import { ActionItem, type ActionItemComponentProps, type ActionItemProps } from './ActionItem' @@ -27,6 +28,7 @@ const commonProps = subtypeOf>()({ location: LOCATION, extensionsController: EXTENSIONS_CONTROLLER, telemetryService: NOOP_TELEMETRY_SERVICE, + telemetryRecorder: noOpTelemetryRecorder, iconClassName: 'icon-inline', active: true, }) @@ -42,7 +44,7 @@ export default config export const NoopAction: StoryFn = () => ( ) @@ -52,7 +54,7 @@ NoopAction.storyName = 'Noop action' export const CommandAction: StoryFn = () => ( ( command: 'open', commandArguments: ['javascript:alert("link clicked")'], actionItem: { label: 'Hello' }, + telemetryProps: { feature: 'a' }, }} variant="actionItem" onDidExecute={onDidExecute} @@ -95,7 +98,7 @@ export const Executing: StoryFn = () => { return ( @@ -113,7 +116,7 @@ export const _Error: StoryFn = () => { return ( diff --git a/client/shared/src/actions/ActionItem.test.tsx b/client/shared/src/actions/ActionItem.test.tsx index 36da3bd496c..7852b4d0b1e 100644 --- a/client/shared/src/actions/ActionItem.test.tsx +++ b/client/shared/src/actions/ActionItem.test.tsx @@ -6,6 +6,7 @@ import { afterEach, describe, expect, it, test, vi } from 'vitest' import { assertAriaEnabled, createBarrier } from '@sourcegraph/testing' import { renderWithBrandedContext } from '@sourcegraph/wildcard/src/testing' +import { noOpTelemetryRecorder } from '../telemetry' import { NOOP_TELEMETRY_SERVICE } from '../telemetry/telemetryService' import { ActionItem, windowLocation__testingOnly } from './ActionItem' @@ -20,8 +21,17 @@ describe('ActionItem', () => { const component = render( @@ -33,8 +43,17 @@ describe('ActionItem', () => { const component = render( { const component = render( @@ -60,8 +87,14 @@ describe('ActionItem', () => { const component = render( { const component = render( { const component = render( t2} location={history.location} @@ -105,8 +153,17 @@ describe('ActionItem', () => { const { container, asFragment } = render( { const { asFragment } = render( { const { asFragment } = render( { const { asFragment } = renderWithBrandedContext( @@ -210,8 +292,15 @@ describe('ActionItem', () => { const { asFragment } = renderWithBrandedContext( @@ -225,9 +314,16 @@ describe('ActionItem', () => { const { asFragment } = renderWithBrandedContext( diff --git a/client/shared/src/actions/ActionItem.tsx b/client/shared/src/actions/ActionItem.tsx index 882a99770fb..29a8ca3a637 100644 --- a/client/shared/src/actions/ActionItem.tsx +++ b/client/shared/src/actions/ActionItem.tsx @@ -21,6 +21,7 @@ import { import type { ExecuteCommandParameters } from '../api/client/mainthread-api' import { urlForOpenPanel } from '../commands/commands' import type { ExtensionsControllerProps } from '../extensions/controller' +import type { TelemetryV2Props } from '../telemetry' import type { TelemetryProps } from '../telemetry/telemetryService' import styles from './ActionItem.module.scss' @@ -59,7 +60,7 @@ export interface ActionItemComponentProps extends ExtensionsControllerProps<'exe actionItemStyleProps?: ActionItemStyleProps } -export interface ActionItemProps extends ActionItemAction, ActionItemComponentProps, TelemetryProps { +export interface ActionItemProps extends ActionItemAction, ActionItemComponentProps, TelemetryProps, TelemetryV2Props { variant?: 'actionItem' hideLabel?: boolean @@ -356,6 +357,28 @@ export class ActionItem extends React.PureComponent { if (this.props.onDidExecute) { diff --git a/client/shared/src/actions/ActionsNavItems.test.tsx b/client/shared/src/actions/ActionsNavItems.test.tsx index 5ac3b2c33ff..cf0d4d7d613 100644 --- a/client/shared/src/actions/ActionsNavItems.test.tsx +++ b/client/shared/src/actions/ActionsNavItems.test.tsx @@ -7,6 +7,7 @@ import { ContributableMenu } from '@sourcegraph/client-api' import type { FlatExtensionHostAPI } from '../api/contract' import { pretendProxySubscribable, pretendRemote } from '../api/util' +import { noOpTelemetryRecorder } from '../telemetry' import { NOOP_TELEMETRY_SERVICE } from '../telemetry/telemetryService' import { extensionsController } from '../testing/searchTestHelpers' @@ -42,6 +43,9 @@ describe('ActionItem', () => { label: 'Action A', description: 'This is Action A', }, + telemetryProps: { + feature: 'a', + }, }, ], menus: { @@ -58,6 +62,7 @@ describe('ActionItem', () => { }} platformContext={NOOP_PLATFORM_CONTEXT} telemetryService={NOOP_TELEMETRY_SERVICE} + telemetryRecorder={noOpTelemetryRecorder} /> ) }) diff --git a/client/shared/src/actions/ActionsNavItems.tsx b/client/shared/src/actions/ActionsNavItems.tsx index f1a644e07a8..18645672401 100644 --- a/client/shared/src/actions/ActionsNavItems.tsx +++ b/client/shared/src/actions/ActionsNavItems.tsx @@ -17,6 +17,7 @@ import type { ContributionOptions } from '../api/extension/extensionHostApi' import { getContributedActionItems } from '../contributions/contributions' import type { RequiredExtensionsControllerProps } from '../extensions/controller' import type { PlatformContextProps } from '../platform/context' +import type { TelemetryV2Props } from '../telemetry' import type { TelemetryProps } from '../telemetry/telemetryService' import { ActionItem, type ActionItemProps } from './ActionItem' @@ -55,6 +56,7 @@ export interface ActionsNavItemsProps extends ActionsProps, ActionNavItemsClassProps, TelemetryProps, + TelemetryV2Props, Pick { /** * If true, it renders a `
    ...
` around the items. If there are no items, it renders `null`. diff --git a/client/shared/src/api/client/connection.ts b/client/shared/src/api/client/connection.ts index cc119403a12..276463fbc4a 100644 --- a/client/shared/src/api/client/connection.ts +++ b/client/shared/src/api/client/connection.ts @@ -21,7 +21,13 @@ export async function createExtensionHostClientConnection( initData: Omit, platformContext: Pick< PlatformContext, - 'settings' | 'updateSettings' | 'getGraphQLClient' | 'requestGraphQL' | 'telemetryService' | 'clientApplication' + | 'settings' + | 'updateSettings' + | 'getGraphQLClient' + | 'requestGraphQL' + | 'telemetryService' + | 'telemetryRecorder' + | 'clientApplication' > ): Promise<{ subscription: Unsubscribable diff --git a/client/shared/src/api/client/mainthread-api.test.ts b/client/shared/src/api/client/mainthread-api.test.ts index 7d238751722..29007437448 100644 --- a/client/shared/src/api/client/mainthread-api.test.ts +++ b/client/shared/src/api/client/mainthread-api.test.ts @@ -7,6 +7,7 @@ import { getGraphQLClient as getGraphQLClientBase, type SuccessGraphQLResult } f import { cache } from '../../backend/apolloCache' import type { PlatformContext } from '../../platform/context' import type { SettingsCascade } from '../../settings/settings' +import { noOpTelemetryRecorder } from '../../telemetry' import type { FlatExtensionHostAPI } from '../contract' import { pretendRemote } from '../util' @@ -23,13 +24,19 @@ describe('MainThreadAPI', () => { const platformContext: Pick< PlatformContext, - 'updateSettings' | 'settings' | 'getGraphQLClient' | 'requestGraphQL' | 'clientApplication' + | 'updateSettings' + | 'settings' + | 'getGraphQLClient' + | 'requestGraphQL' + | 'clientApplication' + | 'telemetryRecorder' > = { settings: EMPTY, getGraphQLClient, updateSettings: () => Promise.resolve(), requestGraphQL, clientApplication: 'other', + telemetryRecorder: noOpTelemetryRecorder, } const { api } = initMainThreadAPI(pretendRemote({}), platformContext) @@ -56,13 +63,19 @@ describe('MainThreadAPI', () => { const platformContext: Pick< PlatformContext, - 'updateSettings' | 'settings' | 'getGraphQLClient' | 'requestGraphQL' | 'clientApplication' + | 'updateSettings' + | 'settings' + | 'getGraphQLClient' + | 'requestGraphQL' + | 'clientApplication' + | 'telemetryRecorder' > = { settings: EMPTY, getGraphQLClient, updateSettings: () => Promise.resolve(), requestGraphQL, clientApplication: 'other', + telemetryRecorder: noOpTelemetryRecorder, } const { api } = initMainThreadAPI(pretendRemote({}), platformContext) @@ -82,7 +95,12 @@ describe('MainThreadAPI', () => { } const platformContext: Pick< PlatformContext, - 'updateSettings' | 'settings' | 'requestGraphQL' | 'getGraphQLClient' | 'clientApplication' + | 'updateSettings' + | 'settings' + | 'requestGraphQL' + | 'getGraphQLClient' + | 'clientApplication' + | 'telemetryRecorder' > = { settings: of({ subjects: [ @@ -116,6 +134,7 @@ describe('MainThreadAPI', () => { getGraphQLClient, requestGraphQL: () => EMPTY, clientApplication: 'other', + telemetryRecorder: noOpTelemetryRecorder, } const { api } = initMainThreadAPI( @@ -147,13 +166,19 @@ describe('MainThreadAPI', () => { const platformContext: Pick< PlatformContext, - 'updateSettings' | 'settings' | 'getGraphQLClient' | 'requestGraphQL' | 'clientApplication' + | 'updateSettings' + | 'settings' + | 'getGraphQLClient' + | 'requestGraphQL' + | 'clientApplication' + | 'telemetryRecorder' > = { getGraphQLClient, settings: of(...values), updateSettings: () => Promise.resolve(), requestGraphQL: () => EMPTY, clientApplication: 'other', + telemetryRecorder: noOpTelemetryRecorder, } const passedToExtensionHost: SettingsCascade[] = [] @@ -173,13 +198,19 @@ describe('MainThreadAPI', () => { const values = new Subject>() const platformContext: Pick< PlatformContext, - 'updateSettings' | 'settings' | 'getGraphQLClient' | 'requestGraphQL' | 'clientApplication' + | 'updateSettings' + | 'settings' + | 'getGraphQLClient' + | 'requestGraphQL' + | 'clientApplication' + | 'telemetryRecorder' > = { settings: values.asObservable(), updateSettings: () => Promise.resolve(), getGraphQLClient, requestGraphQL: () => EMPTY, clientApplication: 'other', + telemetryRecorder: noOpTelemetryRecorder, } const passedToExtensionHost: SettingsCascade[] = [] const { subscription } = initMainThreadAPI( diff --git a/client/shared/src/api/client/mainthread-api.ts b/client/shared/src/api/client/mainthread-api.ts index 6c718253535..ede9a8765ca 100644 --- a/client/shared/src/api/client/mainthread-api.ts +++ b/client/shared/src/api/client/mainthread-api.ts @@ -53,6 +53,7 @@ export const initMainThreadAPI = ( | 'requestGraphQL' | 'getStaticExtensions' | 'telemetryService' + | 'telemetryRecorder' | 'clientApplication' > ): { api: MainThreadAPI; exposedToClient: ExposedToClient; subscription: Subscription } => { @@ -140,6 +141,7 @@ export const initMainThreadAPI = ( return proxySubscribable(of([])) }, logEvent: (eventName, eventProperties) => platformContext.telemetryService?.log(eventName, eventProperties), + getTelemetryRecorder: () => platformContext.telemetryRecorder, logExtensionMessage: (...data) => logger.log(...data), } diff --git a/client/shared/src/api/contract.ts b/client/shared/src/api/contract.ts index e5d7ad1d668..50e6af884d9 100644 --- a/client/shared/src/api/contract.ts +++ b/client/shared/src/api/contract.ts @@ -16,6 +16,7 @@ import type { DocumentHighlight, ReferenceContext } from '../codeintel/legacy-ex import type { Occurrence } from '../codeintel/scip' import type { ConfiguredExtension } from '../extensions/extension' import type { SettingsCascade } from '../settings/settings' +import type { TelemetryV2Props } from '../telemetry' import type { SettingsEdit } from './client/services/settings' import type { ExecutableExtension } from './extension/activation' @@ -175,9 +176,16 @@ export interface MainThreadAPI { /** * Log an event (by sending it to the server). + * + * @deprecated use getTelemetryRecorder().recordEvent instead */ logEvent: (eventName: string, eventProperties?: any) => void + /** + * Get a TelemetryRecorder for recording telemetry events to the server. + */ + getTelemetryRecorder: () => TelemetryV2Props['telemetryRecorder'] + /** * Log messages from extensions in the main thread. Makes it easier to debug extensions for applications * in which extensions run in a different page from the main thread diff --git a/client/shared/src/api/extension/activation.ts b/client/shared/src/api/extension/activation.ts index 98bd5450aef..bd170670588 100644 --- a/client/shared/src/api/extension/activation.ts +++ b/client/shared/src/api/extension/activation.ts @@ -66,7 +66,7 @@ const DEPRECATED_EXTENSION_IDS = new Set(['sourcegraph/code-stats-insights', 'so export function activateExtensions( state: Pick, - mainAPI: Remote>, + mainAPI: Remote>, createExtensionAPI: (extensionID: string) => typeof sourcegraph, mainThreadAPIInitializations: Observable, /** @@ -168,6 +168,10 @@ export function activateExtensions( .catch(() => { // noop }) + const telemetryRecorder = await mainAPI.getTelemetryRecorder() + telemetryRecorder.recordEvent('blob.extension', 'activate', { + privateMetadata: { extensionID: telemetryExtensionID }, + }) } catch (error) { logger.error( `Fail to log ExtensionActivation event for extension ${id}:`, diff --git a/client/shared/src/api/extension/api/contribution.test.ts b/client/shared/src/api/extension/api/contribution.test.ts index c07d2f126e0..412ac07796b 100644 --- a/client/shared/src/api/extension/api/contribution.test.ts +++ b/client/shared/src/api/extension/api/contribution.test.ts @@ -13,8 +13,8 @@ import { describe('mergeContributions()', () => { const FIXTURE_CONTRIBUTIONS_1: Evaluated = { actions: [ - { id: '1.a', command: 'c', title: '1.A' }, - { id: '1.b', command: 'c', title: '1.B' }, + { id: '1.a', command: 'c', title: '1.A', telemetryProps: { feature: '1.a' } }, + { id: '1.b', command: 'c', title: '1.B', telemetryProps: { feature: '1.b' } }, ], menus: { [ContributableMenu.GlobalNav]: [{ action: '1.a' }, { action: '1.b' }], @@ -23,8 +23,8 @@ describe('mergeContributions()', () => { const FIXTURE_CONTRIBUTIONS_2: Evaluated = { actions: [ - { id: '2.a', command: 'c', title: '2.A' }, - { id: '2.b', command: 'c', title: '2.B' }, + { id: '2.a', command: 'c', title: '2.A', telemetryProps: { feature: '2.a' } }, + { id: '2.b', command: 'c', title: '2.B', telemetryProps: { feature: '2.b' } }, ], menus: { [ContributableMenu.EditorTitle]: [{ action: '2.a' }, { action: '2.b' }], @@ -32,10 +32,10 @@ describe('mergeContributions()', () => { } const FIXTURE_CONTRIBUTIONS_MERGED: Evaluated = { actions: [ - { id: '1.a', command: 'c', title: '1.A' }, - { id: '1.b', command: 'c', title: '1.B' }, - { id: '2.a', command: 'c', title: '2.A' }, - { id: '2.b', command: 'c', title: '2.B' }, + { id: '1.a', command: 'c', title: '1.A', telemetryProps: { feature: '1.a' } }, + { id: '1.b', command: 'c', title: '1.B', telemetryProps: { feature: '1.b' } }, + { id: '2.a', command: 'c', title: '2.A', telemetryProps: { feature: '2.a' } }, + { id: '2.b', command: 'c', title: '2.B', telemetryProps: { feature: '2.b' } }, ], menus: { [ContributableMenu.GlobalNav]: [{ action: '1.a' }, { action: '1.b' }], @@ -77,9 +77,9 @@ describe('filterContributions()', () => { it('handles non-empty contributions', () => { const expected: Evaluated = { actions: [ - { id: 'a1', command: 'c' }, - { id: 'a2', command: 'c' }, - { id: 'a3', command: 'c' }, + { id: 'a1', command: 'c', telemetryProps: { feature: 'a1' } }, + { id: 'a2', command: 'c', telemetryProps: { feature: 'a2' } }, + { id: 'a3', command: 'c', telemetryProps: { feature: 'a3' } }, ], menus: { [ContributableMenu.GlobalNav]: [{ action: 'a1', when: true }, { action: 'a2' }], @@ -88,9 +88,9 @@ describe('filterContributions()', () => { expect( filterContributions({ actions: [ - { id: 'a1', command: 'c' }, - { id: 'a2', command: 'c' }, - { id: 'a3', command: 'c' }, + { id: 'a1', command: 'c', telemetryProps: { feature: 'a1' } }, + { id: 'a2', command: 'c', telemetryProps: { feature: 'a2' } }, + { id: 'a3', command: 'c', telemetryProps: { feature: 'a3' } }, ], menus: { [ContributableMenu.GlobalNav]: [ @@ -139,12 +139,14 @@ describe('evaluateContributions()', () => { iconDescription: parseTemplate('${replaceMe}'), iconURL: parseTemplate('${replaceMe}'), }, + telemetryProps: { feature: 'a1' }, }, { id: 'a2', command: 'c2', title: parseTemplate('${replaceMe}'), category: parseTemplate('b'), + telemetryProps: { feature: 'a2' }, }, { id: 'a3', @@ -155,6 +157,7 @@ describe('evaluateContributions()', () => { label: parseTemplate('${replaceMe}'), description: parseTemplate('b'), }, + telemetryProps: { feature: 'a3' }, }, ], } @@ -175,9 +178,17 @@ describe('evaluateContributions()', () => { iconDescription: 'x', iconURL: 'x', }, + telemetryProps: { feature: 'a1' }, + }, + { id: 'a2', command: 'c2', title: 'x', category: 'b', telemetryProps: { feature: 'a2' } }, + { + id: 'a3', + command: 'c3', + title: 'b', + category: 'b', + actionItem: { label: 'x', description: 'b' }, + telemetryProps: { feature: 'a3' }, }, - { id: 'a2', command: 'c2', title: 'x', category: 'b' }, - { id: 'a3', command: 'c3', title: 'b', category: 'b', actionItem: { label: 'x', description: 'b' } }, ], } expect(evaluateContributions(FIXTURE_CONTEXT, input)).toEqual(expected) @@ -191,6 +202,7 @@ describe('evaluateContributions()', () => { id: 'x', command: 'c', commandArguments: ['b', 'x', 'b', 'x'], + telemetryProps: { feature: 'x' }, }, ], } @@ -206,6 +218,7 @@ describe('evaluateContributions()', () => { parseTemplate('b'), parseTemplate('${replaceMe}'), ], + telemetryProps: { feature: 'x' }, }, ], }) @@ -222,11 +235,14 @@ describe('evaluateContributions()', () => { actionItem: { pressed: parse('a'), }, + telemetryProps: { feature: 'a' }, }, ], } expect(evaluateContributions(FIXTURE_CONTEXT, input)).toEqual({ - actions: [{ id: 'a', command: 'c', title: 'a', actionItem: { pressed: true } }], + actions: [ + { id: 'a', command: 'c', title: 'a', actionItem: { pressed: true }, telemetryProps: { feature: 'a' } }, + ], }) }) }) @@ -234,11 +250,11 @@ describe('evaluateContributions()', () => { describe('parseContributionExpressions()', () => { it('should not parse the `id` or `command` values', () => { const expected: Contributions = { - actions: [{ id: '${replaceMe}', command: '${c}' }], + actions: [{ id: '${replaceMe}', command: '${c}', telemetryProps: { feature: 'a' } }], } expect( parseContributionExpressions({ - actions: [{ id: '${replaceMe}', command: '${c}' }], + actions: [{ id: '${replaceMe}', command: '${c}', telemetryProps: { feature: 'a' } }], }) ).toEqual(expected) }) diff --git a/client/shared/src/api/extension/test/activation.test.ts b/client/shared/src/api/extension/test/activation.test.ts index 06f9f2aedb6..8b18c5d3d1b 100644 --- a/client/shared/src/api/extension/test/activation.test.ts +++ b/client/shared/src/api/extension/test/activation.test.ts @@ -7,6 +7,7 @@ import { describe, it } from 'vitest' import type { Contributions } from '@sourcegraph/client-api' import type { SettingsCascade } from '../../../settings/settings' +import { noOpTelemetryRecorder } from '../../../telemetry' import type { MainThreadAPI } from '../../contract' import { pretendRemote } from '../../util' import { activateExtensions, type ExecutableExtension } from '../activation' @@ -17,8 +18,9 @@ describe('Extension activation', () => { it('logs events for activated extensions', async () => { const logEvent = sinon.spy() - const mockMain = pretendRemote>({ + const mockMain = pretendRemote>({ logEvent, + getTelemetryRecorder: () => noOpTelemetryRecorder, }) const FIXTURE_EXTENSION: ExecutableExtension = { diff --git a/client/shared/src/codeintel/legacy-extensions/api.ts b/client/shared/src/codeintel/legacy-extensions/api.ts index 04ea5b2266c..a31c30392d3 100644 --- a/client/shared/src/codeintel/legacy-extensions/api.ts +++ b/client/shared/src/codeintel/legacy-extensions/api.ts @@ -5,6 +5,7 @@ import type { GraphQLResult } from '@sourcegraph/http-client' import type { PlatformContext } from '../../platform/context' import type { Settings, SettingsCascade } from '../../settings/settings' +import type { TelemetryV2Props } from '../../telemetry' /** * Represents a location inside a resource, such as a line @@ -377,7 +378,8 @@ export function updateCodeIntelContext(newContext: CodeIntelContext): void { context = newContext } -export interface CodeIntelContext extends Pick { +export interface CodeIntelContext + extends Pick { settings: SettingsGetter } @@ -419,3 +421,7 @@ export function logTelemetryEvent( ): void { context?.telemetryService?.log(eventName, eventProperties) } + +export function getTelemetryRecorder(): TelemetryV2Props['telemetryRecorder'] | undefined { + return context?.telemetryRecorder +} diff --git a/client/shared/src/codeintel/legacy-extensions/providers.ts b/client/shared/src/codeintel/legacy-extensions/providers.ts index 916c57253e5..f7c44b82d53 100644 --- a/client/shared/src/codeintel/legacy-extensions/providers.ts +++ b/client/shared/src/codeintel/legacy-extensions/providers.ts @@ -7,7 +7,7 @@ import type { LanguageSpec } from './language-specs/language-spec' import { type Logger, NoopLogger } from './logging' import { createProviders as createLSIFProviders } from './lsif/providers' import { createProviders as createSearchProviders } from './search/providers' -import { TelemetryEmitter } from './telemetry' +import { type CodeIntelActions, TelemetryEmitter } from './telemetry' import { API } from './util/api' import { asArray, mapArrayish, nonEmpty } from './util/helpers' import { noopAsyncGenerator, observableFromAsyncIterator } from './util/ix' @@ -465,7 +465,7 @@ function logLocationResults, logger, }: { provider: string - action: string + action: CodeIntelActions repo: string textDocument: sourcegraph.TextDocument position: sourcegraph.Position @@ -473,11 +473,11 @@ function logLocationResults, emitter?: TelemetryEmitter logger?: Logger }): void { - emitter?.emitOnce(action) + emitter?.emitOnce(false, action) // Emit xrepo event if we contain a result from another repository if (asArray(results).some(location => parseGitURI(location.uri.toString()).repo !== repo)) { - emitter?.emitOnce(action + '.xrepo') + emitter?.emitOnce(true, action) } if (logger) { @@ -555,7 +555,7 @@ export function createHoverProvider( } } - emitter.emitOnce('lsifHover') + emitter.emitOnce(false, 'lsifHover') logger?.log({ provider: 'hover', precise: true, ...commonLogFields }) yield badgeHoverResult( lsifWrapper.hover, @@ -582,7 +582,7 @@ export function createHoverProvider( continue } - emitter.emitOnce('searchHover') + emitter.emitOnce(false, 'searchHover') logger?.log({ provider: 'hover', precise: false, ...commonLogFields }) if (hasPreciseDefinition) { @@ -634,14 +634,14 @@ export function createDocumentHighlightProvider( for await (const lsifResult of lsifProvider(textDocument, position)) { if (lsifResult) { - emitter.emitOnce('lsifDocumentHighlight') + emitter.emitOnce(false, 'lsifDocumentHighlight') yield lsifResult hasLsifResults = true } } if (!hasLsifResults) { - emitter.emitOnce('searchDocumentHighlight') + emitter.emitOnce(false, 'searchDocumentHighlight') for await (const searchResult of searchProvider(textDocument, position)) { if (searchResult) { yield searchResult diff --git a/client/shared/src/codeintel/legacy-extensions/telemetry.ts b/client/shared/src/codeintel/legacy-extensions/telemetry.ts index c9cf73a0d34..acb5ed56b1c 100644 --- a/client/shared/src/codeintel/legacy-extensions/telemetry.ts +++ b/client/shared/src/codeintel/legacy-extensions/telemetry.ts @@ -1,5 +1,16 @@ import * as sourcegraph from './api' +export type CodeIntelActions = + | 'lsifHover' + | 'searchHover' + | 'lsifDocumentHighlight' + | 'searchDocumentHighlight' + | 'lsifDefinitions' + | 'searchDefinitions' + | 'lsifReferences' + | 'searchReferences' + | 'lsifImplementations' + /** * A wrapper around telemetry events. A new instance of this class * should be instantiated at the start of each action as it handles @@ -33,31 +44,38 @@ export class TelemetryEmitter { * same action has not yet emitted for this instance. This method * returns true if an event was emitted and false otherwise. */ - public emitOnce(action: string, args: object = {}): boolean { + public emitOnce(xrepo: boolean, action: CodeIntelActions, args: object = {}): boolean { if (this.emitted.has(action)) { return false } this.emitted.add(action) - this.emit(action, args) + this.emit(xrepo, action, args) return true } /** * Emit a telemetry event with durationMs and languageId attributes. */ - public emit(action: string, args: object = {}): void { + public emit(xrepo: boolean, action: CodeIntelActions, args: object = {}): void { if (!this.enabled) { return } try { - sourcegraph.logTelemetryEvent(`codeintel.${action}`, { + sourcegraph.logTelemetryEvent(`codeintel.${action + xrepo ? '.xrepo' : ''}`, { ...args, durationMs: this.elapsed(), languageId: this.languageID, repositoryId: this.repoID, }) + const telemetryRecorder = sourcegraph.getTelemetryRecorder() + telemetryRecorder?.recordEvent(`blob.codeintel${xrepo ? '.xrepo' : ''}`, action, { + metadata: { + durationMs: this.elapsed(), + repositoryId: this.repoID, + }, + }) } catch { // Older version of Sourcegraph may have not registered this // command, causing the promise to reject. We can safely ignore diff --git a/client/shared/src/commands/commands.ts b/client/shared/src/commands/commands.ts index 0f8a21e81fe..e6960888eb5 100644 --- a/client/shared/src/commands/commands.ts +++ b/client/shared/src/commands/commands.ts @@ -17,7 +17,10 @@ import type { PlatformContext } from '../platform/context' * documentation. */ export function registerBuiltinClientCommands( - context: Pick, + context: Pick< + PlatformContext, + 'requestGraphQL' | 'telemetryService' | 'telemetryRecorder' | 'settings' | 'updateSettings' + >, extensionHost: Remote, registerCommand: (entryToRegister: CommandEntry) => Unsubscribable ): Unsubscribable { @@ -116,6 +119,8 @@ export function registerBuiltinClientCommands( if (context.telemetryService) { context.telemetryService.log(eventName, eventProperties) } + // TODO (dadlerj): cannot log telemetry v2 events here as the name isn't a known string. + // TBD whether this is needed. return Promise.resolve() }, }) diff --git a/client/shared/src/contributions/contributions.test.ts b/client/shared/src/contributions/contributions.test.ts index aa8a0e4a6af..6672eb456d6 100644 --- a/client/shared/src/contributions/contributions.test.ts +++ b/client/shared/src/contributions/contributions.test.ts @@ -12,9 +12,9 @@ describe('getContributedActionItems', () => { getContributedActionItems( { actions: [ - { id: 'a', command: 'a', title: 'ta', description: 'da' }, - { id: 'b', command: 'b', title: 'tb', description: 'db' }, - { id: 'c', command: 'c', title: 'tc', description: 'dc' }, + { id: 'a', command: 'a', title: 'ta', description: 'da', telemetryProps: { feature: 'a' } }, + { id: 'b', command: 'b', title: 'tb', description: 'db', telemetryProps: { feature: 'b' } }, + { id: 'c', command: 'c', title: 'tc', description: 'dc', telemetryProps: { feature: 'c' } }, ], menus: { 'editor/title': [{ action: 'b', alt: 'c' }, { action: 'a' }], @@ -24,13 +24,13 @@ describe('getContributedActionItems', () => { ) ).toEqual([ { - action: { id: 'b', command: 'b', title: 'tb', description: 'db' }, + action: { id: 'b', command: 'b', title: 'tb', description: 'db', telemetryProps: { feature: 'b' } }, active: true, - altAction: { id: 'c', command: 'c', title: 'tc', description: 'dc' }, + altAction: { id: 'c', command: 'c', title: 'tc', description: 'dc', telemetryProps: { feature: 'c' } }, disabledWhen: false, }, { - action: { id: 'a', command: 'a', title: 'ta', description: 'da' }, + action: { id: 'a', command: 'a', title: 'ta', description: 'da', telemetryProps: { feature: 'a' } }, active: true, altAction: undefined, disabledWhen: false, diff --git a/client/shared/src/extensions/createSyncLoadedController.ts b/client/shared/src/extensions/createSyncLoadedController.ts index 90a7081464a..bdf495a8875 100644 --- a/client/shared/src/extensions/createSyncLoadedController.ts +++ b/client/shared/src/extensions/createSyncLoadedController.ts @@ -25,6 +25,7 @@ export function createController( | 'requestGraphQL' | 'getStaticExtensions' | 'telemetryService' + | 'telemetryRecorder' | 'clientApplication' | 'sourcegraphURL' | 'createExtensionHost' diff --git a/client/shared/src/hover/HoverOverlay.fixtures.ts b/client/shared/src/hover/HoverOverlay.fixtures.ts index 7fa3ca1799a..e438f63d7c0 100644 --- a/client/shared/src/hover/HoverOverlay.fixtures.ts +++ b/client/shared/src/hover/HoverOverlay.fixtures.ts @@ -5,6 +5,7 @@ import { MarkupKind } from '@sourcegraph/extension-api-classes' import type { ActionItemAction } from '../actions/ActionItem' import type { MarkupContent, Badged, AggregableBadge } from '../codeintel/legacy-extensions/api' import { EMPTY_SETTINGS_CASCADE, type SettingsCascadeProps } from '../settings/settings' +import { noOpTelemetryRecorder } from '../telemetry' import { NOOP_TELEMETRY_SERVICE } from '../telemetry/telemetryService' import type { HoverOverlayProps } from './HoverOverlay' @@ -15,6 +16,7 @@ const NOOP_EXTENSIONS_CONTROLLER = { executeCommand: () => Promise.resolve() } export const commonProps = (): HoverOverlayProps & SettingsCascadeProps => ({ location: history.location, telemetryService: NOOP_TELEMETRY_SERVICE, + telemetryRecorder: noOpTelemetryRecorder, extensionsController: NOOP_EXTENSIONS_CONTROLLER, overlayPosition: { top: 16, left: 16 }, settingsCascade: EMPTY_SETTINGS_CASCADE, @@ -40,6 +42,9 @@ export const FIXTURE_ACTIONS: ActionItemAction[] = [ title: 'Go to definition', command: 'open', commandArguments: ['/github.com/sourcegraph/codeintellify/-/blob/src/hoverifier.ts#L57:1'], + telemetryProps: { + feature: 'blob.goToDefinition.preloaded', + }, }, active: true, }, @@ -49,6 +54,9 @@ export const FIXTURE_ACTIONS: ActionItemAction[] = [ title: 'Find references', command: 'open', commandArguments: ['/github.com/sourcegraph/codeintellify/-/blob/src/hoverifier.ts?tab=references#L57:18'], + telemetryProps: { + feature: 'blob.findReferences', + }, }, active: true, }, diff --git a/client/shared/src/hover/HoverOverlay.test.tsx b/client/shared/src/hover/HoverOverlay.test.tsx index 1bcd564ffb4..604c5eb3a55 100644 --- a/client/shared/src/hover/HoverOverlay.test.tsx +++ b/client/shared/src/hover/HoverOverlay.test.tsx @@ -6,6 +6,7 @@ import { describe, expect, test } from 'vitest' import { subtypeOf } from '@sourcegraph/common' import { MarkupKind } from '@sourcegraph/extension-api-classes' +import { noOpTelemetryRecorder } from '../telemetry' import { NOOP_TELEMETRY_SERVICE } from '../telemetry/telemetryService' import { HoverOverlay, type HoverOverlayProps } from './HoverOverlay' @@ -17,6 +18,7 @@ describe('HoverOverlay', () => { const commonProps = subtypeOf()({ location: history.location, telemetryService: NOOP_TELEMETRY_SERVICE, + telemetryRecorder: noOpTelemetryRecorder, extensionsController: NOOP_EXTENSIONS_CONTROLLER, platformContext: NOOP_PLATFORM_CONTEXT, hoveredToken: { repoName: 'r', commitID: 'c', revision: 'v', filePath: 'f', line: 1, character: 2 }, @@ -62,7 +64,12 @@ describe('HoverOverlay', () => { render( ).asFragment() ).toMatchSnapshot() @@ -100,7 +107,9 @@ describe('HoverOverlay', () => { render( ).asFragment() @@ -112,7 +121,9 @@ describe('HoverOverlay', () => { render( ).asFragment() @@ -172,7 +183,9 @@ describe('HoverOverlay', () => { render( ).asFragment() diff --git a/client/shared/src/hover/HoverOverlay.tsx b/client/shared/src/hover/HoverOverlay.tsx index 4a08c39c30e..0ef004c1a33 100644 --- a/client/shared/src/hover/HoverOverlay.tsx +++ b/client/shared/src/hover/HoverOverlay.tsx @@ -7,6 +7,7 @@ import { isErrorLike, sanitizeClass } from '@sourcegraph/common' import { Card, Icon, Button } from '@sourcegraph/wildcard' import { ActionItem, type ActionItemComponentProps } from '../actions/ActionItem' +import type { TelemetryV2Props } from '../telemetry' import type { TelemetryProps } from '../telemetry/telemetryService' import { CopyLinkIcon } from './CopyLinkIcon' @@ -45,7 +46,8 @@ export interface HoverOverlayProps extends HoverOverlayBaseProps, ActionItemComponentProps, HoverOverlayClassProps, - TelemetryProps { + TelemetryProps, + TelemetryV2Props { /** A ref callback to get the root overlay element. Use this to calculate the position. */ hoverRef?: React.Ref @@ -92,6 +94,7 @@ export const HoverOverlay: React.FunctionComponent telemetryService.log('hover') + const logTelemetryEvent = (): void => { + telemetryService.log('hover') + telemetryRecorder.recordEvent('blob', 'hover') + } // Log a telemetry event on component mount once, so we don't care about dependency updates. // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/client/shared/src/search/searchQueryState.tsx b/client/shared/src/search/searchQueryState.tsx index ebd0f85c60d..40c3fe365ec 100644 --- a/client/shared/src/search/searchQueryState.tsx +++ b/client/shared/src/search/searchQueryState.tsx @@ -3,7 +3,7 @@ import React, { createContext } from 'react' import type { StoreApi, UseBoundStore } from 'zustand' import type { SearchPatternType } from '../graphql-operations' -import { TelemetryV2Props } from '../telemetry' +import type { TelemetryV2Props } from '../telemetry' import { type QueryState, type SubmitSearchParameters, toggleSubquery } from './helpers' import type { FilterType } from './query/filters' diff --git a/client/shared/src/testing/testHelpers.ts b/client/shared/src/testing/testHelpers.ts index 46effbb4e55..783a39d19f5 100644 --- a/client/shared/src/testing/testHelpers.ts +++ b/client/shared/src/testing/testHelpers.ts @@ -9,6 +9,7 @@ import { type InitData, startExtensionHost } from '../api/extension/extensionHos import type { WorkspaceRootWithMetadata } from '../api/extension/extensionHostApi' import type { TextDocumentData, ViewerData } from '../api/viewerTypes' import type { EndpointPair, PlatformContext } from '../platform/context' +import { noOpTelemetryRecorder } from '../telemetry' export function assertToJSON(a: any, expected: any): void { const raw = JSON.stringify(a) @@ -38,7 +39,12 @@ const FIXTURE_INIT_DATA: TestInitData = { interface Mocks extends Pick< PlatformContext, - 'settings' | 'updateSettings' | 'getGraphQLClient' | 'requestGraphQL' | 'clientApplication' + | 'settings' + | 'updateSettings' + | 'getGraphQLClient' + | 'requestGraphQL' + | 'clientApplication' + | 'telemetryRecorder' > {} const NOOP_MOCKS: Mocks = { @@ -47,6 +53,7 @@ const NOOP_MOCKS: Mocks = { getGraphQLClient: () => Promise.reject(new Error('Mocks#getGraphQLClient not implemented')), requestGraphQL: () => throwError(() => new Error('Mocks#queryGraphQL not implemented')), clientApplication: 'sourcegraph', + telemetryRecorder: noOpTelemetryRecorder, } /** diff --git a/client/web-sveltekit/src/routes/[...repo=reporev]/(validrev)/(code)/-/blob/[...path]/FileView.svelte b/client/web-sveltekit/src/routes/[...repo=reporev]/(validrev)/(code)/-/blob/[...path]/FileView.svelte index cb1af440068..9b81a0554a3 100644 --- a/client/web-sveltekit/src/routes/[...repo=reporev]/(validrev)/(code)/-/blob/[...path]/FileView.svelte +++ b/client/web-sveltekit/src/routes/[...repo=reporev]/(validrev)/(code)/-/blob/[...path]/FileView.svelte @@ -8,6 +8,8 @@ import { from } from 'rxjs' import { writable } from 'svelte/store' + import { noOpTelemetryRecorder } from '@sourcegraph/shared/src/telemetry' + import { goto, preloadData, afterNavigate } from '$app/navigation' import { page } from '$app/stores' import type { ScrollSnapshot } from '$lib/codemirror/utils' @@ -92,6 +94,7 @@ requestGraphQL(options) { return from(graphQLClient.query(options.request, options.variables).then(toGraphQLResult)) }, + telemetryRecorder: noOpTelemetryRecorder, }) : null diff --git a/client/web-sveltekit/src/routes/search/PreviewPanel.svelte b/client/web-sveltekit/src/routes/search/PreviewPanel.svelte index 72ade46c7a6..988200accd3 100644 --- a/client/web-sveltekit/src/routes/search/PreviewPanel.svelte +++ b/client/web-sveltekit/src/routes/search/PreviewPanel.svelte @@ -13,6 +13,8 @@ import { mdiClose } from '@mdi/js' import { from } from 'rxjs' + import { noOpTelemetryRecorder } from '@sourcegraph/shared/src/telemetry' + import CodeMirrorBlob from '$lib/CodeMirrorBlob.svelte' import { isErrorLike } from '$lib/common' import { getGraphQLClient, mapOrThrow, toGraphQLResult } from '$lib/graphql' @@ -49,6 +51,7 @@ requestGraphQL(options) { return from(client.query(options.request, options.variables).then(toGraphQLResult)) }, + telemetryRecorder: noOpTelemetryRecorder, }) $: blobStore = toReadable( diff --git a/client/web/package.json b/client/web/package.json index 6127a88dc82..eb9f568c8b7 100644 --- a/client/web/package.json +++ b/client/web/package.json @@ -45,8 +45,8 @@ "@sourcegraph/observability-client": "workspace:*", "@sourcegraph/schema": "workspace:*", "@sourcegraph/shared": "workspace:*", - "@sourcegraph/telemetry": "^0.11.0", + "@sourcegraph/telemetry": "^0.16.0", "@sourcegraph/wildcard": "workspace:*", "mermaid": "^10.9.1" } -} +} \ No newline at end of file diff --git a/client/web/src/repo/blob/CodeMirrorBlob.tsx b/client/web/src/repo/blob/CodeMirrorBlob.tsx index ee31f2ba287..a840a6d288a 100644 --- a/client/web/src/repo/blob/CodeMirrorBlob.tsx +++ b/client/web/src/repo/blob/CodeMirrorBlob.tsx @@ -22,7 +22,7 @@ import { useKeyboardShortcut } from '@sourcegraph/shared/src/keyboardShortcuts/u import { Shortcut } from '@sourcegraph/shared/src/react-shortcuts' import { useSettings } from '@sourcegraph/shared/src/settings/settings' import type { TemporarySettingsSchema } from '@sourcegraph/shared/src/settings/temporary/TemporarySettings' -import { type TelemetryV2Props, noOpTelemetryRecorder } from '@sourcegraph/shared/src/telemetry' +import { type TelemetryV2Props } from '@sourcegraph/shared/src/telemetry' import type { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService' import { useIsLightTheme } from '@sourcegraph/shared/src/theme' import { codeCopiedEvent } from '@sourcegraph/shared/src/tracking/event-log-creators' @@ -313,6 +313,7 @@ export const CodeMirrorBlob: React.FunctionComponent = props => { ) const codeIntelExtension = useCodeIntelExtension( telemetryService, + telemetryRecorder, { repoName: blobInfo.repoName, filePath: blobInfo.filePath, @@ -355,8 +356,7 @@ export const CodeMirrorBlob: React.FunctionComponent = props => { codeFoldingExtension(), isCodyEnabledForFile ? codyWidgetExtension( - // TODO: replace with real telemetryRecorder - noOpTelemetryRecorder, + telemetryRecorder, editorRef.current ? new CodeMirrorEditor({ view: editorRef.current, @@ -538,6 +538,7 @@ export const CodeMirrorBlob: React.FunctionComponent = props => { function useCodeIntelExtension( telemetryService: TelemetryProps['telemetryService'], + telemetryRecorder: TelemetryV2Props['telemetryRecorder'], { repoName, filePath, @@ -559,9 +560,10 @@ function useCodeIntelExtension( settings: name => settings[name], requestGraphQL: requestGraphQLAdapter(apolloClient), telemetryService, + telemetryRecorder, }) : null, - [settings, apolloClient, telemetryService] + [settings, apolloClient, telemetryService, telemetryRecorder] ) useEffect(() => { diff --git a/client/web/src/repo/blob/codemirror/codeintel/api.ts b/client/web/src/repo/blob/codemirror/codeintel/api.ts index cf2336ae9b3..3c715834038 100644 --- a/client/web/src/repo/blob/codemirror/codeintel/api.ts +++ b/client/web/src/repo/blob/codemirror/codeintel/api.ts @@ -340,6 +340,9 @@ export class CodeIntelAPIAdapter { disabledTitle: definition.type === 'none' ? 'No definition found' : 'You are at the definition', command: 'open', + telemetryProps: { + feature: 'blob.goToDefinition', + }, }, }) } else if (definition.type === 'initial') { @@ -350,6 +353,9 @@ export class CodeIntelAPIAdapter { title: 'Go to definition', command: 'invokeFunction-new', commandArguments: [() => this.goToDefinitionAtOccurrence(view, occurrence)], + telemetryProps: { + feature: 'blob.goToDefinition', + }, }, }) } else { @@ -377,6 +383,9 @@ export class CodeIntelAPIAdapter { return false }, ], + telemetryProps: { + feature: 'blob.goToDefinition', + }, }, }) } @@ -389,6 +398,9 @@ export class CodeIntelAPIAdapter { commandArguments: [ () => this.config.openReferences(view, this.config.documentInfo, occurrence), ], + telemetryProps: { + feature: 'blob.findReferences', + }, }, }) @@ -402,6 +414,9 @@ export class CodeIntelAPIAdapter { commandArguments: [ () => this.config.openImplementations(view, this.config.documentInfo, occurrence), ], + telemetryProps: { + feature: 'blob.findImplementations', + }, }, }) } @@ -412,6 +427,9 @@ export class CodeIntelAPIAdapter { title: '?', // special marker for the MDI "Help" icon. description: `Go to definition with ${modifierClickDescription}, long-click, or by pressing Enter with the keyboard. Display this popover by pressing Space with the keyboard.`, command: '', + telemetryProps: { + feature: 'blob.goToDefinition.help', + }, }, }) return actions diff --git a/client/web/src/repo/blob/codemirror/tooltips/HovercardView.tsx b/client/web/src/repo/blob/codemirror/tooltips/HovercardView.tsx index 1327bad0c5f..bb2173ccc9a 100644 --- a/client/web/src/repo/blob/codemirror/tooltips/HovercardView.tsx +++ b/client/web/src/repo/blob/codemirror/tooltips/HovercardView.tsx @@ -128,6 +128,7 @@ export class HovercardView implements TooltipView { location={props.location} onHoverShown={props.onHoverShown} telemetryService={props.telemetryService} + telemetryRecorder={props.telemetryRecorder} extensionsController={NOOP_EXTENSION_CONTROLLER} // Hover props actionsOrError={actionsOrError} diff --git a/client/web/src/search/results/components/new-search-content/NewSearchContent.tsx b/client/web/src/search/results/components/new-search-content/NewSearchContent.tsx index 289674e412a..52134281fda 100644 --- a/client/web/src/search/results/components/new-search-content/NewSearchContent.tsx +++ b/client/web/src/search/results/components/new-search-content/NewSearchContent.tsx @@ -35,7 +35,7 @@ import { type StreamSearchOptions, } from '@sourcegraph/shared/src/search/stream' import type { SettingsCascadeProps } from '@sourcegraph/shared/src/settings/settings' -import { TelemetryV2Props } from '@sourcegraph/shared/src/telemetry' +import type { TelemetryV2Props } from '@sourcegraph/shared/src/telemetry' import { NOOP_TELEMETRY_SERVICE, type TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService' import { lazyComponent } from '@sourcegraph/shared/src/util/lazyComponent' import { Button, H2, H4, Icon, Link, Panel, useLocalStorage, useScrollManager } from '@sourcegraph/wildcard' diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8af360bd31a..f1bbda76283 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1391,8 +1391,8 @@ importers: specifier: workspace:* version: link:../http-client '@sourcegraph/telemetry': - specifier: ^0.11.0 - version: 0.11.0 + specifier: ^0.16.0 + version: 0.16.0 '@sourcegraph/template-parser': specifier: workspace:* version: link:../template-parser @@ -1503,8 +1503,8 @@ importers: specifier: workspace:* version: link:../shared '@sourcegraph/telemetry': - specifier: ^0.11.0 - version: 0.11.0 + specifier: ^0.16.0 + version: 0.16.0 '@sourcegraph/wildcard': specifier: workspace:* version: link:../wildcard @@ -8616,8 +8616,8 @@ packages: stylelint: 14.3.0 dev: true - /@sourcegraph/telemetry@0.11.0: - resolution: {integrity: sha512-cOlkCwX3V5lVJO/F8w7VauhMZob3PrMbE4DApTKwasTMwm4+OxtT6+afC5wJTwZwNWc31V83sNjV2nBkEtFPZg==} + /@sourcegraph/telemetry@0.16.0: + resolution: {integrity: sha512-/LTbGWwscy/iNOYc0JS2Sl5Q21WOaiIhABlGynwJLMfPYy0YoJQfEtrCEGTSYWY735dSdUBW3wcTgCOCz8EEjA==} dependencies: rxjs: 7.8.1 dev: false