Merge pull request #2569 from github/robertbrignull/convert-ResultsApp

Convert ResultsApp to a function component
This commit is contained in:
Robert
2023-07-10 11:36:32 +01:00
committed by GitHub

View File

@@ -18,6 +18,7 @@ import { EventHandlers as EventHandlerList } from "./event-handler-list";
import { ResultTables } from "./result-tables";
import "./resultsView.css";
import { useCallback, useEffect } from "react";
/**
* results.tsx
@@ -70,239 +71,236 @@ export const onNavigation = new EventHandlerList<NavigateMsg>();
/**
* A minimal state container for displaying results.
*/
export class ResultsApp extends React.Component<
Record<string, never>,
ResultsViewState
> {
constructor(props: any) {
super(props);
this.state = {
displayedResults: {
resultsInfo: null,
results: null,
errorMessage: "",
},
nextResultsInfo: null,
isExpectingResultsUpdate: true,
};
}
export function ResultsApp() {
const [state, setState] = React.useState<ResultsViewState>({
displayedResults: {
resultsInfo: null,
results: null,
errorMessage: "",
},
nextResultsInfo: null,
isExpectingResultsUpdate: true,
});
handleMessage(msg: IntoResultsViewMsg): void {
switch (msg.t) {
case "setState":
this.updateStateWithNewResultsInfo({
resultsPath: msg.resultsPath,
parsedResultSets: msg.parsedResultSets,
origResultsPaths: msg.origResultsPaths,
sortedResultsMap: new Map(Object.entries(msg.sortedResultsMap)),
database: msg.database,
interpretation: msg.interpretation,
shouldKeepOldResultsWhileRendering:
msg.shouldKeepOldResultsWhileRendering,
metadata: msg.metadata,
queryName: msg.queryName,
queryPath: msg.queryPath,
});
break;
case "showInterpretedPage": {
const tableName =
msg.interpretation.data.t === "GraphInterpretationData"
? GRAPH_TABLE_NAME
: ALERTS_TABLE_NAME;
this.updateStateWithNewResultsInfo({
resultsPath: "", // FIXME: Not used for interpreted, refactor so this is not needed
parsedResultSets: {
numPages: msg.numPages,
pageSize: msg.pageSize,
numInterpretedPages: msg.numPages,
resultSetNames: msg.resultSetNames,
pageNumber: msg.pageNumber,
resultSet: {
t: "InterpretedResultSet",
name: tableName,
schema: {
name: tableName,
rows: 1,
columns: [],
},
interpretation: msg.interpretation,
const updateStateWithNewResultsInfo = useCallback(
(resultsInfo: ResultsInfo): void => {
setState((prevState) => {
if (resultsInfo === null && prevState.isExpectingResultsUpdate) {
// Display loading message
return {
...prevState,
displayedResults: {
resultsInfo: null,
results: null,
errorMessage: "Loading results…",
},
selectedTable: tableName,
},
origResultsPaths: undefined as any, // FIXME: Not used for interpreted, refactor so this is not needed
sortedResultsMap: new Map(), // FIXME: Not used for interpreted, refactor so this is not needed
database: msg.database,
interpretation: msg.interpretation,
shouldKeepOldResultsWhileRendering: true,
metadata: msg.metadata,
queryName: msg.queryName,
queryPath: msg.queryPath,
});
break;
}
case "resultsUpdating":
this.setState({
isExpectingResultsUpdate: true,
});
break;
case "navigate":
onNavigation.fire(msg);
break;
nextResultsInfo: resultsInfo,
};
} else if (resultsInfo === null) {
// No results to display
return {
...prevState,
displayedResults: {
resultsInfo: null,
results: null,
errorMessage: "No results to display",
},
nextResultsInfo: resultsInfo,
};
}
case "untoggleShowProblems":
// noop
break;
let results: Results | null = null;
let statusText = "";
try {
const resultSets = getResultSets(resultsInfo);
results = {
resultSets,
database: resultsInfo.database,
sortStates: getSortStates(resultsInfo),
};
} catch (e) {
const errorMessage = getErrorMessage(e);
default:
assertNever(msg);
}
}
statusText = `Error loading results: ${errorMessage}`;
}
private updateStateWithNewResultsInfo(resultsInfo: ResultsInfo): void {
this.setState((prevState) => {
if (resultsInfo === null && prevState.isExpectingResultsUpdate) {
// Display loading message
return {
displayedResults: {
resultsInfo: null,
results: null,
errorMessage: "Loading results…",
resultsInfo,
results,
errorMessage: statusText,
},
isExpectingResultsUpdate: prevState.isExpectingResultsUpdate,
nextResultsInfo: resultsInfo,
};
} else if (resultsInfo === null) {
// No results to display
return {
displayedResults: {
resultsInfo: null,
results: null,
errorMessage: "No results to display",
},
isExpectingResultsUpdate: prevState.isExpectingResultsUpdate,
nextResultsInfo: resultsInfo,
nextResultsInfo: null,
isExpectingResultsUpdate: false,
};
});
},
[],
);
const handleMessage = useCallback(
(msg: IntoResultsViewMsg): void => {
switch (msg.t) {
case "setState":
updateStateWithNewResultsInfo({
resultsPath: msg.resultsPath,
parsedResultSets: msg.parsedResultSets,
origResultsPaths: msg.origResultsPaths,
sortedResultsMap: new Map(Object.entries(msg.sortedResultsMap)),
database: msg.database,
interpretation: msg.interpretation,
shouldKeepOldResultsWhileRendering:
msg.shouldKeepOldResultsWhileRendering,
metadata: msg.metadata,
queryName: msg.queryName,
queryPath: msg.queryPath,
});
break;
case "showInterpretedPage": {
const tableName =
msg.interpretation.data.t === "GraphInterpretationData"
? GRAPH_TABLE_NAME
: ALERTS_TABLE_NAME;
updateStateWithNewResultsInfo({
resultsPath: "", // FIXME: Not used for interpreted, refactor so this is not needed
parsedResultSets: {
numPages: msg.numPages,
pageSize: msg.pageSize,
numInterpretedPages: msg.numPages,
resultSetNames: msg.resultSetNames,
pageNumber: msg.pageNumber,
resultSet: {
t: "InterpretedResultSet",
name: tableName,
schema: {
name: tableName,
rows: 1,
columns: [],
},
interpretation: msg.interpretation,
},
selectedTable: tableName,
},
origResultsPaths: undefined as any, // FIXME: Not used for interpreted, refactor so this is not needed
sortedResultsMap: new Map(), // FIXME: Not used for interpreted, refactor so this is not needed
database: msg.database,
interpretation: msg.interpretation,
shouldKeepOldResultsWhileRendering: true,
metadata: msg.metadata,
queryName: msg.queryName,
queryPath: msg.queryPath,
});
break;
}
case "resultsUpdating":
setState((prevState) => ({
...prevState,
isExpectingResultsUpdate: true,
}));
break;
case "navigate":
onNavigation.fire(msg);
break;
case "untoggleShowProblems":
// noop
break;
default:
assertNever(msg);
}
},
[updateStateWithNewResultsInfo],
);
let results: Results | null = null;
let statusText = "";
try {
const resultSets = this.getResultSets(resultsInfo);
results = {
resultSets,
database: resultsInfo.database,
sortStates: this.getSortStates(resultsInfo),
};
} catch (e) {
const errorMessage = getErrorMessage(e);
const vscodeMessageHandler = useCallback(
(evt: MessageEvent) => {
// sanitize origin
const origin = evt.origin.replace(/\n|\r/g, "");
evt.origin === window.origin
? handleMessage(evt.data as IntoResultsViewMsg)
: console.error(`Invalid event origin ${origin}`);
},
[handleMessage],
);
statusText = `Error loading results: ${errorMessage}`;
}
useEffect(() => {
window.addEventListener("message", vscodeMessageHandler);
return () => {
window.removeEventListener("message", vscodeMessageHandler);
};
}, [vscodeMessageHandler]);
return {
displayedResults: {
resultsInfo,
results,
errorMessage: statusText,
},
nextResultsInfo: null,
isExpectingResultsUpdate: false,
};
});
}
const { displayedResults, nextResultsInfo, isExpectingResultsUpdate } = state;
if (
displayedResults.results !== null &&
displayedResults.resultsInfo !== null
) {
const parsedResultSets = displayedResults.resultsInfo.parsedResultSets;
const key =
(parsedResultSets.selectedTable || "") + parsedResultSets.pageNumber;
const data = displayedResults.resultsInfo.interpretation?.data;
private getResultSets(resultsInfo: ResultsInfo): readonly ResultSet[] {
const parsedResultSets = resultsInfo.parsedResultSets;
const resultSet = parsedResultSets.resultSet;
if (
resultSet.t !== "InterpretedResultSet" &&
resultSet.t !== "RawResultSet"
) {
throw new Error(
`Invalid result set type. Should be either "InterpretedResultSet" or "RawResultSet", but got "${
(resultSet as { t: string }).t
}".`,
);
}
return [resultSet];
}
private getSortStates(
resultsInfo: ResultsInfo,
): Map<string, RawResultsSortState> {
const entries = Array.from(resultsInfo.sortedResultsMap.entries());
return new Map(
entries.map(([key, sortedResultSetInfo]) => [
key,
sortedResultSetInfo.sortState,
]),
return (
<ResultTables
key={key}
parsedResultSets={parsedResultSets}
rawResultSets={displayedResults.results.resultSets}
interpretation={
displayedResults.resultsInfo
? displayedResults.resultsInfo.interpretation
: undefined
}
database={displayedResults.results.database}
origResultsPaths={displayedResults.resultsInfo.origResultsPaths}
resultsPath={displayedResults.resultsInfo.resultsPath}
metadata={
displayedResults.resultsInfo
? displayedResults.resultsInfo.metadata
: undefined
}
sortStates={displayedResults.results.sortStates}
interpretedSortState={
data?.t === "SarifInterpretationData" ? data.sortState : undefined
}
isLoadingNewResults={
isExpectingResultsUpdate || nextResultsInfo !== null
}
queryName={displayedResults.resultsInfo.queryName}
queryPath={displayedResults.resultsInfo.queryPath}
/>
);
}
render(): JSX.Element {
const displayedResults = this.state.displayedResults;
if (
displayedResults.results !== null &&
displayedResults.resultsInfo !== null
) {
const parsedResultSets = displayedResults.resultsInfo.parsedResultSets;
const key =
(parsedResultSets.selectedTable || "") + parsedResultSets.pageNumber;
const data = displayedResults.resultsInfo.interpretation?.data;
return (
<ResultTables
key={key}
parsedResultSets={parsedResultSets}
rawResultSets={displayedResults.results.resultSets}
interpretation={
displayedResults.resultsInfo
? displayedResults.resultsInfo.interpretation
: undefined
}
database={displayedResults.results.database}
origResultsPaths={displayedResults.resultsInfo.origResultsPaths}
resultsPath={displayedResults.resultsInfo.resultsPath}
metadata={
displayedResults.resultsInfo
? displayedResults.resultsInfo.metadata
: undefined
}
sortStates={displayedResults.results.sortStates}
interpretedSortState={
data?.t === "SarifInterpretationData" ? data.sortState : undefined
}
isLoadingNewResults={
this.state.isExpectingResultsUpdate ||
this.state.nextResultsInfo !== null
}
queryName={displayedResults.resultsInfo.queryName}
queryPath={displayedResults.resultsInfo.queryPath}
/>
);
} else {
return <span>{displayedResults.errorMessage}</span>;
}
}
componentDidMount(): void {
this.vscodeMessageHandler = this.vscodeMessageHandler.bind(this);
window.addEventListener("message", this.vscodeMessageHandler);
}
componentWillUnmount(): void {
if (this.vscodeMessageHandler) {
window.removeEventListener("message", this.vscodeMessageHandler);
}
}
private vscodeMessageHandler(evt: MessageEvent) {
// sanitize origin
const origin = evt.origin.replace(/\n|\r/g, "");
evt.origin === window.origin
? this.handleMessage(evt.data as IntoResultsViewMsg)
: console.error(`Invalid event origin ${origin}`);
} else {
return <span>{displayedResults.errorMessage}</span>;
}
}
function getSortStates(
resultsInfo: ResultsInfo,
): Map<string, RawResultsSortState> {
const entries = Array.from(resultsInfo.sortedResultsMap.entries());
return new Map(
entries.map(([key, sortedResultSetInfo]) => [
key,
sortedResultSetInfo.sortState,
]),
);
}
function getResultSets(resultsInfo: ResultsInfo): readonly ResultSet[] {
const parsedResultSets = resultsInfo.parsedResultSets;
const resultSet = parsedResultSets.resultSet;
if (
resultSet.t !== "InterpretedResultSet" &&
resultSet.t !== "RawResultSet"
) {
throw new Error(
`Invalid result set type. Should be either "InterpretedResultSet" or "RawResultSet", but got "${
(resultSet as { t: string }).t
}".`,
);
}
return [resultSet];
}