Skip to content

Commit 611f6e3

Browse files
Merge pull request #1953 from github/robertbrignull/telemetry_ui
Report telemetry on actions taken in the UI
2 parents a1f5e5b + 3777eb3 commit 611f6e3

13 files changed

Lines changed: 160 additions & 16 deletions

File tree

extensions/ql-vscode/src/pure/interface-types.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,8 @@ export type FromRemoteQueriesMessage =
413413
| RemoteQueryDownloadAnalysisResultsMessage
414414
| RemoteQueryDownloadAllAnalysesResultsMessage
415415
| RemoteQueryExportResultsMessage
416-
| CopyRepoListMessage;
416+
| CopyRepoListMessage
417+
| TelemetryMessage;
417418

418419
export type ToRemoteQueriesMessage =
419420
| SetRemoteQueryResultMessage
@@ -504,6 +505,11 @@ export interface CancelVariantAnalysisMessage {
504505
t: "cancelVariantAnalysis";
505506
}
506507

508+
export interface TelemetryMessage {
509+
t: "telemetry";
510+
action: string;
511+
}
512+
507513
export type ToVariantAnalysisMessage =
508514
| SetVariantAnalysisMessage
509515
| SetRepoResultsMessage
@@ -517,4 +523,5 @@ export type FromVariantAnalysisMessage =
517523
| CopyRepositoryListMessage
518524
| ExportResultsMessage
519525
| OpenLogsMessage
520-
| CancelVariantAnalysisMessage;
526+
| CancelVariantAnalysisMessage
527+
| TelemetryMessage;

extensions/ql-vscode/src/remote-queries/remote-queries-view.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import { AnalysesResultsManager } from "./analyses-results-manager";
3333
import { AnalysisResults } from "./shared/analysis-result";
3434
import { humanizeUnit } from "../pure/time";
3535
import { AbstractWebview, WebviewPanelConfig } from "../abstract-webview";
36+
import { telemetryListener } from "../telemetry";
3637

3738
export class RemoteQueriesView extends AbstractWebview<
3839
ToRemoteQueriesMessage,
@@ -167,6 +168,9 @@ export class RemoteQueriesView extends AbstractWebview<
167168
msg.queryId,
168169
);
169170
break;
171+
case "telemetry":
172+
telemetryListener?.sendUIInteraction(msg.action);
173+
break;
170174
default:
171175
assertNever(msg);
172176
}

extensions/ql-vscode/src/remote-queries/variant-analysis-view.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
VariantAnalysisViewManager,
1717
} from "./variant-analysis-view-manager";
1818
import { showAndLogWarningMessage } from "../helpers";
19+
import { telemetryListener } from "../telemetry";
1920

2021
export class VariantAnalysisView
2122
extends AbstractWebview<ToVariantAnalysisMessage, FromVariantAnalysisMessage>
@@ -151,6 +152,9 @@ export class VariantAnalysisView
151152
this.variantAnalysisId,
152153
);
153154
break;
155+
case "telemetry":
156+
telemetryListener?.sendUIInteraction(msg.action);
157+
break;
154158
default:
155159
assertNever(msg);
156160
}

extensions/ql-vscode/src/telemetry.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
GLOBAL_ENABLE_TELEMETRY,
1313
LOG_TELEMETRY,
1414
isIntegrationTestMode,
15+
isCanary,
1516
} from "./config";
1617
import * as appInsights from "applicationinsights";
1718
import { extLogger } from "./common";
@@ -155,19 +156,32 @@ export class TelemetryListener extends ConfigListener {
155156
? CommandCompletion.Cancelled
156157
: CommandCompletion.Failed;
157158

158-
const isCanary = (!!CANARY_FEATURES.getValue<boolean>()).toString();
159-
160159
this.reporter.sendTelemetryEvent(
161160
"command-usage",
162161
{
163162
name,
164163
status,
165-
isCanary,
164+
isCanary: isCanary().toString(),
166165
},
167166
{ executionTime },
168167
);
169168
}
170169

