feature: display length of shortest path in local results UI

This commit is contained in:
Peter Stöckli 2024-08-12 15:20:05 +02:00
Родитель 96c33a14b8
Коммит dc6d5c14ea
6 изменённых файлов: 191 добавлений и 4 удалений

Просмотреть файл

@ -2,6 +2,8 @@
## [UNRELEASED]
- Update results view to display the length of the shortest path for path queries.
## 1.14.0 - 7 August 2024
- Add Python support to the CodeQL Model Editor. [#3676](https://github.com/github/vscode-codeql/pull/3676)

Просмотреть файл

@ -11,8 +11,9 @@ import { AlertTableDropdownIndicatorCell } from "./AlertTableDropdownIndicatorCe
import { useCallback, useMemo } from "react";
import { VerticalRule } from "../common/VerticalRule";
import type { UserSettings } from "../../common/interface-types";
import { pluralize } from "../../common/word";
interface Props {
export interface Props {
path: ThreadFlow;
pathIndex: number;
resultIndex: number;
@ -65,7 +66,7 @@ export function AlertTablePathRow(props: Props) {
onClick={handleDropdownClick}
/>
<td className="vscode-codeql__text-center" colSpan={4}>
Path
{`Path (${pluralize(path.locations.length, "step", "steps")})`}
</td>
</tr>
{currentPathExpanded &&

Просмотреть файл

@ -13,8 +13,9 @@ import { SarifLocation } from "./locations/SarifLocation";
import { SarifMessageWithLocations } from "./locations/SarifMessageWithLocations";
import { AlertTablePathRow } from "./AlertTablePathRow";
import type { UserSettings } from "../../common/interface-types";
import { VSCodeBadge } from "@vscode/webview-ui-toolkit/react";
interface Props {
export interface Props {
result: Result;
resultIndex: number;
expanded: Set<string>;
@ -83,6 +84,11 @@ export function AlertTableResultRow(props: Props) {
/>
);
const allPaths = getAllPaths(result);
const shortestPath = Math.min(
...allPaths.map((path) => path.locations.length),
);
const currentResultExpanded = expanded.has(keyToString(resultKey));
return (
<>
@ -102,6 +108,9 @@ export function AlertTableResultRow(props: Props) {
onClick={handleDropdownClick}
/>
<td className="vscode-codeql__icon-cell">{listUnordered}</td>
<td className="vscode-codeql__icon-cell">
<VSCodeBadge title="Shortest path">{shortestPath}</VSCodeBadge>
</td>
<td colSpan={3}>{msg}</td>
</>
)}
@ -118,7 +127,7 @@ export function AlertTableResultRow(props: Props) {
</tr>
{currentResultExpanded &&
result.codeFlows &&
getAllPaths(result).map((path, pathIndex) => (
allPaths.map((path, pathIndex) => (
<AlertTablePathRow
key={`${resultIndex}-${pathIndex}`}
{...props}

Просмотреть файл

@ -0,0 +1,38 @@
import { render as reactRender, screen } from "@testing-library/react";
import type { Props } from "../AlertTablePathRow";
import { AlertTablePathRow } from "../AlertTablePathRow";
import { createMockResults } from "../../../../test/factories/results/mockresults";
describe(AlertTablePathRow.name, () => {
const render = (props?: Props) => {
const mockRef = { current: null } as React.RefObject<HTMLTableRowElement>;
const results = createMockResults();
const threadFlow = results[0]?.codeFlows?.[0]?.threadFlows?.[0];
if (!threadFlow) {
throw new Error("ThreadFlow is undefined");
}
reactRender(
<AlertTablePathRow
resultIndex={1}
selectedItem={undefined}
selectedItemRef={mockRef}
path={threadFlow}
pathIndex={0}
currentPathExpanded={true}
databaseUri={"dbUri"}
sourceLocationPrefix="src"
userSettings={{ shouldShowProvenance: false }}
updateSelectionCallback={jest.fn()}
toggleExpanded={jest.fn()}
{...props}
/>,
);
};
it("renders number of steps", () => {
render();
expect(screen.getByText("Path (3 steps)")).toBeInTheDocument();
});
});

Просмотреть файл

@ -0,0 +1,33 @@
import { render as reactRender, screen } from "@testing-library/react";
import { AlertTableResultRow } from "../AlertTableResultRow";
import type { Props } from "../AlertTablePathRow";
import { createMockResults } from "../../../../test/factories/results/mockresults";
describe(AlertTableResultRow.name, () => {
const render = (props?: Props) => {
const mockRef = { current: null } as React.RefObject<HTMLTableRowElement>;
const results = createMockResults();
reactRender(
<AlertTableResultRow
result={results[0]}
expanded={new Set()}
resultIndex={1}
selectedItem={undefined}
selectedItemRef={mockRef}
databaseUri={"dbUri"}
sourceLocationPrefix="src"
userSettings={{ shouldShowProvenance: false }}
updateSelectionCallback={jest.fn()}
toggleExpanded={jest.fn()}
{...props}
/>,
);
};
it("renders shortest path badge", () => {
render();
expect(screen.getByTitle("Shortest path")).toHaveTextContent("3");
});
});

Просмотреть файл

@ -0,0 +1,104 @@
import type { Result } from "sarif";
export function createMockResults(): Result[] {
return [
{
ruleId: "java/sql-injection",
ruleIndex: 0,
rule: { id: "java/sql-injection", index: 0 },
message: {
text: "This query depends on a [user-provided value](1).",
},
locations: [
{
physicalLocation: {
artifactLocation: {
uri: "src/main/java/org/example/HelloController.java",
uriBaseId: "%SRCROOT%",
index: 0,
},
region: { startLine: 15, startColumn: 29, endColumn: 56 },
},
},
],
partialFingerprints: {
primaryLocationLineHash: "87e2d3cc5b365094:1",
primaryLocationStartColumnFingerprint: "16",
},
codeFlows: [
{
threadFlows: [
{
locations: [
{
location: {
physicalLocation: {
artifactLocation: {
uri: "src/main/java/org/example/HelloController.java",
uriBaseId: "%SRCROOT%",
index: 0,
},
region: {
startLine: 13,
startColumn: 25,
endColumn: 54,
},
},
message: { text: "id : String" },
},
},
{
location: {
physicalLocation: {
artifactLocation: {
uri: "file:/",
index: 5,
},
region: {
startLine: 13,
startColumn: 25,
endColumn: 54,
},
},
message: { text: "id : String" },
},
},
{
location: {
physicalLocation: {
artifactLocation: {
uri: "src/main/java/org/example/HelloController.java",
uriBaseId: "%SRCROOT%",
index: 0,
},
region: {
startLine: 15,
startColumn: 29,
endColumn: 56,
},
},
message: { text: "... + ..." },
},
},
],
},
],
},
],
relatedLocations: [
{
id: 1,
physicalLocation: {
artifactLocation: {
uri: "src/main/java/org/example/HelloController.java",
uriBaseId: "%SRCROOT%",
index: 0,
},
region: { startLine: 13, startColumn: 25, endColumn: 54 },
},
message: { text: "user-provided value" },
},
],
},
];
}