Use new raw result types in result and compare views

This commit is contained in:
Koen Vlaswinkel
2023-11-27 12:34:50 +01:00
parent cb707aea50
commit fc2e6d0432
17 changed files with 145 additions and 102 deletions

View File

@@ -1,9 +1,6 @@
import * as sarif from "sarif";
import {
RawResultSet,
ResultRow,
ResultSetSchema,
Column,
ResolvableLocationValue,
} from "../common/bqrs-cli-types";
import {
@@ -25,7 +22,12 @@ import {
} from "../model-editor/shared/view-state";
import { Mode } from "../model-editor/shared/mode";
import { QueryLanguage } from "./query-language";
import { UrlValueResolvable } from "./raw-result-types";
import {
Column,
RawResultSet,
Row,
UrlValueResolvable,
} from "./raw-result-types";
/**
* This module contains types and code that are shared between
@@ -36,7 +38,11 @@ export const SELECT_TABLE_NAME = "#select";
export const ALERTS_TABLE_NAME = "alerts";
export const GRAPH_TABLE_NAME = "graph";
export type RawTableResultSet = { t: "RawResultSet" } & RawResultSet;
export type RawTableResultSet = {
t: "RawResultSet";
resultSet: RawResultSet;
};
export type InterpretedResultSet<T> = {
t: "InterpretedResultSet";
readonly schema: ResultSetSchema;
@@ -372,8 +378,8 @@ export interface SetComparisonsMessage {
* (or added) in the comparison.
*/
export type QueryCompareResult = {
from: ResultRow[];
to: ResultRow[];
from: Row[];
to: Row[];
};
/**

View File

@@ -1,6 +1,6 @@
import * as Sarif from "sarif";
import type { HighlightedRegion } from "../variant-analysis/shared/analysis-result";
import { ResolvableLocationValue } from "../common/bqrs-cli-types";
import { UrlValueResolvable } from "./raw-result-types";
import { isEmptyPath } from "./bqrs-utils";
export interface SarifLink {
@@ -16,7 +16,7 @@ interface NoLocation {
}
type ParsedSarifLocation =
| (ResolvableLocationValue & {
| (UrlValueResolvable & {
userVisibleFile: string;
})
// Resolvable locations have a `uri` field, but it will sometimes include
@@ -137,6 +137,7 @@ export function parseSarifLocation(
// If the region property is absent, the physicalLocation object refers to the entire file.
// Source: https://docs.oasis-open.org/sarif/sarif/v2.1.0/cs01/sarif-v2.1.0-cs01.html#_Toc16012638.
return {
type: "wholeFileLocation",
uri: effectiveLocation,
userVisibleFile,
} as ParsedSarifLocation;
@@ -144,6 +145,7 @@ export function parseSarifLocation(
const region = parseSarifRegion(physicalLocation.region);
return {
type: "lineColumnLocation",
uri: effectiveLocation,
userVisibleFile,
...region,

View File

@@ -10,11 +10,7 @@ import { extLogger } from "../common/logging/vscode";
import { CodeQLCliServer } from "../codeql-cli/cli";
import { DatabaseManager } from "../databases/local-databases";
import { jumpToLocation } from "../databases/local-databases/locations";
import {
transformBqrsResultSet,
RawResultSet,
BQRSInfo,
} from "../common/bqrs-cli-types";
import { BQRSInfo } from "../common/bqrs-cli-types";
import resultsDiff from "./resultsDiff";
import { CompletedLocalQueryInfo } from "../query-results";
import { assertNever, getErrorMessage } from "../common/helpers-pure";
@@ -26,6 +22,8 @@ import {
import { telemetryListener } from "../common/vscode/telemetry";
import { redactableError } from "../common/errors";
import { App } from "../common/app";
import { bqrsToResultSet } from "../common/bqrs-result";
import { RawResultSet } from "../common/raw-result-types";
interface ComparePair {
from: CompletedLocalQueryInfo;
@@ -93,7 +91,7 @@ export class CompareView extends AbstractWebview<
time: to.startTime,
},
},
columns: fromResultSet.schema.columns,
columns: fromResultSet.columns,
commonResultSetNames,
currentResultSetName,
rows,
@@ -245,7 +243,7 @@ export class CompareView extends AbstractWebview<
throw new Error(`Schema ${resultSetName} not found.`);
}
const chunk = await this.cliServer.bqrsDecode(resultsPath, resultSetName);
return transformBqrsResultSet(schema, chunk);
return bqrsToResultSet(schema, chunk);
}
private compareResults(

View File

@@ -1,5 +1,5 @@
import { RawResultSet } from "../common/bqrs-cli-types";
import { QueryCompareResult } from "../common/interface-types";
import { RawResultSet } from "../common/raw-result-types";
/**
* Compare the rows of two queries. Use deep equality to determine if
@@ -23,7 +23,7 @@ export default function resultsDiff(
fromResults: RawResultSet,
toResults: RawResultSet,
): QueryCompareResult {
if (fromResults.schema.columns.length !== toResults.schema.columns.length) {
if (fromResults.columns.length !== toResults.columns.length) {
throw new Error("CodeQL Compare: Columns do not match.");
}

View File

@@ -60,11 +60,7 @@ import {
shownLocationLineDecoration,
jumpToLocation,
} from "../databases/local-databases/locations";
import {
RawResultSet,
transformBqrsResultSet,
ResultSetSchema,
} from "../common/bqrs-cli-types";
import { bqrsToResultSet } from "../common/bqrs-result";
import {
AbstractWebview,
WebviewPanelConfig,
@@ -76,6 +72,8 @@ import { redactableError } from "../common/errors";
import { ResultsViewCommands } from "../common/commands";
import { App } from "../common/app";
import { Disposable } from "../common/disposable-object";
import { RawResultSet } from "../common/raw-result-types";
import { ResultSetSchema } from "../common/bqrs-cli-types";
/**
* results-view.ts
@@ -136,7 +134,7 @@ function numPagesOfResultSet(
const n =
interpretation?.data.t === "GraphInterpretationData"
? interpretation.data.dot.length
: resultSet.schema.rows;
: resultSet.totalRowCount;
return Math.ceil(n / pageSize);
}
@@ -524,16 +522,16 @@ export class ResultsView extends AbstractWebview<
offset: schema.pagination?.offsets[0],
pageSize,
});
const resultSet = transformBqrsResultSet(schema, chunk);
const resultSet = bqrsToResultSet(schema, chunk);
fullQuery.completedQuery.setResultCount(
interpretationPage?.numTotalResults || resultSet.schema.rows,
interpretationPage?.numTotalResults || resultSet.totalRowCount,
);
const parsedResultSets: ParsedResultSets = {
pageNumber: 0,
pageSize,
numPages: numPagesOfResultSet(resultSet, this._interpretation),
numInterpretedPages: numInterpretedPages(this._interpretation),
resultSet: { ...resultSet, t: "RawResultSet" },
resultSet: { t: "RawResultSet", resultSet },
selectedTable: undefined,
resultSetNames,
};
@@ -668,12 +666,12 @@ export class ResultsView extends AbstractWebview<
pageSize,
},
);
const resultSet = transformBqrsResultSet(schema, chunk);
const resultSet = bqrsToResultSet(schema, chunk);
const parsedResultSets: ParsedResultSets = {
pageNumber,
pageSize,
resultSet: { t: "RawResultSet", ...resultSet },
resultSet: { t: "RawResultSet", resultSet },
numPages: numPagesOfResultSet(resultSet),
numInterpretedPages: numInterpretedPages(this._interpretation),
selectedTable,

View File

@@ -5,6 +5,7 @@ import { Meta, StoryFn } from "@storybook/react";
import CompareTableComponent from "../../view/compare/CompareTable";
import "../../view/results/resultsView.css";
import { ColumnKind } from "../../common/raw-result-types";
export default {
title: "Compare/Compare Table",
@@ -32,8 +33,8 @@ CompareTable.args = {
},
},
columns: [
{ name: "a", kind: "e" },
{ name: "b", kind: "e" },
{ name: "a", kind: ColumnKind.Entity },
{ name: "b", kind: ColumnKind.Entity },
],
commonResultSetNames: ["edges", "nodes", "subpaths", "#select"],
currentResultSetName: "edges",
@@ -42,23 +43,31 @@ CompareTable.args = {
to: [
[
{
label: "url : String",
url: {
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
startLine: 22,
startColumn: 27,
endLine: 22,
endColumn: 57,
type: "entity",
value: {
label: "url : String",
url: {
type: "lineColumnLocation",
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
startLine: 22,
startColumn: 27,
endLine: 22,
endColumn: 57,
},
},
},
{
label: "url",
url: {
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
startLine: 23,
startColumn: 33,
endLine: 23,
endColumn: 35,
type: "entity",
value: {
label: "url",
url: {
type: "lineColumnLocation",
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
startLine: 23,
startColumn: 33,
endLine: 23,
endColumn: 35,
},
},
},
],

View File

@@ -3,12 +3,12 @@ import * as React from "react";
import { SetComparisonsMessage } from "../../common/interface-types";
import RawTableHeader from "../results/RawTableHeader";
import { className } from "../results/result-table-utils";
import { ResultRow } from "../../common/bqrs-cli-types";
import RawTableRow from "../results/RawTableRow";
import { vscode } from "../vscode-api";
import { sendTelemetry } from "../common/telemetry";
import TextButton from "../common/TextButton";
import { styled } from "styled-components";
import { Row } from "../../common/raw-result-types";
interface Props {
comparison: SetComparisonsMessage;
@@ -31,7 +31,7 @@ export default function CompareTable(props: Props) {
});
}
function createRows(rows: ResultRow[], databaseUri: string) {
function createRows(rows: Row[], databaseUri: string) {
return (
<tbody>
{rows.map((row, rowIndex) => (

View File

@@ -6,21 +6,22 @@ import {
RawResultsSortState,
NavigateMsg,
NavigationDirection,
RawTableResultSet,
} from "../../common/interface-types";
import RawTableHeader from "./RawTableHeader";
import RawTableRow from "./RawTableRow";
import { ResultRow } from "../../common/bqrs-cli-types";
import { onNavigation } from "./ResultsApp";
import { tryGetResolvableLocation } from "../../common/bqrs-utils";
import { sendTelemetry } from "../common/telemetry";
import { assertNever } from "../../common/helpers-pure";
import { EmptyQueryResultsMessage } from "./EmptyQueryResultsMessage";
import { useScrollIntoView } from "./useScrollIntoView";
import {
isUrlValueResolvable,
RawResultSet,
} from "../../common/raw-result-types";
type RawTableProps = {
databaseUri: string;
resultSet: RawTableResultSet;
resultSet: RawResultSet;
sortState?: RawResultsSortState;
offset: number;
};
@@ -67,11 +68,12 @@ export function RawTable({
return prevSelectedItem;
}
const cellData = rowData[nextColumn];
if (cellData != null && typeof cellData === "object") {
const location = tryGetResolvableLocation(cellData.url);
if (location !== undefined) {
jumpToLocation(location, databaseUri);
}
if (
cellData?.type === "entity" &&
cellData.value.url &&
isUrlValueResolvable(cellData.value.url)
) {
jumpToLocation(cellData.value.url, databaseUri);
}
return { row: nextRow, column: nextColumn };
});
@@ -126,7 +128,7 @@ export function RawTable({
return <EmptyQueryResultsMessage />;
}
const tableRows = dataRows.map((row: ResultRow, rowIndex: number) => (
const tableRows = dataRows.map((row, rowIndex) => (
<RawTableRow
key={rowIndex}
rowIndex={rowIndex + offset}
@@ -159,8 +161,8 @@ export function RawTable({
return (
<table className={className}>
<RawTableHeader
columns={resultSet.schema.columns}
schemaName={resultSet.schema.name}
columns={resultSet.columns}
schemaName={resultSet.name}
sortState={sortState}
/>
<tbody>{tableRows}</tbody>

View File

@@ -6,7 +6,7 @@ import {
SortDirection,
} from "../../common/interface-types";
import { nextSortDirection } from "./result-table-utils";
import { Column } from "../../common/bqrs-cli-types";
import { Column } from "../../common/raw-result-types";
interface Props {
readonly columns: readonly Column[];

View File

@@ -1,11 +1,11 @@
import * as React from "react";
import { ResultRow } from "../../common/bqrs-cli-types";
import { selectedRowClassName, zebraStripe } from "./result-table-utils";
import RawTableValue from "./RawTableValue";
import { Row } from "../../common/raw-result-types";
interface Props {
rowIndex: number;
row: ResultRow;
row: Row;
databaseUri: string;
className?: string;
selectedColumn?: number;

View File

@@ -1,8 +1,9 @@
import * as React from "react";
import { Location } from "./locations/Location";
import { CellValue } from "../../common/bqrs-cli-types";
import { RawNumberValue } from "../common/RawNumberValue";
import { CellValue } from "../../common/raw-result-types";
import { assertNever } from "../../common/helpers-pure";
interface Props {
value: CellValue;
@@ -15,21 +16,23 @@ export default function RawTableValue({
databaseUri,
onSelected,
}: Props): JSX.Element {
switch (typeof value) {
switch (value.type) {
case "boolean":
return <span>{value.toString()}</span>;
return <span>{value.value.toString()}</span>;
case "number":
return <RawNumberValue value={value} />;
return <RawNumberValue value={value.value} />;
case "string":
return <Location label={value.toString()} />;
default:
return <Location label={value.value} />;
case "entity":
return (
<Location
loc={value.url}
label={value.label}
loc={value.value.url}
label={value.value.label}
databaseUri={databaseUri}
onClick={onSelected}
/>
);
default:
assertNever(value);
}
}

View File

@@ -9,7 +9,7 @@ interface Props {
function getResultCount(resultSet: ResultSet): number {
switch (resultSet.t) {
case "RawResultSet":
return resultSet.schema.rows;
return resultSet.resultSet.totalRowCount;
case "InterpretedResultSet":
return resultSet.interpretation.numTotalResults;
}

View File

@@ -8,7 +8,7 @@ export function ResultTable(props: ResultTableProps) {
const { resultSet } = props;
switch (resultSet.t) {
case "RawResultSet":
return <RawTable {...props} resultSet={resultSet} />;
return <RawTable {...props} resultSet={resultSet.resultSet} />;
case "InterpretedResultSet": {
const data = resultSet.interpretation.data;
switch (data.t) {

View File

@@ -22,6 +22,7 @@ import { ResultTablesHeader } from "./ResultTablesHeader";
import { useCallback, useEffect, useMemo, useState } from "react";
import { ResultCount } from "./ResultCount";
import { ProblemsViewCheckbox } from "./ProblemsViewCheckbox";
import { assertNever } from "../../common/helpers-pure";
/**
* Properties for the `ResultTables` component.
@@ -149,7 +150,7 @@ export function ResultTables(props: ResultTablesProps) {
const resultSetExists =
parsedResultSets.resultSetNames.some((v) => selectedTable === v) ||
getResultSets(rawResultSets, interpretation).some(
(v) => selectedTable === v.schema.name,
(v) => selectedTable === getResultSetName(v),
);
// If the selected result set does not exist, select the default result set.
@@ -207,11 +208,14 @@ export function ResultTables(props: ResultTablesProps) {
const resultSet = useMemo(
() =>
resultSets.find((resultSet) => resultSet.schema.name === selectedTable),
resultSets.find(
(resultSet) => selectedTable === getResultSetName(resultSet),
),
[resultSets, selectedTable],
);
const nonemptyRawResults = resultSets.some(
(resultSet) => resultSet.t === "RawResultSet" && resultSet.rows.length > 0,
(resultSet) =>
resultSet.t === "RawResultSet" && resultSet.resultSet.rows.length > 0,
);
const resultSetOptions = resultSetNames.map((name) => (
@@ -219,6 +223,9 @@ export function ResultTables(props: ResultTablesProps) {
{name}
</option>
));
const resultSetName = resultSet ? getResultSetName(resultSet) : undefined;
return (
<div>
<ResultTablesHeader {...props} selectedTable={selectedTable} />
@@ -239,13 +246,13 @@ export function ResultTables(props: ResultTablesProps) {
</span>
) : null}
</div>
{resultSet && (
{resultSet && resultSetName && (
<ResultTable
key={resultSet.schema.name}
key={resultSetName}
resultSet={resultSet}
databaseUri={database.databaseUri}
resultsPath={resultsPath}
sortState={sortStates.get(resultSet.schema.name)}
sortState={sortStates.get(resultSetName)}
nonemptyRawResults={nonemptyRawResults}
showRawResults={() => {
setSelectedTable(SELECT_TABLE_NAME);
@@ -260,6 +267,17 @@ export function ResultTables(props: ResultTablesProps) {
function getDefaultResultSet(resultSets: readonly ResultSet[]): string {
return getDefaultResultSetName(
resultSets.map((resultSet) => resultSet.schema.name),
resultSets.map((resultSet) => getResultSetName(resultSet)),
);
}
function getResultSetName(resultSet: ResultSet): string {
switch (resultSet.t) {
case "RawResultSet":
return resultSet.resultSet.name;
case "InterpretedResultSet":
return resultSet.schema.name;
default:
assertNever(resultSet);
}
}

View File

@@ -10,6 +10,7 @@ import * as fs from "fs-extra";
import { resolve } from "path";
import { ColumnKindCode } from "../../../common/bqrs-cli-types";
import { postMessage } from "../../common/post-message";
import { ColumnKind } from "../../../common/raw-result-types";
const exampleSarif = fs.readJSONSync(
resolve(__dirname, "../../../../test/data/sarif/validSarif.sarif"),
@@ -120,13 +121,19 @@ describe(ResultsApp.name, () => {
numPages: 1,
numInterpretedPages: 0,
resultSet: {
schema: {
resultSet: {
name: "#select",
rows: 1,
columns: [{ kind: "s" }],
pagination: { "step-size": 200, offsets: [13] },
totalRowCount: 1,
columns: [{ kind: ColumnKind.String }],
rows: [
[
{
type: "string",
value: "foobar1",
},
],
],
},
rows: [["foobar1"]],
t: "RawResultSet",
},
resultSetNames: ["#select"],
@@ -158,13 +165,19 @@ describe(ResultsApp.name, () => {
numPages: 1,
numInterpretedPages: 0,
resultSet: {
schema: {
resultSet: {
name: "#Quick_evaluation_of_expression",
rows: 1,
columns: [{ name: "#expr_result", kind: "s" }],
pagination: { "step-size": 200, offsets: [49] },
totalRowCount: 1,
columns: [{ name: "#expr_result", kind: ColumnKind.String }],
rows: [
[
{
type: "string",
value: "foobar2",
},
],
],
},
rows: [["foobar2"]],
t: "RawResultSet",
},
resultSetNames: ["#Quick_evaluation_of_expression"],

View File

@@ -1,11 +1,7 @@
import * as React from "react";
import { useMemo } from "react";
import { UrlValue } from "../../../common/bqrs-cli-types";
import {
isStringLoc,
tryGetResolvableLocation,
} from "../../../common/bqrs-utils";
import { UrlValue } from "../../../common/raw-result-types";
import { convertNonPrintableChars } from "../../../common/text-utils";
import { NonClickableLocation } from "./NonClickableLocation";
import { ClickableLocation } from "./ClickableLocation";
@@ -28,24 +24,23 @@ export function Location({
title,
onClick,
}: Props): JSX.Element {
const resolvableLoc = useMemo(() => tryGetResolvableLocation(loc), [loc]);
const displayLabel = useMemo(() => convertNonPrintableChars(label), [label]);
if (loc === undefined) {
return <NonClickableLocation msg={displayLabel} />;
}
if (isStringLoc(loc)) {
return <a href={loc}>{loc}</a>;
if (loc.type === "string") {
return <a href={loc.value}>{loc.value}</a>;
}
if (databaseUri === undefined || resolvableLoc === undefined) {
if (databaseUri === undefined) {
return <NonClickableLocation msg={displayLabel} locationHint={title} />;
}
return (
<ClickableLocation
loc={resolvableLoc}
loc={loc}
label={displayLabel}
databaseUri={databaseUri}
onClick={onClick}

View File

@@ -1,6 +1,5 @@
import * as React from "react";
import * as Sarif from "sarif";
import { isLineColumnLoc, isWholeFileLoc } from "../../../common/bqrs-utils";
import { parseSarifLocation } from "../../../common/sarif-utils";
import { basename } from "../../../common/path";
import { useMemo } from "react";
@@ -35,7 +34,7 @@ export function SarifLocation({
return <Location label={text || "[no location]"} title={parsedLoc?.hint} />;
}
if (isWholeFileLoc(parsedLoc)) {
if (parsedLoc.type === "wholeFileLocation") {
return (
<Location
loc={parsedLoc}
@@ -47,7 +46,7 @@ export function SarifLocation({
);
}
if (isLineColumnLoc(parsedLoc)) {
if (parsedLoc.type === "lineColumnLocation") {
return (
<Location
loc={parsedLoc}