170+
sendUIInteraction(name: string) {
171+
if (!this.reporter) {
172+
return;
173+
}
174+
175+
this.reporter.sendTelemetryEvent(
176+
"ui-interaction",
177+
{
178+
name,
179+
isCanary: isCanary().toString(),
180+
},
181+
{},
182+
);
183+
}
184+
171185
/**
172186
* Displays a popup asking the user if they want to enable telemetry
173187
* for this extension.

extensions/ql-vscode/src/view/common/CodePaths/CodePaths.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
ResultSeverity,
1212
} from "../../../remote-queries/shared/analysis-result";
1313
import { CodePathsOverlay } from "./CodePathsOverlay";
14+
import { useTelemetryOnChange } from "../telemetry";
1415

1516
const ShowPathsLink = styled(VSCodeLink)`
1617
cursor: pointer;
@@ -23,13 +24,18 @@ export type CodePathsProps = {
2324
severity: ResultSeverity;
2425
};
2526

27+
const filterIsOpenTelemetry = (v: boolean) => v;
28+
2629
export const CodePaths = ({
2730
codeFlows,
2831
ruleDescription,
2932
message,
3033
severity,
3134
}: CodePathsProps) => {
3235
const [isOpen, setIsOpen] = useState(false);
36+
useTelemetryOnChange(isOpen, "code-path-is-open", {
37+
filterTelemetryOnValue: filterIsOpenTelemetry,
38+
});
3339

3440
const linkRef = useRef<HTMLAnchorElement>(null);
3541

extensions/ql-vscode/src/view/common/CodePaths/CodePathsOverlay.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
CodeFlow,
88
ResultSeverity,
99
} from "../../../remote-queries/shared/analysis-result";
10+
import { useTelemetryOnChange } from "../telemetry";
1011
import { SectionTitle } from "../SectionTitle";
1112
import { VerticalSpace } from "../VerticalSpace";
1213
import { CodeFlowsDropdown } from "./CodeFlowsDropdown";
@@ -77,6 +78,7 @@ export const CodePathsOverlay = ({
7778
onClose,
7879
}: CodePathsOverlayProps) => {
7980
const [selectedCodeFlow, setSelectedCodeFlow] = useState(codeFlows[0]);
81+
useTelemetryOnChange(selectedCodeFlow, "code-flow-selected");
8082

8183
return (
8284
<OverlayContainer>

extensions/ql-vscode/src/view/common/FileCodeSnippet/CodeSnippetMessage.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
} from "../../../remote-queries/shared/analysis-result";
99
import { createRemoteFileRef } from "../../../pure/location-link-utils";
1010
import { VerticalSpace } from "../VerticalSpace";
11+
import { sendTelemetry } from "../telemetry";
1112

1213
const getSeverityColor = (severity: ResultSeverity) => {
1314
switch (severity) {
@@ -49,6 +50,8 @@ type CodeSnippetMessageProps = {
4950
children: React.ReactNode;
5051
};
5152

53+
const sendAlertMessageLinkTelemetry = () => sendTelemetry("alert-message-link");
54+
5255
export const CodeSnippetMessage = ({
5356
message,
5457
severity,
@@ -65,6 +68,7 @@ export const CodeSnippetMessage = ({
6568
return (
6669
<LocationLink
6770
key={index}
71+
onClick={sendAlertMessageLinkTelemetry}
6872
href={createRemoteFileRef(
6973
token.location.fileLink,
7074
token.location.highlightedRegion?.startLine,

extensions/ql-vscode/src/view/common/FileCodeSnippet/FileCodeSnippet.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
import { createRemoteFileRef } from "../../../pure/location-link-utils";
1313
import { CodeSnippetMessage } from "./CodeSnippetMessage";
1414
import { CodeSnippetLine } from "./CodeSnippetLine";
15+
import { sendTelemetry } from "../telemetry";
1516

1617
const borderColor = "var(--vscode-editor-snippetFinalTabstopHighlightBorder)";
1718

@@ -46,6 +47,9 @@ type Props = {
4647
messageChildren?: React.ReactNode;
4748
};
4849

50+
const sendCodeSnippetTitleLinkTelemetry = () =>
51+
sendTelemetry("file-code-snippet-title-link");
52+
4953
export const FileCodeSnippet = ({
5054
fileLink,
5155
codeSnippet,
@@ -67,7 +71,12 @@ export const FileCodeSnippet = ({
6771
return (
6872
<Container>
6973
<TitleContainer>
70-
<VSCodeLink href={titleFileUri}>{fileLink.filePath}</VSCodeLink>
74+
<VSCodeLink
75+
onClick={sendCodeSnippetTitleLinkTelemetry}
76+
href={titleFileUri}
77+
>
78+
{fileLink.filePath}
79+
</VSCodeLink>
7180
</TitleContainer>
7281
{message && severity && (
7382
<CodeSnippetMessage message={message} severity={severity}>
@@ -83,7 +92,12 @@ export const FileCodeSnippet = ({
8392
return (
8493
<Container>
8594
<TitleContainer>
86-
<VSCodeLink href={titleFileUri}>{fileLink.filePath}</VSCodeLink>
95+
<VSCodeLink
96+
onClick={sendCodeSnippetTitleLinkTelemetry}
97+
href={titleFileUri}
98+
>
99+
{fileLink.filePath}
100+
</VSCodeLink>
87101
</TitleContainer>
88102
<CodeContainer>
89103
{code.map((line, index) => (
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { useEffect, useMemo, useRef } from "react";
2+
import { vscode } from "../vscode-api";
3+
4+
/**
5+
* A react effect that outputs telemetry events whenever the value changes.
6+
*
7+
* @param value Default value to pass to React.useState
8+
* @param telemetryAction Name of the telemetry event to output
9+
* @param options Extra optional arguments, including:
10+
* filterTelemetryOnValue: If provided, only output telemetry events when the
11+
* predicate returns true. If not provided always outputs telemetry.
12+
* debounceTimeout: If provided, will not output telemetry events for every change
13+
* but will wait until specified timeout happens with no new events ocurring.
14+
*/
15+
export function useTelemetryOnChange<S>(
16+
value: S,
17+
telemetryAction: string,
18+
{
19+
filterTelemetryOnValue,
20+
debounceTimeoutMillis,
21+
}: {
22+
filterTelemetryOnValue?: (value: S) => boolean;
23+
debounceTimeoutMillis?: number;
24+
} = {},
25+
) {
26+
const previousValue = useRef(value);
27+
28+
const sendTelemetryFunc = useMemo<() => void>(() => {
29+
if (debounceTimeoutMillis === undefined) {
30+
return () => sendTelemetry(telemetryAction);
31+
} else {
32+
let timer: NodeJS.Timeout;
33+
return () => {
34+
clearTimeout(timer);
35+
timer = setTimeout(() => {
36+
sendTelemetry(telemetryAction);
37+
}, debounceTimeoutMillis);
38+
};
39+
}
40+
}, [telemetryAction, debounceTimeoutMillis]);
41+
42+
useEffect(() => {
43+
if (value === previousValue.current) {
44+
return;
45+
}
46+
previousValue.current = value;
47+
48+
if (filterTelemetryOnValue && !filterTelemetryOnValue(value)) {
49+
return;
50+
}
51+
52+
sendTelemetryFunc();
53+
}, [sendTelemetryFunc, filterTelemetryOnValue, value, previousValue]);
54+
}
55+
56+
export function sendTelemetry(telemetryAction: string) {
57+
vscode.postMessage({
58+
t: "telemetry",
59+
action: telemetryAction,
60+
});
61+
}

