diff --git a/extensions/ql-vscode/src/view/results/__tests__/results.spec.tsx b/extensions/ql-vscode/src/view/results/__tests__/results.spec.tsx
index f34e63906..c0a416347 100644
--- a/extensions/ql-vscode/src/view/results/__tests__/results.spec.tsx
+++ b/extensions/ql-vscode/src/view/results/__tests__/results.spec.tsx
@@ -1,5 +1,5 @@
import * as React from "react";
-import { render as reactRender, screen } from "@testing-library/react";
+import { act, render as reactRender, screen } from "@testing-library/react";
import { ResultsApp } from "../results";
import {
Interpretation,
@@ -20,18 +20,20 @@ const exampleSarif = fs.readJSONSync(
describe(ResultsApp.name, () => {
const render = () => reactRender();
const postMessage = async (msg: IntoResultsViewMsg) => {
- // window.postMessage doesn't set the origin correctly, see
- // https://github.com/jsdom/jsdom/issues/2745
- window.dispatchEvent(
- new MessageEvent("message", {
- source: window,
- origin: window.location.origin,
- data: msg,
- }),
- );
+ await act(async () => {
+ // window.postMessage doesn't set the origin correctly, see
+ // https://github.com/jsdom/jsdom/issues/2745
+ window.dispatchEvent(
+ new MessageEvent("message", {
+ source: window,
+ origin: window.location.origin,
+ data: msg,
+ }),
+ );
- // The event is dispatched asynchronously, so we need to wait for it to be handled.
- await new Promise((resolve) => setTimeout(resolve, 0));
+ // The event is dispatched asynchronously, so we need to wait for it to be handled.
+ await new Promise((resolve) => setTimeout(resolve, 0));
+ });
};
it("renders results", async () => {
@@ -95,6 +97,7 @@ describe(ResultsApp.name, () => {
},
},
};
+
await postMessage(message);
expect(
@@ -117,4 +120,84 @@ describe(ResultsApp.name, () => {
screen.getByText("'x' is assigned a value but never used."),
).toBeInTheDocument();
});
+
+ it("renders results when switching between queries with different result set names", async () => {
+ render();
+
+ await postMessage({
+ t: "setState",
+ interpretation: undefined,
+ origResultsPaths: {
+ resultsPath: "/a/b/c/results.bqrs",
+ interpretedResultsPath: "/a/b/c/interpretedResults.sarif",
+ },
+ resultsPath: "/a/b/c/results.bqrs",
+ parsedResultSets: {
+ pageNumber: 0,
+ pageSize: 200,
+ numPages: 1,
+ numInterpretedPages: 0,
+ resultSet: {
+ schema: {
+ name: "#select",
+ rows: 1,
+ columns: [{ kind: "s" }],
+ pagination: { "step-size": 200, offsets: [13] },
+ },
+ rows: [["foobar1"]],
+ t: "RawResultSet",
+ },
+ resultSetNames: ["#select"],
+ },
+ sortedResultsMap: {},
+ database: {
+ name: "test-db",
+ databaseUri: "test-db-uri",
+ },
+ shouldKeepOldResultsWhileRendering: false,
+ metadata: {},
+ queryName: "empty.ql",
+ queryPath: "/a/b/c/empty.ql",
+ });
+
+ expect(screen.getByText("foobar1")).toBeInTheDocument();
+
+ await postMessage({
+ t: "setState",
+ interpretation: undefined,
+ origResultsPaths: {
+ resultsPath: "/a/b/c/results.bqrs",
+ interpretedResultsPath: "/a/b/c/interpretedResults.sarif",
+ },
+ resultsPath: "/a/b/c/results.bqrs",
+ parsedResultSets: {
+ pageNumber: 0,
+ pageSize: 200,
+ numPages: 1,
+ numInterpretedPages: 0,
+ resultSet: {
+ schema: {
+ name: "#Quick_evaluation_of_expression",
+ rows: 1,
+ columns: [{ name: "#expr_result", kind: "s" }],
+ pagination: { "step-size": 200, offsets: [49] },
+ },
+ rows: [["foobar2"]],
+ t: "RawResultSet",
+ },
+ resultSetNames: ["#Quick_evaluation_of_expression"],
+ },
+ sortedResultsMap: {},
+ database: {
+ name: "test-db",
+ databaseUri: "test-db-uri",
+ },
+ shouldKeepOldResultsWhileRendering: false,
+ metadata: {},
+ queryName: "Quick evaluation of empty.ql:1",
+ queryPath: "/a/b/c/empty.ql",
+ });
+
+ expect(screen.getByText("foobar2")).toBeInTheDocument();
+ });
});
diff --git a/extensions/ql-vscode/src/view/results/result-tables.tsx b/extensions/ql-vscode/src/view/results/result-tables.tsx
index fae317f79..24e60f1a0 100644
--- a/extensions/ql-vscode/src/view/results/result-tables.tsx
+++ b/extensions/ql-vscode/src/view/results/result-tables.tsx
@@ -78,6 +78,52 @@ function renderResultCountString(resultSet: ResultSet): JSX.Element {
);
}
+function getInterpretedTableName(interpretation: Interpretation): string {
+ return interpretation.data.t === "GraphInterpretationData"
+ ? GRAPH_TABLE_NAME
+ : ALERTS_TABLE_NAME;
+}
+
+function getResultSetNames(
+ interpretation: Interpretation | undefined,
+ parsedResultSets: ParsedResultSets,
+): string[] {
+ return interpretation
+ ? parsedResultSets.resultSetNames.concat([
+ getInterpretedTableName(interpretation),
+ ])
+ : parsedResultSets.resultSetNames;
+}
+
+function getResultSets(
+ rawResultSets: readonly ResultSet[],
+ interpretation: Interpretation | undefined,
+): ResultSet[] {
+ const resultSets: ResultSet[] =
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore 2783 Avoid compilation error for overwriting the t property
+ rawResultSets.map((rs) => ({ t: "RawResultSet", ...rs }));
+
+ if (interpretation !== undefined) {
+ const tableName = getInterpretedTableName(interpretation);
+ resultSets.push({
+ t: "InterpretedResultSet",
+ // FIXME: The values of version, columns, tupleCount are
+ // unused stubs because a InterpretedResultSet schema isn't used the
+ // same way as a RawResultSet. Probably should pull `name` field
+ // out.
+ schema: {
+ name: tableName,
+ rows: 1,
+ columns: [],
+ },
+ name: tableName,
+ interpretation,
+ });
+ }
+ return resultSets;
+}
+
/**
* Displays multiple `ResultTable` tables, where the table to be displayed is selected by a
* dropdown.
@@ -86,51 +132,13 @@ export class ResultTables extends React.Component<
ResultTablesProps,
ResultTablesState
> {
- private getResultSets(): ResultSet[] {
- const resultSets: ResultSet[] =
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore 2783
- this.props.rawResultSets.map((rs) => ({ t: "RawResultSet", ...rs }));
-
- if (this.props.interpretation !== undefined) {
- const tableName = this.getInterpretedTableName();
- resultSets.push({
- t: "InterpretedResultSet",
- // FIXME: The values of version, columns, tupleCount are
- // unused stubs because a InterpretedResultSet schema isn't used the
- // same way as a RawResultSet. Probably should pull `name` field
- // out.
- schema: {
- name: tableName,
- rows: 1,
- columns: [],
- },
- name: tableName,
- interpretation: this.props.interpretation,
- });
- }
- return resultSets;
- }
-
- private getInterpretedTableName(): string {
- return this.props.interpretation?.data.t === "GraphInterpretationData"
- ? GRAPH_TABLE_NAME
- : ALERTS_TABLE_NAME;
- }
-
- private getResultSetNames(): string[] {
- return this.props.interpretation
- ? this.props.parsedResultSets.resultSetNames.concat([
- this.getInterpretedTableName(),
- ])
- : this.props.parsedResultSets.resultSetNames;
- }
-
constructor(props: ResultTablesProps) {
super(props);
const selectedTable =
props.parsedResultSets.selectedTable ||
- getDefaultResultSet(this.getResultSets());
+ getDefaultResultSet(
+ getResultSets(props.rawResultSets, props.interpretation),
+ );
const selectedPage = `${props.parsedResultSets.pageNumber + 1}`;
this.state = {
selectedTable,
@@ -139,6 +147,36 @@ export class ResultTables extends React.Component<
};
}
+ componentDidUpdate(
+ prevProps: Readonly,
+ prevState: Readonly,
+ snapshot?: any,
+ ) {
+ const resultSetExists =
+ this.props.parsedResultSets.resultSetNames.some(
+ (v) => this.state.selectedTable === v,
+ ) ||
+ getResultSets(this.props.rawResultSets, this.props.interpretation).some(
+ (v) => this.state.selectedTable === v.schema.name,
+ );
+
+ // If the selected result set does not exist, select the default result set.
+ if (!resultSetExists) {
+ this.setState((state, props) => {
+ const selectedTable =
+ props.parsedResultSets.selectedTable ||
+ getDefaultResultSet(
+ getResultSets(props.rawResultSets, props.interpretation),
+ );
+
+ return {
+ selectedTable,
+ selectedPage: `${props.parsedResultSets.pageNumber + 1}`,
+ };
+ });
+ }
+ }
+
untoggleProblemsView() {
this.setState({
problemsViewSelected: false,
@@ -303,8 +341,14 @@ export class ResultTables extends React.Component<
render(): React.ReactNode {
const { selectedTable } = this.state;
- const resultSets = this.getResultSets();
- const resultSetNames = this.getResultSetNames();
+ const resultSets = getResultSets(
+ this.props.rawResultSets,
+ this.props.interpretation,
+ );
+ const resultSetNames = getResultSetNames(
+ this.props.interpretation,
+ this.props.parsedResultSets,
+ );
const resultSet = resultSets.find(
(resultSet) => resultSet.schema.name === selectedTable,