Svelte: Add telemetry v2 to svelte client (#63041)

* Svelte: Add telemetry v2 to svelte client

* Run format

* Add telemetry to the bazel config
This commit is contained in:
Vova Kulikov 2024-06-04 11:12:32 +02:00 committed by GitHub
parent 79c912d6d9
commit 5915788ef0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 187 additions and 29 deletions

View File

@ -7,7 +7,7 @@ import { findFilters } from '@sourcegraph/shared/src/search/query/query'
import { scanSearchQuery, succeedScan } from '@sourcegraph/shared/src/search/query/scanner'
import type { Filter as QueryFilter } from '@sourcegraph/shared/src/search/query/token'
import { omitFilter } from '@sourcegraph/shared/src/search/query/transformer'
import { V2FilterTypes, type Filter } from '@sourcegraph/shared/src/search/stream'
import { TELEMETRY_V2_FILTER_TYPES, type Filter } from '@sourcegraph/shared/src/search/stream'
import type { TelemetryV2Props } from '@sourcegraph/shared/src/telemetry'
import type { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService'
import { Button, H1, H3, Icon, Tooltip } from '@sourcegraph/wildcard'
@ -72,7 +72,7 @@ export const NewSearchFilters: FC<NewSearchFiltersProps> = ({
(filter: URLQueryFilter, remove: boolean): void => {
telemetryService.log('SearchFiltersTypeClick', { filterType: filter.label }, { filterType: filter.label })
telemetryRecorder.recordEvent('search.filters.type', 'click', {
metadata: { filterKind: V2FilterTypes[filter.kind] },
metadata: { filterKind: TELEMETRY_V2_FILTER_TYPES[filter.kind] },
})
if (remove) {
setSelectedFilters(
@ -96,7 +96,7 @@ export const NewSearchFilters: FC<NewSearchFiltersProps> = ({
setSelectedFilters(filters)
telemetryService.log('SearchFiltersSelectFilter', { filterKind }, { filterKind })
telemetryRecorder.recordEvent('search.filters', 'select', {
metadata: { filterKind: V2FilterTypes[filterKind] },
metadata: { filterKind: TELEMETRY_V2_FILTER_TYPES[filterKind] },
})
},
[setSelectedFilters, telemetryService, telemetryRecorder]
@ -303,7 +303,7 @@ const SyntheticCountFilter: FC<SyntheticCountFilterProps> = props => {
const handleCountAllFilter = (filterKind: Filter['kind'], countFilters: URLQueryFilter[]): void => {
telemetryService.log('SearchFiltersSelectFilter', { filterKind }, { filterKind })
telemetryRecorder.recordEvent('search.filters', 'select', {
metadata: { filterKind: V2FilterTypes[filterKind] },
metadata: { filterKind: TELEMETRY_V2_FILTER_TYPES[filterKind] },
})
if (countFilters.length > 0) {

View File

@ -91,6 +91,19 @@ export interface SubmitSearchParameters
searchMode?: SearchMode
}
export const TELEMETRY_V2_SEARCH_SOURCE_TYPE: { [key in SubmitSearchParameters['source']]: number } = {
home: 1,
nav: 2,
repo: 3,
tree: 4,
filter: 5,
type: 6,
scopePage: 7,
communitySearchContextPage: 8,
excludedResults: 9,
smartSearchDisabled: 10,
}
export interface SubmitSearchProps {
submitSearch: (parameters: Partial<Omit<SubmitSearchParameters, 'query'>>) => void
}

View File

@ -284,7 +284,7 @@ export interface Filter {
kind: 'file' | 'repo' | 'lang' | 'utility' | 'author' | 'commit date' | 'symbol type' | 'type'
}
export const V2FilterTypes: { [key in Filter['kind']]: number } = {
export const TELEMETRY_V2_FILTER_TYPES: { [key in Filter['kind']]: number } = {
file: 1,
repo: 2,
lang: 3,

View File

@ -86,6 +86,7 @@ BUILD_DEPS = [
":node_modules/@sourcegraph/shared",
":node_modules/@sourcegraph/web",
":node_modules/@sourcegraph/wildcard",
":node_modules/@sourcegraph/telemetry",
":node_modules/@storybook/svelte",
":node_modules/@sveltejs/adapter-static",
":node_modules/@sveltejs/kit",

View File

@ -75,6 +75,7 @@
"@sourcegraph/common": "workspace:*",
"@sourcegraph/http-client": "workspace:*",
"@sourcegraph/shared": "workspace:*",
"@sourcegraph/telemetry": "^0.11.0",
"@sourcegraph/web": "workspace:*",
"@sourcegraph/wildcard": "workspace:*",
"@storybook/test": "^8.0.5",

View File

@ -42,6 +42,8 @@
import SymbolKindIcon from '$lib/search/SymbolKindIcon.svelte'
import { displayRepoName, scanSearchQuery, type Filter } from '$lib/shared'
import { SVELTE_LOGGER, SVELTE_TELEMETRY_EVENTS } from '$lib/telemetry'
import { TELEMETRY_V2_RECORDER } from '$lib/telemetry2'
import { TELEMETRY_V2_FILTER_TYPES } from '@sourcegraph/shared/src/search/stream'
import { delay } from '$lib/utils'
import { Alert } from '$lib/wildcard'
import Button from '$lib/wildcard/Button.svelte'
@ -90,6 +92,9 @@
function handleFilterSelect(kind: SectionItemData['kind']): void {
SVELTE_LOGGER.log(SVELTE_TELEMETRY_EVENTS.SelectSearchFilter, { kind }, { kind })
TELEMETRY_V2_RECORDER.recordEvent('search.filters', 'select', {
metadata: { filterKind: TELEMETRY_V2_FILTER_TYPES[kind] },
})
}
onMount(() => {

View File

@ -0,0 +1,109 @@
import { gql } from '@urql/core'
import type { BillingCategory, BillingProduct } from '@sourcegraph/shared/src/telemetry'
import { sessionTracker } from '@sourcegraph/shared/src/telemetry/web/sessionTracker'
import { userTracker } from '@sourcegraph/shared/src/telemetry/web/userTracker'
import {
type MarketingTrackingProvider,
type TelemetryEventMarketingTrackingInput,
type TelemetryEventInput,
type TelemetryExporter,
MarketingTrackingTelemetryProcessor,
TelemetryRecorderProvider as BaseTelemetryRecorderProvider,
} from '@sourcegraph/telemetry'
import type { GraphQLClient } from '$lib/graphql'
import { getGraphQLClient } from '$lib/graphql'
import type { ExportTelemetryEventsResult } from '$lib/graphql-operations'
/**
* TelemetryRecorderProvider is the default provider implementation for the
* Sourcegraph web app.
*/
export class TelemetryRecorderProvider extends BaseTelemetryRecorderProvider<BillingCategory, BillingProduct> {
constructor(
graphQlClient: GraphQLClient,
options: {
/**
* Enables buffering of events for export. Only enable if there is a
* reliable unsubscribe mechanism available.
*/
enableBuffering: boolean
}
) {
super(
{
client: getTelemetrySourceClient(),
clientVersion: window.context.version,
},
new GraphQlTelemetryExporter(graphQlClient),
[new MarketingTrackingTelemetryProcessor(new TrackingMetadataProvider())],
{
/**
* Use buffer time of 100ms - some existing buffering uses
* 1000ms, but we use a more conservative value.
*/
bufferTimeMs: options.enableBuffering ? 100 : 0,
bufferMaxSize: 10,
errorHandler: error => {
throw new Error(error)
},
}
)
}
}
function getTelemetrySourceClient(): string {
if (window.context?.sourcegraphDotComMode) {
return 'dotcom.svelte-web'
}
return 'server.svelte-web'
}
/**
* ApolloTelemetryExporter exports events via the new Sourcegraph telemetry
* framework: https://docs-legacy.sourcegraph.com/dev/background-information/telemetry
*/
export class GraphQlTelemetryExporter implements TelemetryExporter {
constructor(private client: GraphQLClient) {}
public async exportEvents(events: TelemetryEventInput[]): Promise<void> {
await this.client.mutation<ExportTelemetryEventsResult>(
gql`
mutation ExportTelemetryEvents($events: [TelemetryEventInput!]!) {
telemetry {
recordEvents(events: $events) {
alwaysNil
}
}
}
`,
{ events }
)
}
}
class TrackingMetadataProvider implements MarketingTrackingProvider {
private user = userTracker
private session = sessionTracker
public getMarketingTrackingMetadata(): TelemetryEventMarketingTrackingInput | null {
if (!window.context?.sourcegraphDotComMode) {
return null // don't report this data outside dotcom
}
return {
cohortID: this.user.cohortID,
deviceSessionID: this.user.deviceSessionID,
firstSourceURL: this.session.getFirstSourceURL(),
lastSourceURL: this.session.getLastSourceURL(),
referrer: this.session.getReferrer(),
sessionFirstURL: this.session.getSessionFirstURL(),
sessionReferrer: this.session.getSessionReferrer(),
url: window.location.href,
}
}
}
export const TELEMETRY_V2 = new TelemetryRecorderProvider(getGraphQLClient(), { enableBuffering: true })
export const TELEMETRY_V2_RECORDER = TELEMETRY_V2.getRecorder()

View File

@ -4,6 +4,7 @@
import { createPromiseStore } from '$lib/utils'
import { SVELTE_LOGGER, SVELTE_TELEMETRY_EVENTS } from '$lib/telemetry'
import { TELEMETRY_V2_RECORDER } from '$lib/telemetry2'
import Readme from '$lib/repo/Readme.svelte'
import type { PageData } from './$types'
@ -16,6 +17,7 @@
onMount(() => {
SVELTE_LOGGER.logViewEvent(SVELTE_TELEMETRY_EVENTS.ViewRepositoryPage)
TELEMETRY_V2_RECORDER.recordEvent('repo', 'view')
})
</script>

View File

@ -4,6 +4,7 @@
// @sg EnableRollout
import { onMount } from 'svelte'
import { TELEMETRY_V2_RECORDER } from '$lib/telemetry2'
import { SVELTE_LOGGER, SVELTE_TELEMETRY_EVENTS } from '$lib/telemetry'
import type { PageData, Snapshot } from './$types'
@ -29,6 +30,7 @@
onMount(() => {
SVELTE_LOGGER.logViewEvent(SVELTE_TELEMETRY_EVENTS.ViewBlobPage)
TELEMETRY_V2_RECORDER.recordEvent('blob', 'view')
})
</script>

View File

@ -24,6 +24,7 @@
import Permalink from '$lib/repo/Permalink.svelte'
import { createCodeIntelAPI } from '$lib/shared'
import { isLightTheme, settings } from '$lib/stores'
import { TELEMETRY_V2_RECORDER } from '$lib/telemetry2'
import { codeCopiedEvent, SVELTE_LOGGER, SVELTE_TELEMETRY_EVENTS } from '$lib/telemetry'
import { createPromiseStore, formatBytes } from '$lib/utils'
import { Alert, Badge, MenuButton, MenuLink } from '$lib/wildcard'
@ -126,6 +127,7 @@
// TODO: track other blob mode
if (event.detail === CodeViewMode.Blame) {
SVELTE_LOGGER.log(SVELTE_TELEMETRY_EVENTS.GitBlameEnabled)
TELEMETRY_V2_RECORDER.recordEvent('repo.gitBlame', 'enable')
}
goto(viewModeURL(event.detail), { replaceState: true, keepFocus: true })

View File

@ -1,7 +1,9 @@
<script lang="ts">
import { SVELTE_LOGGER, SVELTE_TELEMETRY_EVENTS } from '$lib/telemetry'
import Tooltip from '$lib/Tooltip.svelte'
import { TELEMETRY_V2_RECORDER } from '$lib/telemetry2'
import { getHumanNameForCodeHost } from '$lib/repo/shared/codehost'
import Tooltip from '$lib/Tooltip.svelte'
import CodeHostIcon from '$lib/search/CodeHostIcon.svelte'
import type { OpenInCodeHostAction } from './OpenInCodeHostAction.gql'
@ -9,6 +11,7 @@
function handleOpenCodeHostClick(): void {
SVELTE_LOGGER.log(SVELTE_TELEMETRY_EVENTS.GoToCodeHost)
TELEMETRY_V2_RECORDER.recordEvent('repo.goToCodeHost', 'click')
}
</script>

View File

@ -3,13 +3,16 @@
import { mdiMagnify } from '@mdi/js'
import { createDialog } from '@melt-ui/svelte'
import Icon from '$lib/Icon.svelte'
import SearchInput from '$lib/search/input/SearchInput.svelte'
import { QueryState, queryStateStore } from '$lib/search/state'
import { settings } from '$lib/stores'
import { registerHotkey } from '$lib/Hotkey'
import { repositoryInsertText } from '$lib/shared'
import { SVELTE_LOGGER, SVELTE_TELEMETRY_EVENTS } from '$lib/telemetry'
import { registerHotkey } from '$lib/Hotkey'
import { TELEMETRY_V2_RECORDER } from '$lib/telemetry2'
import { TELEMETRY_V2_SEARCH_SOURCE_TYPE } from '@sourcegraph/shared/src/search'
import { QueryState, queryStateStore } from '$lib/search/state'
import Icon from '$lib/Icon.svelte'
import SearchInput from '$lib/search/input/SearchInput.svelte'
export let repoName: string
/**
@ -35,6 +38,9 @@
{ source: 'repo', query: state.query },
{ source: 'repo', patternType: state.patternType }
)
TELEMETRY_V2_RECORDER.recordEvent('search', 'submit', {
metadata: { source: TELEMETRY_V2_SEARCH_SOURCE_TYPE['repo'] },
})
}
$: query = `repo:${repositoryInsertText({ repository: repoName })}${revision ? `@${revision}` : ''} `

View File

@ -2,6 +2,8 @@
import { setContext, onMount } from 'svelte'
import { SVELTE_LOGGER, SVELTE_TELEMETRY_EVENTS } from '$lib/telemetry'
import { TELEMETRY_V2_RECORDER } from '$lib/telemetry2'
import { TELEMETRY_V2_SEARCH_SOURCE_TYPE } from '@sourcegraph/shared/src/search'
import { logoLight, logoDark } from '$lib/images'
import SearchInput from '$lib/search/input/SearchInput.svelte'
import type { QueryStateStore, QueryState } from '$lib/search/state'
@ -20,6 +22,7 @@
onMount(() => {
SVELTE_LOGGER.logViewEvent(SVELTE_TELEMETRY_EVENTS.ViewHomePage)
TELEMETRY_V2_RECORDER.recordEvent('home', 'view')
})
function handleSubmit(state: QueryState) {
@ -28,6 +31,9 @@
{ source: 'home', query: state.query },
{ source: 'home', patternType: state.patternType }
)
TELEMETRY_V2_RECORDER.recordEvent('search', 'submit', {
metadata: { source: TELEMETRY_V2_SEARCH_SOURCE_TYPE['home'] },
})
}
</script>

View File

@ -38,6 +38,8 @@
type ContentMatch,
} from '$lib/shared'
import { SVELTE_LOGGER, SVELTE_TELEMETRY_EVENTS, codeCopiedEvent } from '$lib/telemetry'
import { TELEMETRY_V2_RECORDER } from '$lib/telemetry2'
import { TELEMETRY_V2_SEARCH_SOURCE_TYPE } from '@sourcegraph/shared/src/search'
import Panel from '$lib/wildcard/resizable-panel/Panel.svelte'
import PanelGroup from '$lib/wildcard/resizable-panel/PanelGroup.svelte'
import PanelResizeHandle from '$lib/wildcard/resizable-panel/PanelResizeHandle.svelte'
@ -108,6 +110,7 @@
onMount(() => {
SVELTE_LOGGER.logViewEvent(SVELTE_TELEMETRY_EVENTS.ViewSearchResultsPage)
TELEMETRY_V2_RECORDER.recordEvent('search.results', 'view')
})
function loadMore(event: { detail: boolean }) {
@ -133,8 +136,14 @@
SVELTE_LOGGER.log(...codeCopiedEvent('search-result'))
}
function handleSearchResultClick(): void {
function handleSearchResultClick(index: number): void {
SVELTE_LOGGER.log(SVELTE_TELEMETRY_EVENTS.SearchResultClick)
TELEMETRY_V2_RECORDER.recordEvent('search.result.area', 'click', {
metadata: {
index,
resultsLength: results.length,
},
})
}
function handleSubmit(state: QueryState) {
@ -143,6 +152,9 @@
{ source: 'nav', query: state.query },
{ source: 'nav', patternType: state.patternType }
)
TELEMETRY_V2_RECORDER.recordEvent('search', 'submit', {
metadata: { source: TELEMETRY_V2_SEARCH_SOURCE_TYPE['nav'] },
})
}
</script>
@ -181,15 +193,21 @@
2. A11y: Non-interactive element <ol> should not be assigned mouse
or keyboard event listeners.
-->
<ol on:click={handleSearchResultClick} on:copy={handleResultCopy}>
<ol on:copy={handleResultCopy}>
{#each resultsToShow as result, i}
{@const component = getSearchResultComponent(result)}
{#if i === resultsToShow.length - 1}
<li use:observeIntersection on:intersecting={loadMore}>
<li
use:observeIntersection
on:intersecting={loadMore}
on:click={() => handleSearchResultClick(i)}
>
<svelte:component this={component} {result} />
</li>
{:else}
<li><svelte:component this={component} {result} /></li>
<li on:click={() => handleSearchResultClick(i)}>
<svelte:component this={component} {result} />
</li>
{/if}
{/each}
</ol>

View File

@ -1,6 +1,6 @@
import { FILTERS_URL_KEY } from '@sourcegraph/branded/src/search-ui/results/filters/hooks'
import { compatNavigate } from '@sourcegraph/common'
import type { SubmitSearchParameters } from '@sourcegraph/shared/src/search'
import { type SubmitSearchParameters, TELEMETRY_V2_SEARCH_SOURCE_TYPE } from '@sourcegraph/shared/src/search'
import { appendContextFilter } from '@sourcegraph/shared/src/search/query/transformer'
import type { TelemetryV2Props } from '@sourcegraph/shared/src/telemetry'
import { EVENT_LOGGER } from '@sourcegraph/shared/src/telemetry/web/eventLogger'
@ -8,19 +8,6 @@ import { buildSearchURLQuery } from '@sourcegraph/shared/src/util/url'
import { AGGREGATION_MODE_URL_KEY, AGGREGATION_UI_MODE_URL_KEY } from './results/components/aggregation/constants'
const v2SearchSourceType: { [key in SubmitSearchParameters['source']]: number } = {
home: 1,
nav: 2,
repo: 3,
tree: 4,
filter: 5,
type: 6,
scopePage: 7,
communitySearchContextPage: 8,
excludedResults: 9,
smartSearchDisabled: 10,
}
/**
* By default {@link submitSearch} overrides all existing query parameters.
* This breaks all functionality that is built on top of URL query params and history
@ -85,7 +72,7 @@ export function submitSearch({
},
{ source, patternType }
)
telemetryRecorder.recordEvent('search', 'submit', { metadata: { source: v2SearchSourceType[source] } })
telemetryRecorder.recordEvent('search', 'submit', { metadata: { source: TELEMETRY_V2_SEARCH_SOURCE_TYPE[source] } })
const state = {
...(typeof location.state === 'object' ? location.state : null),

View File

@ -1548,6 +1548,9 @@ importers:
'@sourcegraph/shared':
specifier: workspace:*
version: link:../shared
'@sourcegraph/telemetry':
specifier: ^0.11.0
version: 0.11.0
'@sourcegraph/web':
specifier: workspace:*
version: link:../web