Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions extensions/ql-vscode/src/pure/interface-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,8 @@ export type FromRemoteQueriesMessage =
| RemoteQueryDownloadAnalysisResultsMessage
| RemoteQueryDownloadAllAnalysesResultsMessage
| RemoteQueryExportResultsMessage
| CopyRepoListMessage;
| CopyRepoListMessage
| TelemetryMessage;

export type ToRemoteQueriesMessage =
| SetRemoteQueryResultMessage
Expand Down Expand Up @@ -504,6 +505,11 @@ export interface CancelVariantAnalysisMessage {
t: "cancelVariantAnalysis";
}

export interface TelemetryMessage {
t: "telemetry";
action: string;
}

export type ToVariantAnalysisMessage =
| SetVariantAnalysisMessage
| SetRepoResultsMessage
Expand All @@ -517,4 +523,5 @@ export type FromVariantAnalysisMessage =
| CopyRepositoryListMessage
| ExportResultsMessage
| OpenLogsMessage
| CancelVariantAnalysisMessage;
| CancelVariantAnalysisMessage
| TelemetryMessage;
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -167,6 +168,9 @@ export class RemoteQueriesView extends AbstractWebview<
msg.queryId,
);
break;
case "telemetry":
telemetryListener?.sendUIInteraction(msg.action);
break;
default:
assertNever(msg);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
VariantAnalysisViewManager,
} from "./variant-analysis-view-manager";
import { showAndLogWarningMessage } from "../helpers";
import { telemetryListener } from "../telemetry";

export class VariantAnalysisView
extends AbstractWebview<ToVariantAnalysisMessage, FromVariantAnalysisMessage>
Expand Down Expand Up @@ -151,6 +152,9 @@ export class VariantAnalysisView
this.variantAnalysisId,
);
break;
case "telemetry":
Comment thread
robertbrignull marked this conversation as resolved.
telemetryListener?.sendUIInteraction(msg.action);
break;
default:
assertNever(msg);
}
Expand Down
20 changes: 17 additions & 3 deletions extensions/ql-vscode/src/telemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
GLOBAL_ENABLE_TELEMETRY,
LOG_TELEMETRY,
isIntegrationTestMode,
isCanary,
} from "./config";
import * as appInsights from "applicationinsights";
import { extLogger } from "./common";
Expand Down Expand Up @@ -155,19 +156,32 @@ export class TelemetryListener extends ConfigListener {
? CommandCompletion.Cancelled
: CommandCompletion.Failed;

const isCanary = (!!CANARY_FEATURES.getValue<boolean>()).toString();

this.reporter.sendTelemetryEvent(
"command-usage",
{
name,
status,
isCanary,
isCanary: isCanary().toString(),
},
{ executionTime },
);
}

sendUIInteraction(name: string) {
if (!this.reporter) {
return;
}

this.reporter.sendTelemetryEvent(
"ui-interaction",
{
name,
isCanary: isCanary().toString(),
},
{},
);
}

