Output telemetry on react state changes in MRVA results view

This commit is contained in:
Robert
2023-01-11 12:56:55 +00:00
parent bdfb2f29da
commit f325eeb5ab
7 changed files with 90 additions and 13 deletions

View File

@@ -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);

View File

@@ -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>

View 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];
}
}

View File

@@ -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;

View File

@@ -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 <></>;

View File

@@ -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(() => {

View File

@@ -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) => {