From bdfb2f29da4cab7285c1700941bddf30ac5c087a Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 11 Jan 2023 12:02:18 +0000 Subject: [PATCH 01/14] Add method to loging telemetry of a UI interaction --- .../ql-vscode/src/pure/interface-types.ts | 8 +++++++- .../src/remote-queries/variant-analysis-view.ts | 4 ++++ extensions/ql-vscode/src/telemetry.ts | 17 +++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/extensions/ql-vscode/src/pure/interface-types.ts b/extensions/ql-vscode/src/pure/interface-types.ts index daa6b3eac26..b843e2a9367 100644 --- a/extensions/ql-vscode/src/pure/interface-types.ts +++ b/extensions/ql-vscode/src/pure/interface-types.ts @@ -504,6 +504,11 @@ export interface CancelVariantAnalysisMessage { t: "cancelVariantAnalysis"; } +export interface TelemetryMessage { + t: "telemetry"; + action: string; +} + export type ToVariantAnalysisMessage = | SetVariantAnalysisMessage | SetRepoResultsMessage @@ -517,4 +522,5 @@ export type FromVariantAnalysisMessage = | CopyRepositoryListMessage | ExportResultsMessage | OpenLogsMessage - | CancelVariantAnalysisMessage; + | CancelVariantAnalysisMessage + | TelemetryMessage; diff --git a/extensions/ql-vscode/src/remote-queries/variant-analysis-view.ts b/extensions/ql-vscode/src/remote-queries/variant-analysis-view.ts index c8d91e1f287..adc89286230 100644 --- a/extensions/ql-vscode/src/remote-queries/variant-analysis-view.ts +++ b/extensions/ql-vscode/src/remote-queries/variant-analysis-view.ts @@ -17,6 +17,7 @@ import { VariantAnalysisViewManager, } from "./variant-analysis-view-manager"; import { showAndLogWarningMessage } from "../helpers"; +import { telemetryListener } from "../telemetry"; export class VariantAnalysisView extends AbstractWebview @@ -149,6 +150,9 @@ export class VariantAnalysisView case "openLogs": await this.openLogs(); break; + case "telemetry": + telemetryListener?.sendUIInteraction(msg.action); + break; default: assertNever(msg); } diff --git a/extensions/ql-vscode/src/telemetry.ts b/extensions/ql-vscode/src/telemetry.ts index b145a3985dd..3cb1a00ed99 100644 --- a/extensions/ql-vscode/src/telemetry.ts +++ b/extensions/ql-vscode/src/telemetry.ts @@ -168,6 +168,23 @@ export class TelemetryListener extends ConfigListener { ); } + sendUIInteraction(name: string) { + if (!this.reporter) { + return; + } + + const isCanary = (!!CANARY_FEATURES.getValue()).toString(); + + this.reporter.sendTelemetryEvent( + "ui-interaction", + { + name, + isCanary, + }, + {}, + ); + } + /** * Displays a popup asking the user if they want to enable telemetry * for this extension. From f325eeb5ab3e67655d93ddc4c74b9bc3a4a857ff Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 11 Jan 2023 12:56:55 +0000 Subject: [PATCH 02/14] Output telemetry on react state changes in MRVA results view --- .../src/view/common/CodePaths/CodePaths.tsx | 9 +++- .../common/CodePaths/CodePathsOverlay.tsx | 7 ++- .../ql-vscode/src/view/common/Telemetry.ts | 52 +++++++++++++++++++ .../view/remote-queries/RawResultsTable.tsx | 8 ++- .../src/view/remote-queries/RemoteQueries.tsx | 2 +- .../src/view/variant-analysis/RepoRow.tsx | 11 +++- .../view/variant-analysis/VariantAnalysis.tsx | 14 +++-- 7 files changed, 90 insertions(+), 13 deletions(-) create mode 100644 extensions/ql-vscode/src/view/common/Telemetry.ts diff --git a/extensions/ql-vscode/src/view/common/CodePaths/CodePaths.tsx b/extensions/ql-vscode/src/view/common/CodePaths/CodePaths.tsx index 6b61f50ee8c..3408339a208 100644 --- a/extensions/ql-vscode/src/view/common/CodePaths/CodePaths.tsx +++ b/extensions/ql-vscode/src/view/common/CodePaths/CodePaths.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import { useRef, useState } from "react"; +import { useRef } from "react"; import styled from "styled-components"; import { VSCodeLink } from "@vscode/webview-ui-toolkit/react"; @@ -11,6 +11,7 @@ import { ResultSeverity, } from "../../../remote-queries/shared/analysis-result"; import { CodePathsOverlay } from "./CodePathsOverlay"; +import { useStateWithTelemetry } from "../Telemetry"; const ShowPathsLink = styled(VSCodeLink)` cursor: pointer; @@ -29,7 +30,11 @@ export const CodePaths = ({ message, severity, }: CodePathsProps) => { - const [isOpen, setIsOpen] = useState(false); + const [isOpen, setIsOpen] = useStateWithTelemetry( + false, + "code-path-is-open", + (v) => v === true, + ); const linkRef = useRef(null); diff --git a/extensions/ql-vscode/src/view/common/CodePaths/CodePathsOverlay.tsx b/extensions/ql-vscode/src/view/common/CodePaths/CodePathsOverlay.tsx index a291574fdbd..9a7cc402c8b 100644 --- a/extensions/ql-vscode/src/view/common/CodePaths/CodePathsOverlay.tsx +++ b/extensions/ql-vscode/src/view/common/CodePaths/CodePathsOverlay.tsx @@ -1,5 +1,4 @@ import * as React from "react"; -import { useState } from "react"; import styled from "styled-components"; import { @@ -7,6 +6,7 @@ import { CodeFlow, ResultSeverity, } from "../../../remote-queries/shared/analysis-result"; +import { useStateWithTelemetry } from "../Telemetry"; import { SectionTitle } from "../SectionTitle"; import { VerticalSpace } from "../VerticalSpace"; import { CodeFlowsDropdown } from "./CodeFlowsDropdown"; @@ -76,7 +76,10 @@ export const CodePathsOverlay = ({ severity, onClose, }: CodePathsOverlayProps) => { - const [selectedCodeFlow, setSelectedCodeFlow] = useState(codeFlows[0]); + const [selectedCodeFlow, setSelectedCodeFlow] = useStateWithTelemetry( + codeFlows[0], + "code-flow-selected", + ); return ( diff --git a/extensions/ql-vscode/src/view/common/Telemetry.ts b/extensions/ql-vscode/src/view/common/Telemetry.ts new file mode 100644 index 00000000000..68da92cfeb3 --- /dev/null +++ b/extensions/ql-vscode/src/view/common/Telemetry.ts @@ -0,0 +1,52 @@ +import * as React from "react"; +import { useState } from "react"; +import { vscode } from "../vscode-api"; + +/** + * Wraps `React.useState` to output telemetry events whenever the value changes. + * + * The only catch is that when using a predicate to filter which values output telemetry, + * the setter only accepts a raw value, instead of a `(prevState: S) => S` function. + * + * @param defaultValue Default value to pass to React.useState + * @param telemetryAction Name of the telemetry event to output + * @param filterTelemetryOnValue If provided, only output telemetry events when the predicate returns true. If not provided always outputs telemetry. + * @returns A value and a setter function, just as if from `React.useState` + */ +export function useStateWithTelemetry( + defaultValue: S | (() => S), + telemetryAction: string, +): [S, React.Dispatch>]; +export function useStateWithTelemetry( + defaultValue: S | (() => S), + telemetryAction: string, + filterTelemetryOnValue: (value: S) => boolean, +): [S, React.Dispatch]; +export function useStateWithTelemetry( + defaultValue: S | (() => S), + telemetryAction: string, + filterTelemetryOnValue?: (value: S) => boolean, +): [S, React.Dispatch | React.Dispatch>] { + const [value, setter] = useState(defaultValue); + if (filterTelemetryOnValue === undefined) { + const setterWithTelemetry = (x: React.SetStateAction) => { + vscode.postMessage({ + t: "telemetry", + action: telemetryAction, + }); + setter(x); + }; + return [value, setterWithTelemetry]; + } else { + const setterWithTelemetry = (x: S) => { + if (filterTelemetryOnValue(x)) { + vscode.postMessage({ + t: "telemetry", + action: telemetryAction, + }); + } + setter(x); + }; + return [value, setterWithTelemetry]; + } +} diff --git a/extensions/ql-vscode/src/view/remote-queries/RawResultsTable.tsx b/extensions/ql-vscode/src/view/remote-queries/RawResultsTable.tsx index 2d88a5c2314..d09be9489ad 100644 --- a/extensions/ql-vscode/src/view/remote-queries/RawResultsTable.tsx +++ b/extensions/ql-vscode/src/view/remote-queries/RawResultsTable.tsx @@ -1,5 +1,4 @@ import * as React from "react"; -import { useState } from "react"; import styled from "styled-components"; import { VSCodeLink } from "@vscode/webview-ui-toolkit/react"; import { @@ -10,6 +9,7 @@ import { import { tryGetRemoteLocation } from "../../pure/bqrs-utils"; import TextButton from "./TextButton"; import { convertNonPrintableChars } from "../../text-utils"; +import { useStateWithTelemetry } from "../common/Telemetry"; const numOfResultsInContractedMode = 5; @@ -100,7 +100,11 @@ const RawResultsTable = ({ fileLinkPrefix, sourceLocationPrefix, }: RawResultsTableProps) => { - const [tableExpanded, setTableExpanded] = useState(false); + const [tableExpanded, setTableExpanded] = useStateWithTelemetry( + false, + "raw-results-table-expanded", + (v) => v === true, + ); const numOfResultsToShow = tableExpanded ? results.rows.length : numOfResultsInContractedMode; diff --git a/extensions/ql-vscode/src/view/remote-queries/RemoteQueries.tsx b/extensions/ql-vscode/src/view/remote-queries/RemoteQueries.tsx index c1a0dc4a34b..69c8292a687 100644 --- a/extensions/ql-vscode/src/view/remote-queries/RemoteQueries.tsx +++ b/extensions/ql-vscode/src/view/remote-queries/RemoteQueries.tsx @@ -433,7 +433,7 @@ const AnalysesResults = ({ sort: Sort; }) => { const totalAnalysesResults = sumAnalysesResults(analysesResults); - const [filterValue, setFilterValue] = React.useState(""); + const [filterValue, setFilterValue] = useState(""); if (totalResults === 0) { return <>; diff --git a/extensions/ql-vscode/src/view/variant-analysis/RepoRow.tsx b/extensions/ql-vscode/src/view/variant-analysis/RepoRow.tsx index 91bb97fc4ef..d07eff0985a 100644 --- a/extensions/ql-vscode/src/view/variant-analysis/RepoRow.tsx +++ b/extensions/ql-vscode/src/view/variant-analysis/RepoRow.tsx @@ -24,6 +24,7 @@ import { vscode } from "../vscode-api"; import { AnalyzedRepoItemContent } from "./AnalyzedRepoItemContent"; import StarCount from "../common/StarCount"; import { LastUpdated } from "../common/LastUpdated"; +import { useStateWithTelemetry } from "../common/Telemetry"; // This will ensure that these icons have a className which we can use in the TitleContainer const ExpandCollapseCodicon = styled(Codicon)``; @@ -167,7 +168,11 @@ export const RepoRow = ({ selected, onSelectedChange, }: RepoRowProps) => { - const [isExpanded, setExpanded] = useState(false); + const [isExpanded, setExpanded] = useStateWithTelemetry( + false, + "variant-analysis-repo-row-expanded", + (v) => v === true, + ); const resultsLoaded = !!interpretedResults || !!rawResults; const [resultsLoading, setResultsLoading] = useState(false); @@ -182,7 +187,7 @@ export const RepoRow = ({ downloadStatus !== VariantAnalysisScannedRepositoryDownloadStatus.Succeeded ) { - setExpanded((oldIsExpanded) => !oldIsExpanded); + setExpanded(!isExpanded); return; } @@ -198,6 +203,8 @@ export const RepoRow = ({ repository.fullName, status, downloadStatus, + isExpanded, + setExpanded, ]); useEffect(() => { diff --git a/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysis.tsx b/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysis.tsx index 023f2d80a83..4500f4a3b29 100644 --- a/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysis.tsx +++ b/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysis.tsx @@ -16,6 +16,7 @@ import { defaultFilterSortState, RepositoriesFilterSortState, } from "../../pure/variant-analysis-filter-sort"; +import { useStateWithTelemetry } from "../common/Telemetry"; export type VariantAnalysisProps = { variantAnalysis?: VariantAnalysisDomainModel; @@ -60,11 +61,16 @@ export function VariantAnalysis({ const [repoResults, setRepoResults] = useState(initialRepoResults); - const [selectedRepositoryIds, setSelectedRepositoryIds] = useState( - [], - ); + const [selectedRepositoryIds, setSelectedRepositoryIds] = + useStateWithTelemetry( + [], + "variant-analysis-selected-repository-ids", + ); const [filterSortState, setFilterSortState] = - useState(defaultFilterSortState); + useStateWithTelemetry( + defaultFilterSortState, + "variant-analysis-filter-sort-state", + ); useEffect(() => { const listener = (evt: MessageEvent) => { From 34e98c4cc5a01d04be91e2f90a38a686f6dbf30e Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 11 Jan 2023 13:04:06 +0000 Subject: [PATCH 03/14] Convert useStateWithTelemetry to use useMemo --- .../src/view/common/CodePaths/CodePaths.tsx | 6 ++- .../ql-vscode/src/view/common/Telemetry.ts | 37 ++++++++++--------- .../view/remote-queries/RawResultsTable.tsx | 6 ++- .../src/view/variant-analysis/RepoRow.tsx | 8 ++-- 4 files changed, 33 insertions(+), 24 deletions(-) diff --git a/extensions/ql-vscode/src/view/common/CodePaths/CodePaths.tsx b/extensions/ql-vscode/src/view/common/CodePaths/CodePaths.tsx index 3408339a208..f01e9f21303 100644 --- a/extensions/ql-vscode/src/view/common/CodePaths/CodePaths.tsx +++ b/extensions/ql-vscode/src/view/common/CodePaths/CodePaths.tsx @@ -24,16 +24,18 @@ export type CodePathsProps = { severity: ResultSeverity; }; +const filterIsOpenTelemetry = (v: boolean) => v === true; + export const CodePaths = ({ codeFlows, ruleDescription, message, severity, }: CodePathsProps) => { - const [isOpen, setIsOpen] = useStateWithTelemetry( + const [isOpen, setIsOpen] = useStateWithTelemetry( false, "code-path-is-open", - (v) => v === true, + filterIsOpenTelemetry, ); const linkRef = useRef(null); diff --git a/extensions/ql-vscode/src/view/common/Telemetry.ts b/extensions/ql-vscode/src/view/common/Telemetry.ts index 68da92cfeb3..7a983c303c7 100644 --- a/extensions/ql-vscode/src/view/common/Telemetry.ts +++ b/extensions/ql-vscode/src/view/common/Telemetry.ts @@ -28,25 +28,28 @@ export function useStateWithTelemetry( filterTelemetryOnValue?: (value: S) => boolean, ): [S, React.Dispatch | React.Dispatch>] { const [value, setter] = useState(defaultValue); - if (filterTelemetryOnValue === undefined) { - const setterWithTelemetry = (x: React.SetStateAction) => { - vscode.postMessage({ - t: "telemetry", - action: telemetryAction, - }); - setter(x); - }; - return [value, setterWithTelemetry]; - } else { - const setterWithTelemetry = (x: S) => { - if (filterTelemetryOnValue(x)) { + const setterWithTelemetry = React.useMemo< + React.Dispatch | React.Dispatch> + >(() => { + if (filterTelemetryOnValue === undefined) { + return (x: React.SetStateAction) => { vscode.postMessage({ t: "telemetry", action: telemetryAction, }); - } - setter(x); - }; - return [value, setterWithTelemetry]; - } + setter(x); + }; + } else { + return (x: S) => { + if (filterTelemetryOnValue(x)) { + vscode.postMessage({ + t: "telemetry", + action: telemetryAction, + }); + } + setter(x); + }; + } + }, [telemetryAction, filterTelemetryOnValue, setter]); + return [value, setterWithTelemetry]; } diff --git a/extensions/ql-vscode/src/view/remote-queries/RawResultsTable.tsx b/extensions/ql-vscode/src/view/remote-queries/RawResultsTable.tsx index d09be9489ad..3038d4a4b71 100644 --- a/extensions/ql-vscode/src/view/remote-queries/RawResultsTable.tsx +++ b/extensions/ql-vscode/src/view/remote-queries/RawResultsTable.tsx @@ -94,16 +94,18 @@ type RawResultsTableProps = { sourceLocationPrefix: string; }; +const filterTableExpandedTelemetry = (v: boolean) => v === true; + const RawResultsTable = ({ schema, results, fileLinkPrefix, sourceLocationPrefix, }: RawResultsTableProps) => { - const [tableExpanded, setTableExpanded] = useStateWithTelemetry( + const [tableExpanded, setTableExpanded] = useStateWithTelemetry( false, "raw-results-table-expanded", - (v) => v === true, + filterTableExpandedTelemetry, ); const numOfResultsToShow = tableExpanded ? results.rows.length diff --git a/extensions/ql-vscode/src/view/variant-analysis/RepoRow.tsx b/extensions/ql-vscode/src/view/variant-analysis/RepoRow.tsx index d07eff0985a..69433a791ed 100644 --- a/extensions/ql-vscode/src/view/variant-analysis/RepoRow.tsx +++ b/extensions/ql-vscode/src/view/variant-analysis/RepoRow.tsx @@ -158,6 +158,8 @@ const isExpandableContentLoaded = ( return resultsLoaded; }; +const filterRepoRowExpandedTelemetry = (v: boolean) => v === true; + export const RepoRow = ({ repository, status, @@ -168,10 +170,10 @@ export const RepoRow = ({ selected, onSelectedChange, }: RepoRowProps) => { - const [isExpanded, setExpanded] = useStateWithTelemetry( + const [isExpanded, setExpanded] = useStateWithTelemetry( false, "variant-analysis-repo-row-expanded", - (v) => v === true, + filterRepoRowExpandedTelemetry, ); const resultsLoaded = !!interpretedResults || !!rawResults; const [resultsLoading, setResultsLoading] = useState(false); @@ -212,7 +214,7 @@ export const RepoRow = ({ setResultsLoading(false); setExpanded(true); } - }, [resultsLoaded, resultsLoading]); + }, [resultsLoaded, resultsLoading, setExpanded]); const onClickCheckbox = useCallback((e: React.MouseEvent) => { // Prevent calling the onClick event of the container, which would toggle the expanded state From f1a8564db4632b167b30d6c067bb06cee7e383f5 Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 11 Jan 2023 14:27:46 +0000 Subject: [PATCH 04/14] Output telemetry when clicking links --- .../FileCodeSnippet/CodeSnippetMessage.tsx | 2 ++ .../common/FileCodeSnippet/FileCodeSnippet.tsx | 15 +++++++++++++-- .../ql-vscode/src/view/common/Telemetry.ts | 17 +++++++++-------- .../src/view/remote-queries/RawResultsTable.tsx | 11 +++++++++-- 4 files changed, 33 insertions(+), 12 deletions(-) diff --git a/extensions/ql-vscode/src/view/common/FileCodeSnippet/CodeSnippetMessage.tsx b/extensions/ql-vscode/src/view/common/FileCodeSnippet/CodeSnippetMessage.tsx index 64e6e1340c6..a7b0ef50b0a 100644 --- a/extensions/ql-vscode/src/view/common/FileCodeSnippet/CodeSnippetMessage.tsx +++ b/extensions/ql-vscode/src/view/common/FileCodeSnippet/CodeSnippetMessage.tsx @@ -8,6 +8,7 @@ import { } from "../../../remote-queries/shared/analysis-result"; import { createRemoteFileRef } from "../../../pure/location-link-utils"; import { VerticalSpace } from "../VerticalSpace"; +import { sendTelemetry } from "../Telemetry"; const getSeverityColor = (severity: ResultSeverity) => { switch (severity) { @@ -65,6 +66,7 @@ export const CodeSnippetMessage = ({ return ( sendTelemetry("alert-message-link")} href={createRemoteFileRef( token.location.fileLink, token.location.highlightedRegion?.startLine, diff --git a/extensions/ql-vscode/src/view/common/FileCodeSnippet/FileCodeSnippet.tsx b/extensions/ql-vscode/src/view/common/FileCodeSnippet/FileCodeSnippet.tsx index 525402d0740..f2ce56f0978 100644 --- a/extensions/ql-vscode/src/view/common/FileCodeSnippet/FileCodeSnippet.tsx +++ b/extensions/ql-vscode/src/view/common/FileCodeSnippet/FileCodeSnippet.tsx @@ -12,6 +12,7 @@ import { import { createRemoteFileRef } from "../../../pure/location-link-utils"; import { CodeSnippetMessage } from "./CodeSnippetMessage"; import { CodeSnippetLine } from "./CodeSnippetLine"; +import { sendTelemetry } from "../Telemetry"; const borderColor = "var(--vscode-editor-snippetFinalTabstopHighlightBorder)"; @@ -67,7 +68,12 @@ export const FileCodeSnippet = ({ return ( - {fileLink.filePath} + sendTelemetry("file-code-snippet-title-link")} + href={titleFileUri} + > + {fileLink.filePath} + {message && severity && ( @@ -83,7 +89,12 @@ export const FileCodeSnippet = ({ return ( - {fileLink.filePath} + sendTelemetry("file-code-snippet-title-link")} + href={titleFileUri} + > + {fileLink.filePath} + {code.map((line, index) => ( diff --git a/extensions/ql-vscode/src/view/common/Telemetry.ts b/extensions/ql-vscode/src/view/common/Telemetry.ts index 7a983c303c7..07c327dd01d 100644 --- a/extensions/ql-vscode/src/view/common/Telemetry.ts +++ b/extensions/ql-vscode/src/view/common/Telemetry.ts @@ -33,19 +33,13 @@ export function useStateWithTelemetry( >(() => { if (filterTelemetryOnValue === undefined) { return (x: React.SetStateAction) => { - vscode.postMessage({ - t: "telemetry", - action: telemetryAction, - }); + sendTelemetry(telemetryAction); setter(x); }; } else { return (x: S) => { if (filterTelemetryOnValue(x)) { - vscode.postMessage({ - t: "telemetry", - action: telemetryAction, - }); + sendTelemetry(telemetryAction); } setter(x); }; @@ -53,3 +47,10 @@ export function useStateWithTelemetry( }, [telemetryAction, filterTelemetryOnValue, setter]); return [value, setterWithTelemetry]; } + +export function sendTelemetry(telemetryAction: string) { + vscode.postMessage({ + t: "telemetry", + action: telemetryAction, + }); +} diff --git a/extensions/ql-vscode/src/view/remote-queries/RawResultsTable.tsx b/extensions/ql-vscode/src/view/remote-queries/RawResultsTable.tsx index 3038d4a4b71..4fdcd466776 100644 --- a/extensions/ql-vscode/src/view/remote-queries/RawResultsTable.tsx +++ b/extensions/ql-vscode/src/view/remote-queries/RawResultsTable.tsx @@ -9,7 +9,7 @@ import { import { tryGetRemoteLocation } from "../../pure/bqrs-utils"; import TextButton from "./TextButton"; import { convertNonPrintableChars } from "../../text-utils"; -import { useStateWithTelemetry } from "../common/Telemetry"; +import { sendTelemetry, useStateWithTelemetry } from "../common/Telemetry"; const numOfResultsInContractedMode = 5; @@ -59,7 +59,14 @@ const Cell = ({ value, fileLinkPrefix, sourceLocationPrefix }: CellProps) => { ); const safeLabel = convertNonPrintableChars(value.label); if (url) { - return {safeLabel}; + return ( + sendTelemetry("raw-results-link")} + href={url} + > + {safeLabel} + + ); } else { return {safeLabel}; } From 0aa2cd6e90262430772c7aa8b5e9af4e052086c6 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 12 Jan 2023 10:58:01 +0000 Subject: [PATCH 05/14] Make Telemetry.ts lowercase --- extensions/ql-vscode/src/view/common/CodePaths/CodePaths.tsx | 2 +- .../ql-vscode/src/view/common/CodePaths/CodePathsOverlay.tsx | 2 +- .../src/view/common/FileCodeSnippet/CodeSnippetMessage.tsx | 2 +- .../src/view/common/FileCodeSnippet/FileCodeSnippet.tsx | 2 +- .../ql-vscode/src/view/common/{Telemetry.ts => telemetry.ts} | 0 .../ql-vscode/src/view/remote-queries/RawResultsTable.tsx | 2 +- extensions/ql-vscode/src/view/variant-analysis/RepoRow.tsx | 2 +- .../ql-vscode/src/view/variant-analysis/VariantAnalysis.tsx | 2 +- 8 files changed, 7 insertions(+), 7 deletions(-) rename extensions/ql-vscode/src/view/common/{Telemetry.ts => telemetry.ts} (100%) diff --git a/extensions/ql-vscode/src/view/common/CodePaths/CodePaths.tsx b/extensions/ql-vscode/src/view/common/CodePaths/CodePaths.tsx index f01e9f21303..af9bd42f252 100644 --- a/extensions/ql-vscode/src/view/common/CodePaths/CodePaths.tsx +++ b/extensions/ql-vscode/src/view/common/CodePaths/CodePaths.tsx @@ -11,7 +11,7 @@ import { ResultSeverity, } from "../../../remote-queries/shared/analysis-result"; import { CodePathsOverlay } from "./CodePathsOverlay"; -import { useStateWithTelemetry } from "../Telemetry"; +import { useStateWithTelemetry } from "../telemetry"; const ShowPathsLink = styled(VSCodeLink)` cursor: pointer; diff --git a/extensions/ql-vscode/src/view/common/CodePaths/CodePathsOverlay.tsx b/extensions/ql-vscode/src/view/common/CodePaths/CodePathsOverlay.tsx index 9a7cc402c8b..04d969fa255 100644 --- a/extensions/ql-vscode/src/view/common/CodePaths/CodePathsOverlay.tsx +++ b/extensions/ql-vscode/src/view/common/CodePaths/CodePathsOverlay.tsx @@ -6,7 +6,7 @@ import { CodeFlow, ResultSeverity, } from "../../../remote-queries/shared/analysis-result"; -import { useStateWithTelemetry } from "../Telemetry"; +import { useStateWithTelemetry } from "../telemetry"; import { SectionTitle } from "../SectionTitle"; import { VerticalSpace } from "../VerticalSpace"; import { CodeFlowsDropdown } from "./CodeFlowsDropdown"; diff --git a/extensions/ql-vscode/src/view/common/FileCodeSnippet/CodeSnippetMessage.tsx b/extensions/ql-vscode/src/view/common/FileCodeSnippet/CodeSnippetMessage.tsx index a7b0ef50b0a..f60c666cc4a 100644 --- a/extensions/ql-vscode/src/view/common/FileCodeSnippet/CodeSnippetMessage.tsx +++ b/extensions/ql-vscode/src/view/common/FileCodeSnippet/CodeSnippetMessage.tsx @@ -8,7 +8,7 @@ import { } from "../../../remote-queries/shared/analysis-result"; import { createRemoteFileRef } from "../../../pure/location-link-utils"; import { VerticalSpace } from "../VerticalSpace"; -import { sendTelemetry } from "../Telemetry"; +import { sendTelemetry } from "../telemetry"; const getSeverityColor = (severity: ResultSeverity) => { switch (severity) { diff --git a/extensions/ql-vscode/src/view/common/FileCodeSnippet/FileCodeSnippet.tsx b/extensions/ql-vscode/src/view/common/FileCodeSnippet/FileCodeSnippet.tsx index f2ce56f0978..801b6f93f2c 100644 --- a/extensions/ql-vscode/src/view/common/FileCodeSnippet/FileCodeSnippet.tsx +++ b/extensions/ql-vscode/src/view/common/FileCodeSnippet/FileCodeSnippet.tsx @@ -12,7 +12,7 @@ import { import { createRemoteFileRef } from "../../../pure/location-link-utils"; import { CodeSnippetMessage } from "./CodeSnippetMessage"; import { CodeSnippetLine } from "./CodeSnippetLine"; -import { sendTelemetry } from "../Telemetry"; +import { sendTelemetry } from "../telemetry"; const borderColor = "var(--vscode-editor-snippetFinalTabstopHighlightBorder)"; diff --git a/extensions/ql-vscode/src/view/common/Telemetry.ts b/extensions/ql-vscode/src/view/common/telemetry.ts similarity index 100% rename from extensions/ql-vscode/src/view/common/Telemetry.ts rename to extensions/ql-vscode/src/view/common/telemetry.ts diff --git a/extensions/ql-vscode/src/view/remote-queries/RawResultsTable.tsx b/extensions/ql-vscode/src/view/remote-queries/RawResultsTable.tsx index 4fdcd466776..838c27375a5 100644 --- a/extensions/ql-vscode/src/view/remote-queries/RawResultsTable.tsx +++ b/extensions/ql-vscode/src/view/remote-queries/RawResultsTable.tsx @@ -9,7 +9,7 @@ import { import { tryGetRemoteLocation } from "../../pure/bqrs-utils"; import TextButton from "./TextButton"; import { convertNonPrintableChars } from "../../text-utils"; -import { sendTelemetry, useStateWithTelemetry } from "../common/Telemetry"; +import { sendTelemetry, useStateWithTelemetry } from "../common/telemetry"; const numOfResultsInContractedMode = 5; diff --git a/extensions/ql-vscode/src/view/variant-analysis/RepoRow.tsx b/extensions/ql-vscode/src/view/variant-analysis/RepoRow.tsx index 69433a791ed..7062f6fcd16 100644 --- a/extensions/ql-vscode/src/view/variant-analysis/RepoRow.tsx +++ b/extensions/ql-vscode/src/view/variant-analysis/RepoRow.tsx @@ -24,7 +24,7 @@ import { vscode } from "../vscode-api"; import { AnalyzedRepoItemContent } from "./AnalyzedRepoItemContent"; import StarCount from "../common/StarCount"; import { LastUpdated } from "../common/LastUpdated"; -import { useStateWithTelemetry } from "../common/Telemetry"; +import { useStateWithTelemetry } from "../common/telemetry"; // This will ensure that these icons have a className which we can use in the TitleContainer const ExpandCollapseCodicon = styled(Codicon)``; diff --git a/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysis.tsx b/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysis.tsx index 4500f4a3b29..896900bd55c 100644 --- a/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysis.tsx +++ b/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysis.tsx @@ -16,7 +16,7 @@ import { defaultFilterSortState, RepositoriesFilterSortState, } from "../../pure/variant-analysis-filter-sort"; -import { useStateWithTelemetry } from "../common/Telemetry"; +import { useStateWithTelemetry } from "../common/telemetry"; export type VariantAnalysisProps = { variantAnalysis?: VariantAnalysisDomainModel; From 25a3ba76ed6b8423332cf103e9ef6ed5f35b4bdf Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 12 Jan 2023 11:01:10 +0000 Subject: [PATCH 06/14] Extract on-click handlers to consts --- .../src/view/common/FileCodeSnippet/CodeSnippetMessage.tsx | 4 +++- .../src/view/common/FileCodeSnippet/FileCodeSnippet.tsx | 7 +++++-- .../ql-vscode/src/view/remote-queries/RawResultsTable.tsx | 7 +++---- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/extensions/ql-vscode/src/view/common/FileCodeSnippet/CodeSnippetMessage.tsx b/extensions/ql-vscode/src/view/common/FileCodeSnippet/CodeSnippetMessage.tsx index f60c666cc4a..dade5e03653 100644 --- a/extensions/ql-vscode/src/view/common/FileCodeSnippet/CodeSnippetMessage.tsx +++ b/extensions/ql-vscode/src/view/common/FileCodeSnippet/CodeSnippetMessage.tsx @@ -50,6 +50,8 @@ type CodeSnippetMessageProps = { children: React.ReactNode; }; +const sendAlertMessageLinkTelemetry = () => sendTelemetry("alert-message-link"); + export const CodeSnippetMessage = ({ message, severity, @@ -66,7 +68,7 @@ export const CodeSnippetMessage = ({ return ( sendTelemetry("alert-message-link")} + onClick={sendAlertMessageLinkTelemetry} href={createRemoteFileRef( token.location.fileLink, token.location.highlightedRegion?.startLine, diff --git a/extensions/ql-vscode/src/view/common/FileCodeSnippet/FileCodeSnippet.tsx b/extensions/ql-vscode/src/view/common/FileCodeSnippet/FileCodeSnippet.tsx index 801b6f93f2c..444dea09395 100644 --- a/extensions/ql-vscode/src/view/common/FileCodeSnippet/FileCodeSnippet.tsx +++ b/extensions/ql-vscode/src/view/common/FileCodeSnippet/FileCodeSnippet.tsx @@ -47,6 +47,9 @@ type Props = { messageChildren?: React.ReactNode; }; +const sendCodeSnippetTitleLinkTelemetry = () => + sendTelemetry("file-code-snippet-title-link"); + export const FileCodeSnippet = ({ fileLink, codeSnippet, @@ -69,7 +72,7 @@ export const FileCodeSnippet = ({ sendTelemetry("file-code-snippet-title-link")} + onClick={sendCodeSnippetTitleLinkTelemetry} href={titleFileUri} > {fileLink.filePath} @@ -90,7 +93,7 @@ export const FileCodeSnippet = ({ sendTelemetry("file-code-snippet-title-link")} + onClick={sendCodeSnippetTitleLinkTelemetry} href={titleFileUri} > {fileLink.filePath} diff --git a/extensions/ql-vscode/src/view/remote-queries/RawResultsTable.tsx b/extensions/ql-vscode/src/view/remote-queries/RawResultsTable.tsx index 838c27375a5..9ef6ce1c552 100644 --- a/extensions/ql-vscode/src/view/remote-queries/RawResultsTable.tsx +++ b/extensions/ql-vscode/src/view/remote-queries/RawResultsTable.tsx @@ -45,6 +45,8 @@ type CellProps = { sourceLocationPrefix: string; }; +const sendRawResultsLinkTelemetry = () => sendTelemetry("raw-results-link"); + const Cell = ({ value, fileLinkPrefix, sourceLocationPrefix }: CellProps) => { switch (typeof value) { case "string": @@ -60,10 +62,7 @@ const Cell = ({ value, fileLinkPrefix, sourceLocationPrefix }: CellProps) => { const safeLabel = convertNonPrintableChars(value.label); if (url) { return ( - sendTelemetry("raw-results-link")} - href={url} - > + {safeLabel} ); From e9830ee8546ec96797e1d1287657ab58f2e01b86 Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 13 Jan 2023 11:21:10 +0000 Subject: [PATCH 07/14] Add telemetry message to remote queries view --- extensions/ql-vscode/src/pure/interface-types.ts | 3 ++- .../ql-vscode/src/remote-queries/remote-queries-view.ts | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/extensions/ql-vscode/src/pure/interface-types.ts b/extensions/ql-vscode/src/pure/interface-types.ts index b843e2a9367..dabf31369fb 100644 --- a/extensions/ql-vscode/src/pure/interface-types.ts +++ b/extensions/ql-vscode/src/pure/interface-types.ts @@ -413,7 +413,8 @@ export type FromRemoteQueriesMessage = | RemoteQueryDownloadAnalysisResultsMessage | RemoteQueryDownloadAllAnalysesResultsMessage | RemoteQueryExportResultsMessage - | CopyRepoListMessage; + | CopyRepoListMessage + | TelemetryMessage; export type ToRemoteQueriesMessage = | SetRemoteQueryResultMessage diff --git a/extensions/ql-vscode/src/remote-queries/remote-queries-view.ts b/extensions/ql-vscode/src/remote-queries/remote-queries-view.ts index 92142b45797..9a42622eacb 100644 --- a/extensions/ql-vscode/src/remote-queries/remote-queries-view.ts +++ b/extensions/ql-vscode/src/remote-queries/remote-queries-view.ts @@ -33,6 +33,7 @@ import { AnalysesResultsManager } from "./analyses-results-manager"; import { AnalysisResults } from "./shared/analysis-result"; import { humanizeUnit } from "../pure/time"; import { AbstractWebview, WebviewPanelConfig } from "../abstract-webview"; +import { telemetryListener } from "../telemetry"; export class RemoteQueriesView extends AbstractWebview< ToRemoteQueriesMessage, @@ -167,6 +168,9 @@ export class RemoteQueriesView extends AbstractWebview< msg.queryId, ); break; + case "telemetry": + telemetryListener?.sendUIInteraction(msg.action); + break; default: assertNever(msg); } From 98a29d2459a3437d307841aaa1e8a8dfe26cae42 Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 13 Jan 2023 11:59:23 +0000 Subject: [PATCH 08/14] Convert useStateWithTelemetry to useTelemetryOnChange --- .../src/view/common/CodePaths/CodePaths.tsx | 13 ++-- .../common/CodePaths/CodePathsOverlay.tsx | 9 ++- .../ql-vscode/src/view/common/telemetry.ts | 66 ++++++++----------- .../view/remote-queries/RawResultsTable.tsx | 12 ++-- .../src/view/variant-analysis/RepoRow.tsx | 14 ++-- .../view/variant-analysis/VariantAnalysis.tsx | 28 ++++---- 6 files changed, 61 insertions(+), 81 deletions(-) diff --git a/extensions/ql-vscode/src/view/common/CodePaths/CodePaths.tsx b/extensions/ql-vscode/src/view/common/CodePaths/CodePaths.tsx index af9bd42f252..ba1c2d03ef3 100644 --- a/extensions/ql-vscode/src/view/common/CodePaths/CodePaths.tsx +++ b/extensions/ql-vscode/src/view/common/CodePaths/CodePaths.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import { useRef } from "react"; +import { useRef, useState } from "react"; import styled from "styled-components"; import { VSCodeLink } from "@vscode/webview-ui-toolkit/react"; @@ -11,7 +11,7 @@ import { ResultSeverity, } from "../../../remote-queries/shared/analysis-result"; import { CodePathsOverlay } from "./CodePathsOverlay"; -import { useStateWithTelemetry } from "../telemetry"; +import { useTelemetryOnChange } from "../telemetry"; const ShowPathsLink = styled(VSCodeLink)` cursor: pointer; @@ -32,11 +32,10 @@ export const CodePaths = ({ message, severity, }: CodePathsProps) => { - const [isOpen, setIsOpen] = useStateWithTelemetry( - false, - "code-path-is-open", - filterIsOpenTelemetry, - ); + const [isOpen, setIsOpen] = useState(false); + useTelemetryOnChange(isOpen, "code-path-is-open", { + filterTelemetryOnValue: filterIsOpenTelemetry, + }); const linkRef = useRef(null); diff --git a/extensions/ql-vscode/src/view/common/CodePaths/CodePathsOverlay.tsx b/extensions/ql-vscode/src/view/common/CodePaths/CodePathsOverlay.tsx index 04d969fa255..9672e8fe171 100644 --- a/extensions/ql-vscode/src/view/common/CodePaths/CodePathsOverlay.tsx +++ b/extensions/ql-vscode/src/view/common/CodePaths/CodePathsOverlay.tsx @@ -1,4 +1,5 @@ import * as React from "react"; +import { useState } from "react"; import styled from "styled-components"; import { @@ -6,7 +7,7 @@ import { CodeFlow, ResultSeverity, } from "../../../remote-queries/shared/analysis-result"; -import { useStateWithTelemetry } from "../telemetry"; +import { useTelemetryOnChange } from "../telemetry"; import { SectionTitle } from "../SectionTitle"; import { VerticalSpace } from "../VerticalSpace"; import { CodeFlowsDropdown } from "./CodeFlowsDropdown"; @@ -76,10 +77,8 @@ export const CodePathsOverlay = ({ severity, onClose, }: CodePathsOverlayProps) => { - const [selectedCodeFlow, setSelectedCodeFlow] = useStateWithTelemetry( - codeFlows[0], - "code-flow-selected", - ); + const [selectedCodeFlow, setSelectedCodeFlow] = useState(codeFlows[0]); + useTelemetryOnChange(selectedCodeFlow, "code-flow-selected"); return ( diff --git a/extensions/ql-vscode/src/view/common/telemetry.ts b/extensions/ql-vscode/src/view/common/telemetry.ts index 07c327dd01d..b2fb12cd43c 100644 --- a/extensions/ql-vscode/src/view/common/telemetry.ts +++ b/extensions/ql-vscode/src/view/common/telemetry.ts @@ -1,51 +1,37 @@ -import * as React from "react"; -import { useState } from "react"; +import { useEffect, useRef } from "react"; import { vscode } from "../vscode-api"; /** - * Wraps `React.useState` to output telemetry events whenever the value changes. + * A react effect that outputs telemetry events whenever the value changes. * - * The only catch is that when using a predicate to filter which values output telemetry, - * the setter only accepts a raw value, instead of a `(prevState: S) => S` function. - * - * @param defaultValue Default value to pass to React.useState + * @param value Default value to pass to React.useState * @param telemetryAction Name of the telemetry event to output - * @param filterTelemetryOnValue If provided, only output telemetry events when the predicate returns true. If not provided always outputs telemetry. - * @returns A value and a setter function, just as if from `React.useState` + * @param options Extra optional arguments, including: + * filterTelemetryOnValue: If provided, only output telemetry events when the + * predicate returns true. If not provided always outputs telemetry. */ -export function useStateWithTelemetry( - defaultValue: S | (() => S), - telemetryAction: string, -): [S, React.Dispatch>]; -export function useStateWithTelemetry( - defaultValue: S | (() => S), +export function useTelemetryOnChange( + value: S, telemetryAction: string, - filterTelemetryOnValue: (value: S) => boolean, -): [S, React.Dispatch]; -export function useStateWithTelemetry( - defaultValue: S | (() => S), - telemetryAction: string, - filterTelemetryOnValue?: (value: S) => boolean, -): [S, React.Dispatch | React.Dispatch>] { - const [value, setter] = useState(defaultValue); - const setterWithTelemetry = React.useMemo< - React.Dispatch | React.Dispatch> - >(() => { - if (filterTelemetryOnValue === undefined) { - return (x: React.SetStateAction) => { - sendTelemetry(telemetryAction); - setter(x); - }; - } else { - return (x: S) => { - if (filterTelemetryOnValue(x)) { - sendTelemetry(telemetryAction); - } - setter(x); - }; + options?: { + filterTelemetryOnValue?: (value: S) => boolean; + }, +) { + const previousValue = useRef(value); + const filterTelemetryOnValue = options?.filterTelemetryOnValue; + + useEffect(() => { + if (value === previousValue.current) { + return; } - }, [telemetryAction, filterTelemetryOnValue, setter]); - return [value, setterWithTelemetry]; + previousValue.current = value; + + if (filterTelemetryOnValue && !filterTelemetryOnValue(value)) { + return; + } + + sendTelemetry(telemetryAction); + }, [telemetryAction, filterTelemetryOnValue, value, previousValue]); } export function sendTelemetry(telemetryAction: string) { diff --git a/extensions/ql-vscode/src/view/remote-queries/RawResultsTable.tsx b/extensions/ql-vscode/src/view/remote-queries/RawResultsTable.tsx index 9ef6ce1c552..266a5279c04 100644 --- a/extensions/ql-vscode/src/view/remote-queries/RawResultsTable.tsx +++ b/extensions/ql-vscode/src/view/remote-queries/RawResultsTable.tsx @@ -1,4 +1,5 @@ import * as React from "react"; +import { useState } from "react"; import styled from "styled-components"; import { VSCodeLink } from "@vscode/webview-ui-toolkit/react"; import { @@ -9,7 +10,7 @@ import { import { tryGetRemoteLocation } from "../../pure/bqrs-utils"; import TextButton from "./TextButton"; import { convertNonPrintableChars } from "../../text-utils"; -import { sendTelemetry, useStateWithTelemetry } from "../common/telemetry"; +import { sendTelemetry, useTelemetryOnChange } from "../common/telemetry"; const numOfResultsInContractedMode = 5; @@ -108,11 +109,10 @@ const RawResultsTable = ({ fileLinkPrefix, sourceLocationPrefix, }: RawResultsTableProps) => { - const [tableExpanded, setTableExpanded] = useStateWithTelemetry( - false, - "raw-results-table-expanded", - filterTableExpandedTelemetry, - ); + const [tableExpanded, setTableExpanded] = useState(false); + useTelemetryOnChange(tableExpanded, "raw-results-table-expanded", { + filterTelemetryOnValue: filterTableExpandedTelemetry, + }); const numOfResultsToShow = tableExpanded ? results.rows.length : numOfResultsInContractedMode; diff --git a/extensions/ql-vscode/src/view/variant-analysis/RepoRow.tsx b/extensions/ql-vscode/src/view/variant-analysis/RepoRow.tsx index 7062f6fcd16..274fe7ccca1 100644 --- a/extensions/ql-vscode/src/view/variant-analysis/RepoRow.tsx +++ b/extensions/ql-vscode/src/view/variant-analysis/RepoRow.tsx @@ -24,7 +24,7 @@ import { vscode } from "../vscode-api"; import { AnalyzedRepoItemContent } from "./AnalyzedRepoItemContent"; import StarCount from "../common/StarCount"; import { LastUpdated } from "../common/LastUpdated"; -import { useStateWithTelemetry } from "../common/telemetry"; +import { useTelemetryOnChange } from "../common/telemetry"; // This will ensure that these icons have a className which we can use in the TitleContainer const ExpandCollapseCodicon = styled(Codicon)``; @@ -170,11 +170,10 @@ export const RepoRow = ({ selected, onSelectedChange, }: RepoRowProps) => { - const [isExpanded, setExpanded] = useStateWithTelemetry( - false, - "variant-analysis-repo-row-expanded", - filterRepoRowExpandedTelemetry, - ); + const [isExpanded, setExpanded] = useState(false); + useTelemetryOnChange(isExpanded, "variant-analysis-repo-row-expanded", { + filterTelemetryOnValue: filterRepoRowExpandedTelemetry, + }); const resultsLoaded = !!interpretedResults || !!rawResults; const [resultsLoading, setResultsLoading] = useState(false); @@ -189,7 +188,7 @@ export const RepoRow = ({ downloadStatus !== VariantAnalysisScannedRepositoryDownloadStatus.Succeeded ) { - setExpanded(!isExpanded); + setExpanded((oldIsExpanded) => !oldIsExpanded); return; } @@ -205,7 +204,6 @@ export const RepoRow = ({ repository.fullName, status, downloadStatus, - isExpanded, setExpanded, ]); diff --git a/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysis.tsx b/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysis.tsx index 896900bd55c..f7670ca2d10 100644 --- a/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysis.tsx +++ b/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysis.tsx @@ -12,11 +12,8 @@ import { VariantAnalysisOutcomePanels } from "./VariantAnalysisOutcomePanels"; import { VariantAnalysisLoading } from "./VariantAnalysisLoading"; import { ToVariantAnalysisMessage } from "../../pure/interface-types"; import { vscode } from "../vscode-api"; -import { - defaultFilterSortState, - RepositoriesFilterSortState, -} from "../../pure/variant-analysis-filter-sort"; -import { useStateWithTelemetry } from "../common/telemetry"; +import { defaultFilterSortState } from "../../pure/variant-analysis-filter-sort"; +import { useTelemetryOnChange } from "../common/telemetry"; export type VariantAnalysisProps = { variantAnalysis?: VariantAnalysisDomainModel; @@ -61,16 +58,17 @@ export function VariantAnalysis({ const [repoResults, setRepoResults] = useState(initialRepoResults); - const [selectedRepositoryIds, setSelectedRepositoryIds] = - useStateWithTelemetry( - [], - "variant-analysis-selected-repository-ids", - ); - const [filterSortState, setFilterSortState] = - useStateWithTelemetry( - defaultFilterSortState, - "variant-analysis-filter-sort-state", - ); + const [selectedRepositoryIds, setSelectedRepositoryIds] = useState( + [], + ); + useTelemetryOnChange( + selectedRepositoryIds, + "variant-analysis-selected-repository-ids", + ); + const [filterSortState, setFilterSortState] = useState( + defaultFilterSortState, + ); + useTelemetryOnChange(filterSortState, "variant-analysis-filter-sort-state"); useEffect(() => { const listener = (evt: MessageEvent) => { From 47f63f6df6f9398d49c8aaac4fd878ea71c6bd5f Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 13 Jan 2023 12:10:28 +0000 Subject: [PATCH 09/14] Implement debounce for telemetry events --- .../ql-vscode/src/view/common/telemetry.ts | 24 ++++++++++++++++--- .../view/variant-analysis/VariantAnalysis.tsx | 4 +++- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/extensions/ql-vscode/src/view/common/telemetry.ts b/extensions/ql-vscode/src/view/common/telemetry.ts index b2fb12cd43c..8c4133a7d0c 100644 --- a/extensions/ql-vscode/src/view/common/telemetry.ts +++ b/extensions/ql-vscode/src/view/common/telemetry.ts @@ -1,4 +1,4 @@ -import { useEffect, useRef } from "react"; +import { useEffect, useMemo, useRef } from "react"; import { vscode } from "../vscode-api"; /** @@ -9,16 +9,34 @@ import { vscode } from "../vscode-api"; * @param options Extra optional arguments, including: * filterTelemetryOnValue: If provided, only output telemetry events when the * predicate returns true. If not provided always outputs telemetry. + * debounceTimeout: If provided, will not output telemetry events for every change + * but will wait until specified timeout happens with no new events ocurring. */ export function useTelemetryOnChange( value: S, telemetryAction: string, options?: { filterTelemetryOnValue?: (value: S) => boolean; + debounceTimeoutMillis?: number; }, ) { const previousValue = useRef(value); const filterTelemetryOnValue = options?.filterTelemetryOnValue; + const debounceTimeoutMillis = options?.debounceTimeoutMillis; + + const sendTelemetryFunc = useMemo<() => void>(() => { + if (debounceTimeoutMillis === undefined) { + return () => sendTelemetry(telemetryAction); + } else { + let timer: NodeJS.Timeout; + return () => { + clearTimeout(timer); + timer = setTimeout(() => { + sendTelemetry(telemetryAction); + }, debounceTimeoutMillis); + }; + } + }, [telemetryAction, debounceTimeoutMillis]); useEffect(() => { if (value === previousValue.current) { @@ -30,8 +48,8 @@ export function useTelemetryOnChange( return; } - sendTelemetry(telemetryAction); - }, [telemetryAction, filterTelemetryOnValue, value, previousValue]); + sendTelemetryFunc(); + }, [sendTelemetryFunc, filterTelemetryOnValue, value, previousValue]); } export function sendTelemetry(telemetryAction: string) { diff --git a/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysis.tsx b/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysis.tsx index f7670ca2d10..6886f304fa4 100644 --- a/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysis.tsx +++ b/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysis.tsx @@ -68,7 +68,9 @@ export function VariantAnalysis({ const [filterSortState, setFilterSortState] = useState( defaultFilterSortState, ); - useTelemetryOnChange(filterSortState, "variant-analysis-filter-sort-state"); + useTelemetryOnChange(filterSortState, "variant-analysis-filter-sort-state", { + debounceTimeoutMillis: 1000, + }); useEffect(() => { const listener = (evt: MessageEvent) => { From 18ddb3a297c26fb76f04bb62034ef4aec8806f1d Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 16 Jan 2023 16:12:20 +0000 Subject: [PATCH 10/14] Use isCanary() method --- extensions/ql-vscode/src/telemetry.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/extensions/ql-vscode/src/telemetry.ts b/extensions/ql-vscode/src/telemetry.ts index 3cb1a00ed99..723e6a3600e 100644 --- a/extensions/ql-vscode/src/telemetry.ts +++ b/extensions/ql-vscode/src/telemetry.ts @@ -12,6 +12,7 @@ import { GLOBAL_ENABLE_TELEMETRY, LOG_TELEMETRY, isIntegrationTestMode, + isCanary, } from "./config"; import * as appInsights from "applicationinsights"; import { extLogger } from "./common"; @@ -155,14 +156,12 @@ export class TelemetryListener extends ConfigListener { ? CommandCompletion.Cancelled : CommandCompletion.Failed; - const isCanary = (!!CANARY_FEATURES.getValue()).toString(); - this.reporter.sendTelemetryEvent( "command-usage", { name, status, - isCanary, + isCanary: isCanary(), }, { executionTime }, ); @@ -173,13 +172,11 @@ export class TelemetryListener extends ConfigListener { return; } - const isCanary = (!!CANARY_FEATURES.getValue()).toString(); - this.reporter.sendTelemetryEvent( "ui-interaction", { name, - isCanary, + isCanary: isCanary(), }, {}, ); From 38cbb95ecc8015b26c4eb900bb2ca9505625ace0 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 16 Jan 2023 16:34:13 +0000 Subject: [PATCH 11/14] Avoid === true on a boolean --- extensions/ql-vscode/src/view/common/CodePaths/CodePaths.tsx | 2 +- .../ql-vscode/src/view/remote-queries/RawResultsTable.tsx | 2 +- extensions/ql-vscode/src/view/variant-analysis/RepoRow.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions/ql-vscode/src/view/common/CodePaths/CodePaths.tsx b/extensions/ql-vscode/src/view/common/CodePaths/CodePaths.tsx index ba1c2d03ef3..717cde2f8e6 100644 --- a/extensions/ql-vscode/src/view/common/CodePaths/CodePaths.tsx +++ b/extensions/ql-vscode/src/view/common/CodePaths/CodePaths.tsx @@ -24,7 +24,7 @@ export type CodePathsProps = { severity: ResultSeverity; }; -const filterIsOpenTelemetry = (v: boolean) => v === true; +const filterIsOpenTelemetry = (v: boolean) => v; export const CodePaths = ({ codeFlows, diff --git a/extensions/ql-vscode/src/view/remote-queries/RawResultsTable.tsx b/extensions/ql-vscode/src/view/remote-queries/RawResultsTable.tsx index 266a5279c04..6ad78c29952 100644 --- a/extensions/ql-vscode/src/view/remote-queries/RawResultsTable.tsx +++ b/extensions/ql-vscode/src/view/remote-queries/RawResultsTable.tsx @@ -101,7 +101,7 @@ type RawResultsTableProps = { sourceLocationPrefix: string; }; -const filterTableExpandedTelemetry = (v: boolean) => v === true; +const filterTableExpandedTelemetry = (v: boolean) => v; const RawResultsTable = ({ schema, diff --git a/extensions/ql-vscode/src/view/variant-analysis/RepoRow.tsx b/extensions/ql-vscode/src/view/variant-analysis/RepoRow.tsx index 274fe7ccca1..a1aa2790fda 100644 --- a/extensions/ql-vscode/src/view/variant-analysis/RepoRow.tsx +++ b/extensions/ql-vscode/src/view/variant-analysis/RepoRow.tsx @@ -158,7 +158,7 @@ const isExpandableContentLoaded = ( return resultsLoaded; }; -const filterRepoRowExpandedTelemetry = (v: boolean) => v === true; +const filterRepoRowExpandedTelemetry = (v: boolean) => v; export const RepoRow = ({ repository, From 293ec1f204cd64a6ce6b816d6a66d1848a886db0 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 16 Jan 2023 16:36:00 +0000 Subject: [PATCH 12/14] Add debounce to variant-analysis-selected-repository-ids --- .../ql-vscode/src/view/variant-analysis/VariantAnalysis.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysis.tsx b/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysis.tsx index 6886f304fa4..55c564d1007 100644 --- a/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysis.tsx +++ b/extensions/ql-vscode/src/view/variant-analysis/VariantAnalysis.tsx @@ -64,6 +64,9 @@ export function VariantAnalysis({ useTelemetryOnChange( selectedRepositoryIds, "variant-analysis-selected-repository-ids", + { + debounceTimeoutMillis: 1000, + }, ); const [filterSortState, setFilterSortState] = useState( defaultFilterSortState, From 44a0cad1460d7b2ce018f4b9ba56e9eb3f355cd2 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 16 Jan 2023 16:44:32 +0000 Subject: [PATCH 13/14] Make useTelemetryOnChange signature simpler --- extensions/ql-vscode/src/view/common/telemetry.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/extensions/ql-vscode/src/view/common/telemetry.ts b/extensions/ql-vscode/src/view/common/telemetry.ts index 8c4133a7d0c..272dc074196 100644 --- a/extensions/ql-vscode/src/view/common/telemetry.ts +++ b/extensions/ql-vscode/src/view/common/telemetry.ts @@ -15,14 +15,15 @@ import { vscode } from "../vscode-api"; export function useTelemetryOnChange( value: S, telemetryAction: string, - options?: { + { + filterTelemetryOnValue, + debounceTimeoutMillis, + }: { filterTelemetryOnValue?: (value: S) => boolean; debounceTimeoutMillis?: number; - }, + } = {}, ) { const previousValue = useRef(value); - const filterTelemetryOnValue = options?.filterTelemetryOnValue; - const debounceTimeoutMillis = options?.debounceTimeoutMillis; const sendTelemetryFunc = useMemo<() => void>(() => { if (debounceTimeoutMillis === undefined) { From 3777eb382f0232367f1cdc3ab33531b11328071b Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 16 Jan 2023 16:56:30 +0000 Subject: [PATCH 14/14] Make isCanary a string --- extensions/ql-vscode/src/telemetry.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/ql-vscode/src/telemetry.ts b/extensions/ql-vscode/src/telemetry.ts index 723e6a3600e..948b13c90ff 100644 --- a/extensions/ql-vscode/src/telemetry.ts +++ b/extensions/ql-vscode/src/telemetry.ts @@ -161,7 +161,7 @@ export class TelemetryListener extends ConfigListener { { name, status, - isCanary: isCanary(), + isCanary: isCanary().toString(), }, { executionTime }, ); @@ -176,7 +176,7 @@ export class TelemetryListener extends ConfigListener { "ui-interaction", { name, - isCanary: isCanary(), + isCanary: isCanary().toString(), }, {}, );