/**
* Displays a popup asking the user if they want to enable telemetry
* for this extension.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
ResultSeverity,
} from "../../../remote-queries/shared/analysis-result";
import { CodePathsOverlay } from "./CodePathsOverlay";
import { useTelemetryOnChange } from "../telemetry";

const ShowPathsLink = styled(VSCodeLink)`
cursor: pointer;
Expand All @@ -23,13 +24,18 @@ export type CodePathsProps = {
severity: ResultSeverity;
};

const filterIsOpenTelemetry = (v: boolean) => v;

export const CodePaths = ({
codeFlows,
ruleDescription,
message,
severity,
}: CodePathsProps) => {
const [isOpen, setIsOpen] = useState(false);
useTelemetryOnChange(isOpen, "code-path-is-open", {
filterTelemetryOnValue: filterIsOpenTelemetry,
});

const linkRef = useRef<HTMLAnchorElement>(null);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
CodeFlow,
ResultSeverity,
} from "../../../remote-queries/shared/analysis-result";
import { useTelemetryOnChange } from "../telemetry";
import { SectionTitle } from "../SectionTitle";
import { VerticalSpace } from "../VerticalSpace";
import { CodeFlowsDropdown } from "./CodeFlowsDropdown";
Expand Down Expand Up @@ -77,6 +78,7 @@ export const CodePathsOverlay = ({
onClose,
}: CodePathsOverlayProps) => {
const [selectedCodeFlow, setSelectedCodeFlow] = useState(codeFlows[0]);
useTelemetryOnChange(selectedCodeFlow, "code-flow-selected");

return (
<OverlayContainer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -49,6 +50,8 @@ type CodeSnippetMessageProps = {
children: React.ReactNode;
};

const sendAlertMessageLinkTelemetry = () => sendTelemetry("alert-message-link");

export const CodeSnippetMessage = ({
message,
severity,
Expand All @@ -65,6 +68,7 @@ export const CodeSnippetMessage = ({
return (
<LocationLink
key={index}
onClick={sendAlertMessageLinkTelemetry}
href={createRemoteFileRef(
token.location.fileLink,
token.location.highlightedRegion?.startLine,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)";

Expand Down Expand Up @@ -46,6 +47,9 @@ type Props = {
messageChildren?: React.ReactNode;
};

const sendCodeSnippetTitleLinkTelemetry = () =>
sendTelemetry("file-code-snippet-title-link");

export const FileCodeSnippet = ({
fileLink,
codeSnippet,
Expand All @@ -67,7 +71,12 @@ export const FileCodeSnippet = ({
return (
<Container>
<TitleContainer>
<VSCodeLink href={titleFileUri}>{fileLink.filePath}</VSCodeLink>
<VSCodeLink
onClick={sendCodeSnippetTitleLinkTelemetry}
href={titleFileUri}
>
{fileLink.filePath}
</VSCodeLink>
</TitleContainer>
{message && severity && (
<CodeSnippetMessage message={message} severity={severity}>
Expand All @@ -83,7 +92,12 @@ export const FileCodeSnippet = ({
return (
<Container>
<TitleContainer>
<VSCodeLink href={titleFileUri}>{fileLink.filePath}</VSCodeLink>
<VSCodeLink
onClick={sendCodeSnippetTitleLinkTelemetry}
href={titleFileUri}
>
{fileLink.filePath}
</VSCodeLink>
</TitleContainer>
<CodeContainer>
{code.map((line, index) => (
Expand Down
61 changes: 61 additions & 0 deletions extensions/ql-vscode/src/view/common/telemetry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { useEffect, useMemo, useRef } from "react";
import { vscode } from "../vscode-api";

/**
* A react effect that outputs telemetry events whenever the value changes.
*
* @param value Default value to pass to React.useState
* @param telemetryAction Name of the telemetry event to output
* @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<S>(
value: S,
telemetryAction: string,
{
filterTelemetryOnValue,
debounceTimeoutMillis,
}: {
filterTelemetryOnValue?: (value: S) => boolean;
debounceTimeoutMillis?: number;
} = {},
) {
const previousValue = useRef(value);

const sendTelemetryFunc = useMemo<() => void>(() => {
Comment thread
aeisenberg marked this conversation as resolved.
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) {
return;
}
previousValue.current = value;

if (filterTelemetryOnValue && !filterTelemetryOnValue(value)) {
return;
}

sendTelemetryFunc();
}, [sendTelemetryFunc, filterTelemetryOnValue, value, previousValue]);
}

export function sendTelemetry(telemetryAction: string) {
vscode.postMessage({
t: "telemetry",
action: telemetryAction,
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import { tryGetRemoteLocation } from "../../pure/bqrs-utils";
import TextButton from "./TextButton";
import { convertNonPrintableChars } from "../../text-utils";
import { sendTelemetry, useTelemetryOnChange } from "../common/telemetry";

const numOfResultsInContractedMode = 5;

Expand Down Expand Up @@ -45,6 +46,8 @@ type CellProps = {
sourceLocationPrefix: string;
};

const sendRawResultsLinkTelemetry = () => sendTelemetry("raw-results-link");

const Cell = ({ value, fileLinkPrefix, sourceLocationPrefix }: CellProps) => {
switch (typeof value) {
case "string":
Expand All @@ -59,7 +62,11 @@ const Cell = ({ value, fileLinkPrefix, sourceLocationPrefix }: CellProps) => {
);
const safeLabel = convertNonPrintableChars(value.label);
if (url) {
return <VSCodeLink href={url}>{safeLabel}</VSCodeLink>;
return (
<VSCodeLink onClick={sendRawResultsLinkTelemetry} href={url}>
{safeLabel}
</VSCodeLink>
);
} else {
return <span>{safeLabel}</span>;
}
Expand Down Expand Up @@ -94,13 +101,18 @@ type RawResultsTableProps = {
sourceLocationPrefix: string;
};

const filterTableExpandedTelemetry = (v: boolean) => v;

const RawResultsTable = ({
schema,
results,
fileLinkPrefix,
sourceLocationPrefix,
}: RawResultsTableProps) => {
const [tableExpanded, setTableExpanded] = useState(false);
useTelemetryOnChange(tableExpanded, "raw-results-table-expanded", {
filterTelemetryOnValue: filterTableExpandedTelemetry,
});
const numOfResultsToShow = tableExpanded
? results.rows.length
: numOfResultsInContractedMode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 <></>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { vscode } from "../vscode-api";
import { AnalyzedRepoItemContent } from "./AnalyzedRepoItemContent";
import StarCount from "../common/StarCount";
import { LastUpdated } from "../common/LastUpdated";
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)``;
Expand Down Expand Up @@ -157,6 +158,8 @@ const isExpandableContentLoaded = (
return resultsLoaded;
};

const filterRepoRowExpandedTelemetry = (v: boolean) => v;

export const RepoRow = ({
repository,
status,
Expand All @@ -168,6 +171,9 @@ export const RepoRow = ({
onSelectedChange,
}: RepoRowProps) => {
const [isExpanded, setExpanded] = useState(false);
useTelemetryOnChange(isExpanded, "variant-analysis-repo-row-expanded", {
filterTelemetryOnValue: filterRepoRowExpandedTelemetry,
});
const resultsLoaded = !!interpretedResults || !!rawResults;
const [resultsLoading, setResultsLoading] = useState(false);

Expand Down Expand Up @@ -198,14 +204,15 @@ export const RepoRow = ({
repository.fullName,
status,
downloadStatus,
setExpanded,
]);

useEffect(() => {
if (resultsLoaded && resultsLoading) {
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
Expand Down
Loading