feature: display length of shortest path in local results UI
This commit is contained in:
Родитель
96c33a14b8
Коммит
dc6d5c14ea
|
@ -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" },
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
Загрузка…
Ссылка в новой задаче