Merge pull request #2279 from github/koesie10/fix-result-view-selected
Fix empty result view when switching between queries
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import * as React from "react";
|
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 { ResultsApp } from "../results";
|
||||||
import {
|
import {
|
||||||
Interpretation,
|
Interpretation,
|
||||||
@@ -20,18 +20,20 @@ const exampleSarif = fs.readJSONSync(
|
|||||||
describe(ResultsApp.name, () => {
|
describe(ResultsApp.name, () => {
|
||||||
const render = () => reactRender(<ResultsApp />);
|
const render = () => reactRender(<ResultsApp />);
|
||||||
const postMessage = async (msg: IntoResultsViewMsg) => {
|
const postMessage = async (msg: IntoResultsViewMsg) => {
|
||||||
// window.postMessage doesn't set the origin correctly, see
|
await act(async () => {
|
||||||
// https://github.com/jsdom/jsdom/issues/2745
|
// window.postMessage doesn't set the origin correctly, see
|
||||||
window.dispatchEvent(
|
// https://github.com/jsdom/jsdom/issues/2745
|
||||||
new MessageEvent("message", {
|
window.dispatchEvent(
|
||||||
source: window,
|
new MessageEvent("message", {
|
||||||
origin: window.location.origin,
|
source: window,
|
||||||
data: msg,
|
origin: window.location.origin,
|
||||||
}),
|
data: msg,
|
||||||
);
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
// The event is dispatched asynchronously, so we need to wait for it to be handled.
|
// The event is dispatched asynchronously, so we need to wait for it to be handled.
|
||||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
it("renders results", async () => {
|
it("renders results", async () => {
|
||||||
@@ -95,6 +97,7 @@ describe(ResultsApp.name, () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
await postMessage(message);
|
await postMessage(message);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
@@ -117,4 +120,84 @@ describe(ResultsApp.name, () => {
|
|||||||
screen.getByText("'x' is assigned a value but never used."),
|
screen.getByText("'x' is assigned a value but never used."),
|
||||||
).toBeInTheDocument();
|
).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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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
|
* Displays multiple `ResultTable` tables, where the table to be displayed is selected by a
|
||||||
* dropdown.
|
* dropdown.
|
||||||
@@ -86,51 +132,13 @@ export class ResultTables extends React.Component<
|
|||||||
ResultTablesProps,
|
ResultTablesProps,
|
||||||
ResultTablesState
|
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) {
|
constructor(props: ResultTablesProps) {
|
||||||
super(props);
|
super(props);
|
||||||
const selectedTable =
|
const selectedTable =
|
||||||
props.parsedResultSets.selectedTable ||
|
props.parsedResultSets.selectedTable ||
|
||||||
getDefaultResultSet(this.getResultSets());
|
getDefaultResultSet(
|
||||||
|
getResultSets(props.rawResultSets, props.interpretation),
|
||||||
|
);
|
||||||
const selectedPage = `${props.parsedResultSets.pageNumber + 1}`;
|
const selectedPage = `${props.parsedResultSets.pageNumber + 1}`;
|
||||||
this.state = {
|
this.state = {
|
||||||
selectedTable,
|
selectedTable,
|
||||||
@@ -139,6 +147,36 @@ export class ResultTables extends React.Component<
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(
|
||||||
|
prevProps: Readonly<ResultTablesProps>,
|
||||||
|
prevState: Readonly<ResultTablesState>,
|
||||||
|
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() {
|
untoggleProblemsView() {
|
||||||
this.setState({
|
this.setState({
|
||||||
problemsViewSelected: false,
|
problemsViewSelected: false,
|
||||||
@@ -303,8 +341,14 @@ export class ResultTables extends React.Component<
|
|||||||
|
|
||||||
render(): React.ReactNode {
|
render(): React.ReactNode {
|
||||||
const { selectedTable } = this.state;
|
const { selectedTable } = this.state;
|
||||||
const resultSets = this.getResultSets();
|
const resultSets = getResultSets(
|
||||||
const resultSetNames = this.getResultSetNames();
|
this.props.rawResultSets,
|
||||||
|
this.props.interpretation,
|
||||||
|
);
|
||||||
|
const resultSetNames = getResultSetNames(
|
||||||
|
this.props.interpretation,
|
||||||
|
this.props.parsedResultSets,
|
||||||
|
);
|
||||||
|
|
||||||
const resultSet = resultSets.find(
|
const resultSet = resultSets.find(
|
||||||
(resultSet) => resultSet.schema.name === selectedTable,
|
(resultSet) => resultSet.schema.name === selectedTable,
|
||||||
|
|||||||
Reference in New Issue
Block a user