mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 13:31:54 +00:00
v2t: add v2 telemetry to the client/shared folder (#62586)
This commit is contained in:
parent
b215eb9fb5
commit
8275054987
@ -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,
|
||||
|
||||
@ -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<CodeIntelligenceProps, 'codeHost' | 'platformContext' | 'extensionsController' | 'telemetryService'> & {
|
||||
}: Pick<
|
||||
CodeIntelligenceProps,
|
||||
'codeHost' | 'platformContext' | 'extensionsController' | 'telemetryService' | 'telemetryRecorder'
|
||||
> & {
|
||||
render: Renderer
|
||||
mutations: Observable<MutationRecordLike[]>
|
||||
repoSyncErrors: Observable<boolean>
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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 }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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:*"
|
||||
},
|
||||
|
||||
@ -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<Partial<ActionItemProps>>()({
|
||||
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 = () => (
|
||||
<ActionItem
|
||||
{...commonProps}
|
||||
action={{ id: 'a', command: undefined, actionItem: { label: 'Hello' } }}
|
||||
action={{ id: 'a', command: undefined, actionItem: { label: 'Hello' }, telemetryProps: { feature: 'a' } }}
|
||||
variant="actionItem"
|
||||
/>
|
||||
)
|
||||
@ -52,7 +54,7 @@ NoopAction.storyName = 'Noop action'
|
||||
export const CommandAction: StoryFn = () => (
|
||||
<ActionItem
|
||||
{...commonProps}
|
||||
action={{ id: 'a', command: 'c', title: 'Hello', iconURL: ICON_URL }}
|
||||
action={{ id: 'a', command: 'c', title: 'Hello', iconURL: ICON_URL, telemetryProps: { feature: 'a' } }}
|
||||
telemetryService={NOOP_TELEMETRY_SERVICE}
|
||||
disabledDuringExecution={true}
|
||||
showLoadingSpinnerDuringExecution={true}
|
||||
@ -76,6 +78,7 @@ export const LinkAction: 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 (
|
||||
<ActionItemExecuting
|
||||
{...commonProps}
|
||||
action={{ id: 'a', command: 'c', title: 'Hello', iconURL: ICON_URL }}
|
||||
action={{ id: 'a', command: 'c', title: 'Hello', iconURL: ICON_URL, telemetryProps: { feature: 'a' } }}
|
||||
disabledDuringExecution={true}
|
||||
showLoadingSpinnerDuringExecution={true}
|
||||
/>
|
||||
@ -113,7 +116,7 @@ export const _Error: StoryFn = () => {
|
||||
return (
|
||||
<ActionItemWithError
|
||||
{...commonProps}
|
||||
action={{ id: 'a', command: 'c', title: 'Hello', iconURL: ICON_URL }}
|
||||
action={{ id: 'a', command: 'c', title: 'Hello', iconURL: ICON_URL, telemetryProps: { feature: 'a' } }}
|
||||
disabledDuringExecution={true}
|
||||
showLoadingSpinnerDuringExecution={true}
|
||||
/>
|
||||
|
||||
@ -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(
|
||||
<ActionItem
|
||||
active={true}
|
||||
action={{ id: 'c', command: 'c', title: 't', description: 'd', iconURL: 'u', category: 'g' }}
|
||||
action={{
|
||||
id: 'c',
|
||||
command: 'c',
|
||||
title: 't',
|
||||
description: 'd',
|
||||
iconURL: 'u',
|
||||
category: 'g',
|
||||
telemetryProps: { feature: 'a' },
|
||||
}}
|
||||
telemetryService={NOOP_TELEMETRY_SERVICE}
|
||||
telemetryRecorder={noOpTelemetryRecorder}
|
||||
location={history.location}
|
||||
extensionsController={NOOP_EXTENSIONS_CONTROLLER}
|
||||
/>
|
||||
@ -33,8 +43,17 @@ describe('ActionItem', () => {
|
||||
const component = render(
|
||||
<ActionItem
|
||||
active={true}
|
||||
action={{ id: 'c', command: 'c', title: 't', description: 'd', iconURL: 'u', category: 'g' }}
|
||||
action={{
|
||||
id: 'c',
|
||||
command: 'c',
|
||||
title: 't',
|
||||
description: 'd',
|
||||
iconURL: 'u',
|
||||
category: 'g',
|
||||
telemetryProps: { feature: 'a' },
|
||||
}}
|
||||
telemetryService={NOOP_TELEMETRY_SERVICE}
|
||||
telemetryRecorder={noOpTelemetryRecorder}
|
||||
variant="actionItem"
|
||||
location={history.location}
|
||||
extensionsController={NOOP_EXTENSIONS_CONTROLLER}
|
||||
@ -47,8 +66,16 @@ describe('ActionItem', () => {
|
||||
const component = render(
|
||||
<ActionItem
|
||||
active={true}
|
||||
action={{ id: 'c', title: 't', description: 'd', iconURL: 'u', category: 'g' }}
|
||||
action={{
|
||||
id: 'c',
|
||||
title: 't',
|
||||
description: 'd',
|
||||
iconURL: 'u',
|
||||
category: 'g',
|
||||
telemetryProps: { feature: 'a' },
|
||||
}}
|
||||
telemetryService={NOOP_TELEMETRY_SERVICE}
|
||||
telemetryRecorder={noOpTelemetryRecorder}
|
||||
location={history.location}
|
||||
extensionsController={NOOP_EXTENSIONS_CONTROLLER}
|
||||
/>
|
||||
@ -60,8 +87,14 @@ describe('ActionItem', () => {
|
||||
const component = render(
|
||||
<ActionItem
|
||||
active={true}
|
||||
action={{ id: 'a', command: 'c', actionItem: { pressed: true, label: 'b' } }}
|
||||
action={{
|
||||
id: 'a',
|
||||
command: 'c',
|
||||
actionItem: { pressed: true, label: 'b' },
|
||||
telemetryProps: { feature: 'a' },
|
||||
}}
|
||||
telemetryService={NOOP_TELEMETRY_SERVICE}
|
||||
telemetryRecorder={noOpTelemetryRecorder}
|
||||
variant="actionItem"
|
||||
location={history.location}
|
||||
extensionsController={NOOP_EXTENSIONS_CONTROLLER}
|
||||
@ -74,8 +107,14 @@ describe('ActionItem', () => {
|
||||
const component = render(
|
||||
<ActionItem
|
||||
active={true}
|
||||
action={{ id: 'a', command: 'c', actionItem: { pressed: false, label: 'b' } }}
|
||||
action={{
|
||||
id: 'a',
|
||||
command: 'c',
|
||||
actionItem: { pressed: false, label: 'b' },
|
||||
telemetryProps: { feature: 'a' },
|
||||
}}
|
||||
telemetryService={NOOP_TELEMETRY_SERVICE}
|
||||
telemetryRecorder={noOpTelemetryRecorder}
|
||||
variant="actionItem"
|
||||
location={history.location}
|
||||
extensionsController={NOOP_EXTENSIONS_CONTROLLER}
|
||||
@ -88,8 +127,17 @@ describe('ActionItem', () => {
|
||||
const component = render(
|
||||
<ActionItem
|
||||
active={true}
|
||||
action={{ id: 'c', command: 'c', title: 't', description: 'd', iconURL: 'u', category: 'g' }}
|
||||
action={{
|
||||
id: 'c',
|
||||
command: 'c',
|
||||
title: 't',
|
||||
description: 'd',
|
||||
iconURL: 'u',
|
||||
category: 'g',
|
||||
telemetryProps: { feature: 'a' },
|
||||
}}
|
||||
telemetryService={NOOP_TELEMETRY_SERVICE}
|
||||
telemetryRecorder={noOpTelemetryRecorder}
|
||||
variant="actionItem"
|
||||
title={<span>t2</span>}
|
||||
location={history.location}
|
||||
@ -105,8 +153,17 @@ describe('ActionItem', () => {
|
||||
const { container, asFragment } = render(
|
||||
<ActionItem
|
||||
active={true}
|
||||
action={{ id: 'c', command: 'c', title: 't', description: 'd', iconURL: 'u', category: 'g' }}
|
||||
action={{
|
||||
id: 'c',
|
||||
command: 'c',
|
||||
title: 't',
|
||||
description: 'd',
|
||||
iconURL: 'u',
|
||||
category: 'g',
|
||||
telemetryProps: { feature: 'a' },
|
||||
}}
|
||||
telemetryService={NOOP_TELEMETRY_SERVICE}
|
||||
telemetryRecorder={noOpTelemetryRecorder}
|
||||
variant="actionItem"
|
||||
disabledDuringExecution={true}
|
||||
location={history.location}
|
||||
@ -131,8 +188,17 @@ describe('ActionItem', () => {
|
||||
const { asFragment } = render(
|
||||
<ActionItem
|
||||
active={true}
|
||||
action={{ id: 'c', command: 'c', title: 't', description: 'd', iconURL: 'u', category: 'g' }}
|
||||
action={{
|
||||
id: 'c',
|
||||
command: 'c',
|
||||
title: 't',
|
||||
description: 'd',
|
||||
iconURL: 'u',
|
||||
category: 'g',
|
||||
telemetryProps: { feature: 'a' },
|
||||
}}
|
||||
telemetryService={NOOP_TELEMETRY_SERVICE}
|
||||
telemetryRecorder={noOpTelemetryRecorder}
|
||||
variant="actionItem"
|
||||
showLoadingSpinnerDuringExecution={true}
|
||||
location={history.location}
|
||||
@ -162,8 +228,17 @@ describe('ActionItem', () => {
|
||||
const { asFragment } = render(
|
||||
<ActionItem
|
||||
active={true}
|
||||
action={{ id: 'c', command: 'c', title: 't', description: 'd', iconURL: 'u', category: 'g' }}
|
||||
action={{
|
||||
id: 'c',
|
||||
command: 'c',
|
||||
title: 't',
|
||||
description: 'd',
|
||||
iconURL: 'u',
|
||||
category: 'g',
|
||||
telemetryProps: { feature: 'a' },
|
||||
}}
|
||||
telemetryService={NOOP_TELEMETRY_SERVICE}
|
||||
telemetryRecorder={noOpTelemetryRecorder}
|
||||
variant="actionItem"
|
||||
disabledDuringExecution={true}
|
||||
location={history.location}
|
||||
@ -195,8 +270,15 @@ describe('ActionItem', () => {
|
||||
const { asFragment } = renderWithBrandedContext(
|
||||
<ActionItem
|
||||
active={true}
|
||||
action={{ id: 'c', command: 'open', commandArguments: ['https://example.com/bar'], title: 't' }}
|
||||
action={{
|
||||
id: 'c',
|
||||
command: 'open',
|
||||
commandArguments: ['https://example.com/bar'],
|
||||
title: 't',
|
||||
telemetryProps: { feature: 'a' },
|
||||
}}
|
||||
telemetryService={NOOP_TELEMETRY_SERVICE}
|
||||
telemetryRecorder={noOpTelemetryRecorder}
|
||||
location={history.location}
|
||||
extensionsController={NOOP_EXTENSIONS_CONTROLLER}
|
||||
/>
|
||||
@ -210,8 +292,15 @@ describe('ActionItem', () => {
|
||||
const { asFragment } = renderWithBrandedContext(
|
||||
<ActionItem
|
||||
active={true}
|
||||
action={{ id: 'c', command: 'open', commandArguments: ['https://other.com/foo'], title: 't' }}
|
||||
action={{
|
||||
id: 'c',
|
||||
command: 'open',
|
||||
commandArguments: ['https://other.com/foo'],
|
||||
title: 't',
|
||||
telemetryProps: { feature: 'a' },
|
||||
}}
|
||||
telemetryService={NOOP_TELEMETRY_SERVICE}
|
||||
telemetryRecorder={noOpTelemetryRecorder}
|
||||
location={history.location}
|
||||
extensionsController={NOOP_EXTENSIONS_CONTROLLER}
|
||||
/>
|
||||
@ -225,9 +314,16 @@ describe('ActionItem', () => {
|
||||
const { asFragment } = renderWithBrandedContext(
|
||||
<ActionItem
|
||||
active={true}
|
||||
action={{ id: 'c1', command: 'whatever', title: 'primary' }}
|
||||
altAction={{ id: 'c2', command: 'open', commandArguments: ['https://other.com/foo'], title: 'alt' }}
|
||||
action={{ id: 'c1', command: 'whatever', title: 'primary', telemetryProps: { feature: 'a' } }}
|
||||
altAction={{
|
||||
id: 'c2',
|
||||
command: 'open',
|
||||
commandArguments: ['https://other.com/foo'],
|
||||
title: 'alt',
|
||||
telemetryProps: { feature: 'a' },
|
||||
}}
|
||||
telemetryService={NOOP_TELEMETRY_SERVICE}
|
||||
telemetryRecorder={noOpTelemetryRecorder}
|
||||
location={history.location}
|
||||
extensionsController={NOOP_EXTENSIONS_CONTROLLER}
|
||||
/>
|
||||
|
||||
@ -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<ActionItemProps, State, type
|
||||
|
||||
// Record action ID (but not args, which might leak sensitive data).
|
||||
this.props.telemetryService.log(action.id)
|
||||
if (action.telemetryProps) {
|
||||
this.props.telemetryRecorder.recordEvent(
|
||||
// 👷 HACK: We have no control over what gets sent over Comlink/
|
||||
// web workers, so we depend on action contribution implementations
|
||||
// to give type guidance to ensure that we don't accidentally share
|
||||
// arbitrary, potentially sensitive string values. In this
|
||||
// RPC handler, when passing the provided event to the
|
||||
// TelemetryRecorder implementation, we forcibly cast all
|
||||
// the inputs below (feature) into known types
|
||||
// (the string 'feature') so that the recorder will accept
|
||||
// it. DO NOT do this elsewhere!
|
||||
action.telemetryProps.feature as 'feature',
|
||||
'executed',
|
||||
{
|
||||
privateMetadata: { action: action.id, ...action.telemetryProps.privateMetadata },
|
||||
}
|
||||
)
|
||||
} else {
|
||||
this.props.telemetryRecorder.recordEvent('blob.action', 'executed', {
|
||||
privateMetadata: { action: action.id },
|
||||
})
|
||||
}
|
||||
|
||||
const emitDidExecute = (): void => {
|
||||
if (this.props.onDidExecute) {
|
||||
|
||||
@ -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}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
@ -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<ActionItemProps, 'showLoadingSpinnerDuringExecution' | 'actionItemStyleProps'> {
|
||||
/**
|
||||
* If true, it renders a `<ul className="nav">...</ul>` around the items. If there are no items, it renders `null`.
|
||||
|
||||
@ -21,7 +21,13 @@ export async function createExtensionHostClientConnection(
|
||||
initData: Omit<InitData, 'initialSettings'>,
|
||||
platformContext: Pick<
|
||||
PlatformContext,
|
||||
'settings' | 'updateSettings' | 'getGraphQLClient' | 'requestGraphQL' | 'telemetryService' | 'clientApplication'
|
||||
| 'settings'
|
||||
| 'updateSettings'
|
||||
| 'getGraphQLClient'
|
||||
| 'requestGraphQL'
|
||||
| 'telemetryService'
|
||||
| 'telemetryRecorder'
|
||||
| 'clientApplication'
|
||||
>
|
||||
): Promise<{
|
||||
subscription: Unsubscribable
|
||||
|
||||
@ -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<object>[] = []
|
||||
@ -173,13 +198,19 @@ describe('MainThreadAPI', () => {
|
||||
const values = new Subject<SettingsCascade<{ a: string }>>()
|
||||
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<object>[] = []
|
||||
const { subscription } = initMainThreadAPI(
|
||||
|
||||
@ -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),
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -66,7 +66,7 @@ const DEPRECATED_EXTENSION_IDS = new Set(['sourcegraph/code-stats-insights', 'so
|
||||
|
||||
export function activateExtensions(
|
||||
state: Pick<ExtensionHostState, 'activeExtensions' | 'contributions' | 'haveInitialExtensionsLoaded' | 'settings'>,
|
||||
mainAPI: Remote<Pick<MainThreadAPI, 'logEvent'>>,
|
||||
mainAPI: Remote<Pick<MainThreadAPI, 'logEvent' | 'getTelemetryRecorder'>>,
|
||||
createExtensionAPI: (extensionID: string) => typeof sourcegraph,
|
||||
mainThreadAPIInitializations: Observable<boolean>,
|
||||
/**
|
||||
@ -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}:`,
|
||||
|
||||
@ -13,8 +13,8 @@ import {
|
||||
describe('mergeContributions()', () => {
|
||||
const FIXTURE_CONTRIBUTIONS_1: Evaluated<Contributions> = {
|
||||
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<Contributions> = {
|
||||
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<Contributions> = {
|
||||
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<Contributions> = {
|
||||
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)
|
||||
})
|
||||
|
||||
@ -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<Pick<MainThreadAPI, 'logEvent'>>({
|
||||
const mockMain = pretendRemote<Pick<MainThreadAPI, 'logEvent' | 'getTelemetryRecorder'>>({
|
||||
logEvent,
|
||||
getTelemetryRecorder: () => noOpTelemetryRecorder,
|
||||
})
|
||||
|
||||
const FIXTURE_EXTENSION: ExecutableExtension = {
|
||||
|
||||
@ -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<PlatformContext, 'requestGraphQL' | 'telemetryService'> {
|
||||
export interface CodeIntelContext
|
||||
extends Pick<PlatformContext, 'requestGraphQL' | 'telemetryService' | 'telemetryRecorder'> {
|
||||
settings: SettingsGetter
|
||||
}
|
||||
|
||||
@ -419,3 +421,7 @@ export function logTelemetryEvent(
|
||||
): void {
|
||||
context?.telemetryService?.log(eventName, eventProperties)
|
||||
}
|
||||
|
||||
export function getTelemetryRecorder(): TelemetryV2Props['telemetryRecorder'] | undefined {
|
||||
return context?.telemetryRecorder
|
||||
}
|
||||
|
||||
@ -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<T extends sourcegraph.Badged<sourcegraph.Location>,
|
||||
logger,
|
||||
}: {
|
||||
provider: string
|
||||
action: string
|
||||
action: CodeIntelActions
|
||||
repo: string
|
||||
textDocument: sourcegraph.TextDocument
|
||||
position: sourcegraph.Position
|
||||
@ -473,11 +473,11 @@ function logLocationResults<T extends sourcegraph.Badged<sourcegraph.Location>,
|
||||
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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -17,7 +17,10 @@ import type { PlatformContext } from '../platform/context'
|
||||
* documentation.
|
||||
*/
|
||||
export function registerBuiltinClientCommands(
|
||||
context: Pick<PlatformContext, 'requestGraphQL' | 'telemetryService' | 'settings' | 'updateSettings'>,
|
||||
context: Pick<
|
||||
PlatformContext,
|
||||
'requestGraphQL' | 'telemetryService' | 'telemetryRecorder' | 'settings' | 'updateSettings'
|
||||
>,
|
||||
extensionHost: Remote<FlatExtensionHostAPI>,
|
||||
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()
|
||||
},
|
||||
})
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -25,6 +25,7 @@ export function createController(
|
||||
| 'requestGraphQL'
|
||||
| 'getStaticExtensions'
|
||||
| 'telemetryService'
|
||||
| 'telemetryRecorder'
|
||||
| 'clientApplication'
|
||||
| 'sourcegraphURL'
|
||||
| 'createExtensionHost'
|
||||
|
||||
@ -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,
|
||||
},
|
||||
|
||||
@ -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<HoverOverlayProps>()({
|
||||
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(
|
||||
<HoverOverlay
|
||||
{...commonProps}
|
||||
actionsOrError={[{ action: { id: 'a', command: 'c', title: 'Some title' }, active: true }]}
|
||||
actionsOrError={[
|
||||
{
|
||||
action: { id: 'a', command: 'c', title: 'Some title', telemetryProps: { feature: 'test' } },
|
||||
active: true,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
).asFragment()
|
||||
).toMatchSnapshot()
|
||||
@ -100,7 +107,9 @@ describe('HoverOverlay', () => {
|
||||
render(
|
||||
<HoverOverlay
|
||||
{...commonProps}
|
||||
actionsOrError={[{ action: { id: 'a', command: 'c' }, active: true }]}
|
||||
actionsOrError={[
|
||||
{ action: { id: 'a', command: 'c', telemetryProps: { feature: 'a' } }, active: true },
|
||||
]}
|
||||
hoverOrError={{ contents: [{ kind: MarkupKind.Markdown, value: 'v' }] }}
|
||||
/>
|
||||
).asFragment()
|
||||
@ -112,7 +121,9 @@ describe('HoverOverlay', () => {
|
||||
render(
|
||||
<HoverOverlay
|
||||
{...commonProps}
|
||||
actionsOrError={[{ action: { id: 'a', command: 'c' }, active: true }]}
|
||||
actionsOrError={[
|
||||
{ action: { id: 'a', command: 'c', telemetryProps: { feature: 'a' } }, active: true },
|
||||
]}
|
||||
hoverOrError="loading"
|
||||
/>
|
||||
).asFragment()
|
||||
@ -172,7 +183,9 @@ describe('HoverOverlay', () => {
|
||||
render(
|
||||
<HoverOverlay
|
||||
{...commonProps}
|
||||
actionsOrError={[{ action: { id: 'a', command: 'c' }, active: true }]}
|
||||
actionsOrError={[
|
||||
{ action: { id: 'a', command: 'c', telemetryProps: { feature: 'a' } }, active: true },
|
||||
]}
|
||||
hoverOrError={{ message: 'm', name: 'c' }}
|
||||
/>
|
||||
).asFragment()
|
||||
|
||||
@ -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<HTMLDivElement>
|
||||
|
||||
@ -92,6 +94,7 @@ export const HoverOverlay: React.FunctionComponent<React.PropsWithChildren<Hover
|
||||
overlayPosition,
|
||||
actionsOrError,
|
||||
telemetryService,
|
||||
telemetryRecorder,
|
||||
extensionsController,
|
||||
pinOptions,
|
||||
location,
|
||||
@ -188,6 +191,7 @@ export const HoverOverlay: React.FunctionComponent<React.PropsWithChildren<Hover
|
||||
disabledDuringExecution={true}
|
||||
showLoadingSpinnerDuringExecution={true}
|
||||
telemetryService={telemetryService}
|
||||
telemetryRecorder={telemetryRecorder}
|
||||
extensionsController={extensionsController}
|
||||
location={location}
|
||||
actionItemStyleProps={actionItemStyleProps}
|
||||
|
||||
@ -379,6 +379,9 @@ export function registerHoverContributions({
|
||||
'${json(hoverPosition)}',
|
||||
/* eslint-enable no-template-curly-in-string */
|
||||
],
|
||||
telemetryProps: {
|
||||
feature: 'blob.goToDefinition',
|
||||
},
|
||||
},
|
||||
{
|
||||
// This action is used when preloading the definition succeeded and at least 1
|
||||
@ -389,6 +392,9 @@ export function registerHoverContributions({
|
||||
command: 'open',
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
commandArguments: ['${goToDefinition.url}'],
|
||||
telemetryProps: {
|
||||
feature: 'blob.goToDefinition.preloaded',
|
||||
},
|
||||
},
|
||||
],
|
||||
menus: {
|
||||
@ -479,6 +485,9 @@ export function registerHoverContributions({
|
||||
command: 'open',
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
commandArguments: ['${findReferences.url}'],
|
||||
telemetryProps: {
|
||||
feature: 'blob.findReferences',
|
||||
},
|
||||
},
|
||||
],
|
||||
menus: {
|
||||
@ -518,6 +527,10 @@ export function registerHoverContributions({
|
||||
],
|
||||
id: 'findImplementations_' + spec.languageID,
|
||||
title: 'Find implementations',
|
||||
telemetryProps: {
|
||||
feature: 'blob.findImplementations',
|
||||
privateMetadata: { languageID: spec.languageID },
|
||||
},
|
||||
})),
|
||||
],
|
||||
menus: {
|
||||
|
||||
@ -17,10 +17,13 @@ const isEmptyHover = ({
|
||||
|
||||
// Log telemetry event on mount and once per new hover position
|
||||
export function useLogTelemetryEvent(props: HoverOverlayProps): void {
|
||||
const { telemetryService, hoveredToken } = props
|
||||
const { telemetryService, telemetryRecorder, hoveredToken } = props
|
||||
|
||||
const previousPropsReference = useRef(props)
|
||||
const logTelemetryEvent = (): void => 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
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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,
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<BlobProps> = props => {
|
||||
)
|
||||
const codeIntelExtension = useCodeIntelExtension(
|
||||
telemetryService,
|
||||
telemetryRecorder,
|
||||
{
|
||||
repoName: blobInfo.repoName,
|
||||
filePath: blobInfo.filePath,
|
||||
@ -355,8 +356,7 @@ export const CodeMirrorBlob: React.FunctionComponent<BlobProps> = 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<BlobProps> = 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(() => {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user