extensions/ql-vscode/src/view/remote-queries/RawResultsTable.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
import { tryGetRemoteLocation } from "../../pure/bqrs-utils";
1111
import TextButton from "./TextButton";
1212
import { convertNonPrintableChars } from "../../text-utils";
13+
import { sendTelemetry, useTelemetryOnChange } from "../common/telemetry";
1314

1415
const numOfResultsInContractedMode = 5;
1516

@@ -45,6 +46,8 @@ type CellProps = {
4546
sourceLocationPrefix: string;
4647
};
4748

49+
const sendRawResultsLinkTelemetry = () => sendTelemetry("raw-results-link");
50+
4851
const Cell = ({ value, fileLinkPrefix, sourceLocationPrefix }: CellProps) => {
4952
switch (typeof value) {
5053
case "string":
@@ -59,7 +62,11 @@ const Cell = ({ value, fileLinkPrefix, sourceLocationPrefix }: CellProps) => {
5962
);
6063
const safeLabel = convertNonPrintableChars(value.label);
6164
if (url) {
62-
return <VSCodeLink href={url}>{safeLabel}</VSCodeLink>;
65+
return (
66+
<VSCodeLink onClick={sendRawResultsLinkTelemetry} href={url}>
67+
{safeLabel}
68+
</VSCodeLink>
69+
);
6370
} else {
6471
return <span>{safeLabel}</span>;
6572
}
@@ -94,13 +101,18 @@ type RawResultsTableProps = {
94101
sourceLocationPrefix: string;
95102
};
96103

104+
const filterTableExpandedTelemetry = (v: boolean) => v;
105+
97106
const RawResultsTable = ({
98107
schema,
99108
results,
100109
fileLinkPrefix,
101110
sourceLocationPrefix,
102111
}: RawResultsTableProps) => {
103112
const [tableExpanded, setTableExpanded] = useState(false);
113+
useTelemetryOnChange(tableExpanded, "raw-results-table-expanded", {
114+
filterTelemetryOnValue: filterTableExpandedTelemetry,
115+
});
104116
const numOfResultsToShow = tableExpanded
105117
? results.rows.length
106118
: numOfResultsInContractedMode;

0 commit comments

Comments
 (0)