Output telemetry on react state changes in MRVA results view
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useRef, useState } from "react";
|
import { useRef } from "react";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { VSCodeLink } from "@vscode/webview-ui-toolkit/react";
|
import { VSCodeLink } from "@vscode/webview-ui-toolkit/react";
|
||||||
|
|
||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
ResultSeverity,
|
ResultSeverity,
|
||||||
} from "../../../remote-queries/shared/analysis-result";
|
} from "../../../remote-queries/shared/analysis-result";
|
||||||
import { CodePathsOverlay } from "./CodePathsOverlay";
|
import { CodePathsOverlay } from "./CodePathsOverlay";
|
||||||
|
import { useStateWithTelemetry } from "../Telemetry";
|
||||||
|
|
||||||
const ShowPathsLink = styled(VSCodeLink)`
|
const ShowPathsLink = styled(VSCodeLink)`
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -29,7 +30,11 @@ export const CodePaths = ({
|
|||||||
message,
|
message,
|
||||||
severity,
|
severity,
|
||||||
}: CodePathsProps) => {
|
}: CodePathsProps) => {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useStateWithTelemetry(
|
||||||
|
false,
|
||||||
|
"code-path-is-open",
|
||||||
|
(v) => v === true,
|
||||||
|
);
|
||||||
|
|
||||||
const linkRef = useRef<HTMLAnchorElement>(null);
|
const linkRef = useRef<HTMLAnchorElement>(null);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useState } from "react";
|
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -7,6 +6,7 @@ import {
|
|||||||
CodeFlow,
|
CodeFlow,
|
||||||
ResultSeverity,
|
ResultSeverity,
|
||||||
} from "../../../remote-queries/shared/analysis-result";
|
} from "../../../remote-queries/shared/analysis-result";
|
||||||
|
import { useStateWithTelemetry } from "../Telemetry";
|
||||||
import { SectionTitle } from "../SectionTitle";
|
import { SectionTitle } from "../SectionTitle";
|
||||||
import { VerticalSpace } from "../VerticalSpace";
|
import { VerticalSpace } from "../VerticalSpace";
|
||||||
import { CodeFlowsDropdown } from "./CodeFlowsDropdown";
|
import { CodeFlowsDropdown } from "./CodeFlowsDropdown";
|
||||||
@@ -76,7 +76,10 @@ export const CodePathsOverlay = ({
|
|||||||
severity,
|
severity,
|
||||||
onClose,
|
onClose,
|
||||||
}: CodePathsOverlayProps) => {
|
}: CodePathsOverlayProps) => {
|
||||||
const [selectedCodeFlow, setSelectedCodeFlow] = useState(codeFlows[0]);
|
const [selectedCodeFlow, setSelectedCodeFlow] = useStateWithTelemetry(
|
||||||
|
codeFlows[0],
|
||||||
|
"code-flow-selected",
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OverlayContainer>
|
<OverlayContainer>
|
||||||
|
|||||||
52
extensions/ql-vscode/src/view/common/Telemetry.ts
Normal file
52
extensions/ql-vscode/src/view/common/Telemetry.ts
Normal file
@@ -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<S>(
|
||||||
|
defaultValue: S | (() => S),
|
||||||
|
telemetryAction: string,
|
||||||
|
): [S, React.Dispatch<React.SetStateAction<S>>];
|
||||||
|
export function useStateWithTelemetry<S>(
|
||||||
|
defaultValue: S | (() => S),
|
||||||
|
telemetryAction: string,
|
||||||
|
filterTelemetryOnValue: (value: S) => boolean,
|
||||||
|
): [S, React.Dispatch<S>];
|
||||||
|
export function useStateWithTelemetry<S>(
|
||||||
|
defaultValue: S | (() => S),
|
||||||
|
telemetryAction: string,
|
||||||
|
filterTelemetryOnValue?: (value: S) => boolean,
|
||||||
|
): [S, React.Dispatch<S> | React.Dispatch<React.SetStateAction<S>>] {
|
||||||
|
const [value, setter] = useState<S>(defaultValue);
|
||||||
|
if (filterTelemetryOnValue === undefined) {
|
||||||
|
const setterWithTelemetry = (x: React.SetStateAction<S>) => {
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useState } from "react";
|
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { VSCodeLink } from "@vscode/webview-ui-toolkit/react";
|
import { VSCodeLink } from "@vscode/webview-ui-toolkit/react";
|
||||||
import {
|
import {
|
||||||
@@ -10,6 +9,7 @@ import {
|
|||||||
import { tryGetRemoteLocation } from "../../pure/bqrs-utils";
|
import { tryGetRemoteLocation } from "../../pure/bqrs-utils";
|
||||||
import TextButton from "./TextButton";
|
import TextButton from "./TextButton";
|
||||||
import { convertNonPrintableChars } from "../../text-utils";
|
import { convertNonPrintableChars } from "../../text-utils";
|
||||||
|
import { useStateWithTelemetry } from "../common/Telemetry";
|
||||||
|
|
||||||
const numOfResultsInContractedMode = 5;
|
const numOfResultsInContractedMode = 5;
|
||||||
|
|
||||||
@@ -100,7 +100,11 @@ const RawResultsTable = ({
|
|||||||
fileLinkPrefix,
|
fileLinkPrefix,
|
||||||
sourceLocationPrefix,
|
sourceLocationPrefix,
|
||||||
}: RawResultsTableProps) => {
|
}: RawResultsTableProps) => {
|
||||||
const [tableExpanded, setTableExpanded] = useState(false);
|
const [tableExpanded, setTableExpanded] = useStateWithTelemetry(
|
||||||
|
false,
|
||||||
|
"raw-results-table-expanded",
|
||||||
|
(v) => v === true,
|
||||||
|
);
|
||||||
const numOfResultsToShow = tableExpanded
|
const numOfResultsToShow = tableExpanded
|
||||||
? results.rows.length
|
? results.rows.length
|
||||||
: numOfResultsInContractedMode;
|
: numOfResultsInContractedMode;
|
||||||
|
|||||||
@@ -433,7 +433,7 @@ const AnalysesResults = ({
|
|||||||
sort: Sort;
|
sort: Sort;
|
||||||
}) => {
|
}) => {
|
||||||
const totalAnalysesResults = sumAnalysesResults(analysesResults);
|
const totalAnalysesResults = sumAnalysesResults(analysesResults);
|
||||||
const [filterValue, setFilterValue] = React.useState("");
|
const [filterValue, setFilterValue] = useState("");
|
||||||
|
|
||||||
if (totalResults === 0) {
|
if (totalResults === 0) {
|
||||||
return <></>;
|
return <></>;
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import { vscode } from "../vscode-api";
|
|||||||
import { AnalyzedRepoItemContent } from "./AnalyzedRepoItemContent";
|
import { AnalyzedRepoItemContent } from "./AnalyzedRepoItemContent";
|
||||||
import StarCount from "../common/StarCount";
|
import StarCount from "../common/StarCount";
|
||||||
import { LastUpdated } from "../common/LastUpdated";
|
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
|
// This will ensure that these icons have a className which we can use in the TitleContainer
|
||||||
const ExpandCollapseCodicon = styled(Codicon)``;
|
const ExpandCollapseCodicon = styled(Codicon)``;
|
||||||
@@ -167,7 +168,11 @@ export const RepoRow = ({
|
|||||||
selected,
|
selected,
|
||||||
onSelectedChange,
|
onSelectedChange,
|
||||||
}: RepoRowProps) => {
|
}: RepoRowProps) => {
|
||||||
const [isExpanded, setExpanded] = useState(false);
|
const [isExpanded, setExpanded] = useStateWithTelemetry(
|
||||||
|
false,
|
||||||
|
"variant-analysis-repo-row-expanded",
|
||||||
|
(v) => v === true,
|
||||||
|
);
|
||||||
const resultsLoaded = !!interpretedResults || !!rawResults;
|
const resultsLoaded = !!interpretedResults || !!rawResults;
|
||||||
const [resultsLoading, setResultsLoading] = useState(false);
|
const [resultsLoading, setResultsLoading] = useState(false);
|
||||||
|
|
||||||
@@ -182,7 +187,7 @@ export const RepoRow = ({
|
|||||||
downloadStatus !==
|
downloadStatus !==
|
||||||
VariantAnalysisScannedRepositoryDownloadStatus.Succeeded
|
VariantAnalysisScannedRepositoryDownloadStatus.Succeeded
|
||||||
) {
|
) {
|
||||||
setExpanded((oldIsExpanded) => !oldIsExpanded);
|
setExpanded(!isExpanded);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,6 +203,8 @@ export const RepoRow = ({
|
|||||||
repository.fullName,
|
repository.fullName,
|
||||||
status,
|
status,
|
||||||
downloadStatus,
|
downloadStatus,
|
||||||
|
isExpanded,
|
||||||
|
setExpanded,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
defaultFilterSortState,
|
defaultFilterSortState,
|
||||||
RepositoriesFilterSortState,
|
RepositoriesFilterSortState,
|
||||||
} from "../../pure/variant-analysis-filter-sort";
|
} from "../../pure/variant-analysis-filter-sort";
|
||||||
|
import { useStateWithTelemetry } from "../common/Telemetry";
|
||||||
|
|
||||||
export type VariantAnalysisProps = {
|
export type VariantAnalysisProps = {
|
||||||
variantAnalysis?: VariantAnalysisDomainModel;
|
variantAnalysis?: VariantAnalysisDomainModel;
|
||||||
@@ -60,11 +61,16 @@ export function VariantAnalysis({
|
|||||||
const [repoResults, setRepoResults] =
|
const [repoResults, setRepoResults] =
|
||||||
useState<VariantAnalysisScannedRepositoryResult[]>(initialRepoResults);
|
useState<VariantAnalysisScannedRepositoryResult[]>(initialRepoResults);
|
||||||
|
|
||||||
const [selectedRepositoryIds, setSelectedRepositoryIds] = useState<number[]>(
|
const [selectedRepositoryIds, setSelectedRepositoryIds] =
|
||||||
[],
|
useStateWithTelemetry<number[]>(
|
||||||
);
|
[],
|
||||||
|
"variant-analysis-selected-repository-ids",
|
||||||
|
);
|
||||||
const [filterSortState, setFilterSortState] =
|
const [filterSortState, setFilterSortState] =
|
||||||
useState<RepositoriesFilterSortState>(defaultFilterSortState);
|
useStateWithTelemetry<RepositoriesFilterSortState>(
|
||||||
|
defaultFilterSortState,
|
||||||
|
"variant-analysis-filter-sort-state",
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const listener = (evt: MessageEvent) => {
|
const listener = (evt: MessageEvent) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user