Display path provenance information in results view
This commit is contained in:
@@ -147,6 +147,17 @@ interface SetStateMsg {
|
|||||||
parsedResultSets: ParsedResultSets;
|
parsedResultSets: ParsedResultSets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UserSettings {
|
||||||
|
/** Whether to display links to the dataflow models that generated particular nodes in a flow path. */
|
||||||
|
shouldShowProvenance: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Message indicating that the user's configuration settings have changed. */
|
||||||
|
interface SetUserSettingsMsg {
|
||||||
|
t: "setUserSettings";
|
||||||
|
userSettings: UserSettings;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Message indicating that the results view should display interpreted
|
* Message indicating that the results view should display interpreted
|
||||||
* results.
|
* results.
|
||||||
@@ -191,6 +202,7 @@ interface UntoggleShowProblemsMsg {
|
|||||||
export type IntoResultsViewMsg =
|
export type IntoResultsViewMsg =
|
||||||
| ResultsUpdatingMsg
|
| ResultsUpdatingMsg
|
||||||
| SetStateMsg
|
| SetStateMsg
|
||||||
|
| SetUserSettingsMsg
|
||||||
| ShowInterpretedPageMsg
|
| ShowInterpretedPageMsg
|
||||||
| NavigateMsg
|
| NavigateMsg
|
||||||
| UntoggleShowProblemsMsg;
|
| UntoggleShowProblemsMsg;
|
||||||
@@ -208,13 +220,15 @@ export type FromResultsViewMsg =
|
|||||||
| OpenFileMsg;
|
| OpenFileMsg;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Message from the results view to open a database source
|
* Message from the results view to open a source
|
||||||
* file at the provided location.
|
* file at the provided location.
|
||||||
*/
|
*/
|
||||||
interface ViewSourceFileMsg {
|
interface ViewSourceFileMsg {
|
||||||
t: "viewSourceFile";
|
t: "viewSourceFile";
|
||||||
loc: UrlValueResolvable;
|
loc: UrlValueResolvable;
|
||||||
databaseUri: string;
|
/** URI of the database whose source archive contains the file, or `undefined` to open a file from
|
||||||
|
* the local disk. The latter case is used for opening links to data extension model files. */
|
||||||
|
databaseUri: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -341,7 +355,8 @@ interface ChangeCompareMessage {
|
|||||||
|
|
||||||
export type ToCompareViewMessage =
|
export type ToCompareViewMessage =
|
||||||
| SetComparisonQueryInfoMessage
|
| SetComparisonQueryInfoMessage
|
||||||
| SetComparisonsMessage;
|
| SetComparisonsMessage
|
||||||
|
| SetUserSettingsMsg;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Message to the compare view that sets the metadata of the compared queries.
|
* Message to the compare view that sets the metadata of the compared queries.
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
import type { Log, Tool } from "sarif";
|
import type { Log } from "sarif";
|
||||||
import { createReadStream } from "fs-extra";
|
import { createReadStream } from "fs-extra";
|
||||||
import { connectTo } from "stream-json/Assembler";
|
import { connectTo } from "stream-json/Assembler";
|
||||||
import { getErrorMessage } from "./helpers-pure";
|
import { getErrorMessage } from "./helpers-pure";
|
||||||
import { withParser } from "stream-json/filters/Pick";
|
import { withParser } from "stream-json/filters/Ignore";
|
||||||
|
|
||||||
const DUMMY_TOOL: Tool = { driver: { name: "" } };
|
|
||||||
|
|
||||||
export async function sarifParser(
|
export async function sarifParser(
|
||||||
interpretedResultsPath: string,
|
interpretedResultsPath: string,
|
||||||
): Promise<Log> {
|
): Promise<Log> {
|
||||||
try {
|
try {
|
||||||
// Parse the SARIF file into token streams, filtering out only the results array.
|
// Parse the SARIF file into token streams, filtering out some of the larger subtrees that we
|
||||||
|
// don't need.
|
||||||
const pipeline = createReadStream(interpretedResultsPath).pipe(
|
const pipeline = createReadStream(interpretedResultsPath).pipe(
|
||||||
withParser({ filter: "runs.0.results" }),
|
withParser({
|
||||||
|
// We don't need to run's `artifacts` property, nor the driver's `notifications` property.
|
||||||
|
filter: /^runs\.\d+\.(artifacts|tool\.driver\.notifications)/,
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Creates JavaScript objects from the token stream
|
// Creates JavaScript objects from the token stream
|
||||||
@@ -38,15 +40,7 @@ export async function sarifParser(
|
|||||||
});
|
});
|
||||||
|
|
||||||
asm.on("done", (asm) => {
|
asm.on("done", (asm) => {
|
||||||
const log: Log = {
|
const log: Log = asm.current;
|
||||||
version: "2.1.0",
|
|
||||||
runs: [
|
|
||||||
{
|
|
||||||
tool: DUMMY_TOOL,
|
|
||||||
results: asm.current ?? [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
resolve(log);
|
resolve(log);
|
||||||
alreadyDone = true;
|
alreadyDone = true;
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import {
|
|||||||
getResultSetNames,
|
getResultSetNames,
|
||||||
} from "./result-set-names";
|
} from "./result-set-names";
|
||||||
import { compareInterpretedResults } from "./interpreted-results";
|
import { compareInterpretedResults } from "./interpreted-results";
|
||||||
|
import { isCanary } from "../config";
|
||||||
|
|
||||||
interface ComparePair {
|
interface ComparePair {
|
||||||
from: CompletedLocalQueryInfo;
|
from: CompletedLocalQueryInfo;
|
||||||
@@ -116,6 +117,13 @@ export class CompareView extends AbstractWebview<
|
|||||||
panel.reveal(undefined, true);
|
panel.reveal(undefined, true);
|
||||||
await this.waitForPanelLoaded();
|
await this.waitForPanelLoaded();
|
||||||
|
|
||||||
|
await this.postMessage({
|
||||||
|
t: "setUserSettings",
|
||||||
|
userSettings: {
|
||||||
|
shouldShowProvenance: isCanary(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
await this.postMessage({
|
await this.postMessage({
|
||||||
t: "setComparisonQueryInfo",
|
t: "setComparisonQueryInfo",
|
||||||
stats: {
|
stats: {
|
||||||
|
|||||||
@@ -37,11 +37,12 @@ export const shownLocationLineDecoration =
|
|||||||
/**
|
/**
|
||||||
* Resolves the specified CodeQL location to a URI into the source archive.
|
* Resolves the specified CodeQL location to a URI into the source archive.
|
||||||
* @param loc CodeQL location to resolve. Must have a non-empty value for `loc.file`.
|
* @param loc CodeQL location to resolve. Must have a non-empty value for `loc.file`.
|
||||||
* @param databaseItem Database in which to resolve the file location.
|
* @param databaseItem Database in which to resolve the file location, or `undefined` to resolve
|
||||||
|
* from the local file system.
|
||||||
*/
|
*/
|
||||||
function resolveFivePartLocation(
|
function resolveFivePartLocation(
|
||||||
loc: UrlValueLineColumnLocation,
|
loc: UrlValueLineColumnLocation,
|
||||||
databaseItem: DatabaseItem,
|
databaseItem: DatabaseItem | undefined,
|
||||||
): Location {
|
): Location {
|
||||||
// `Range` is a half-open interval, and is zero-based. CodeQL locations are closed intervals, and
|
// `Range` is a half-open interval, and is zero-based. CodeQL locations are closed intervals, and
|
||||||
// are one-based. Adjust accordingly.
|
// are one-based. Adjust accordingly.
|
||||||
@@ -52,7 +53,10 @@ function resolveFivePartLocation(
|
|||||||
Math.max(1, loc.endColumn),
|
Math.max(1, loc.endColumn),
|
||||||
);
|
);
|
||||||
|
|
||||||
return new Location(databaseItem.resolveSourceFile(loc.uri), range);
|
return new Location(
|
||||||
|
databaseItem?.resolveSourceFile(loc.uri) ?? Uri.parse(loc.uri),
|
||||||
|
range,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -62,22 +66,26 @@ function resolveFivePartLocation(
|
|||||||
*/
|
*/
|
||||||
function resolveWholeFileLocation(
|
function resolveWholeFileLocation(
|
||||||
loc: UrlValueWholeFileLocation,
|
loc: UrlValueWholeFileLocation,
|
||||||
databaseItem: DatabaseItem,
|
databaseItem: DatabaseItem | undefined,
|
||||||
): Location {
|
): Location {
|
||||||
// A location corresponding to the start of the file.
|
// A location corresponding to the start of the file.
|
||||||
const range = new Range(0, 0, 0, 0);
|
const range = new Range(0, 0, 0, 0);
|
||||||
return new Location(databaseItem.resolveSourceFile(loc.uri), range);
|
return new Location(
|
||||||
|
databaseItem?.resolveSourceFile(loc.uri) ?? Uri.parse(loc.uri),
|
||||||
|
range,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Try to resolve the specified CodeQL location to a URI into the source archive. If no exact location
|
* Try to resolve the specified CodeQL location to a URI into the source archive. If no exact location
|
||||||
* can be resolved, returns `undefined`.
|
* can be resolved, returns `undefined`.
|
||||||
* @param loc CodeQL location to resolve
|
* @param loc CodeQL location to resolve
|
||||||
* @param databaseItem Database in which to resolve the file location.
|
* @param databaseItem Database in which to resolve the file location, or `undefined` to resolve
|
||||||
|
* from the local file system.
|
||||||
*/
|
*/
|
||||||
export function tryResolveLocation(
|
export function tryResolveLocation(
|
||||||
loc: UrlValueResolvable | undefined,
|
loc: UrlValueResolvable | undefined,
|
||||||
databaseItem: DatabaseItem,
|
databaseItem: DatabaseItem | undefined,
|
||||||
): Location | undefined {
|
): Location | undefined {
|
||||||
if (!loc) {
|
if (!loc) {
|
||||||
return;
|
return;
|
||||||
@@ -95,7 +103,7 @@ export function tryResolveLocation(
|
|||||||
|
|
||||||
export async function showResolvableLocation(
|
export async function showResolvableLocation(
|
||||||
loc: UrlValueResolvable,
|
loc: UrlValueResolvable,
|
||||||
databaseItem: DatabaseItem,
|
databaseItem: DatabaseItem | undefined,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
@@ -151,13 +159,14 @@ export async function showLocation(location?: Location) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function jumpToLocation(
|
export async function jumpToLocation(
|
||||||
databaseUri: string,
|
databaseUri: string | undefined,
|
||||||
loc: UrlValueResolvable,
|
loc: UrlValueResolvable,
|
||||||
databaseManager: DatabaseManager,
|
databaseManager: DatabaseManager,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
) {
|
) {
|
||||||
const databaseItem = databaseManager.findDatabaseItem(Uri.parse(databaseUri));
|
const databaseItem =
|
||||||
if (databaseItem !== undefined) {
|
databaseUri !== undefined
|
||||||
await showResolvableLocation(loc, databaseItem, logger);
|
? databaseManager.findDatabaseItem(Uri.parse(databaseUri))
|
||||||
}
|
: undefined;
|
||||||
|
await showResolvableLocation(loc, databaseItem, logger);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -537,6 +537,14 @@ export class ResultsView extends AbstractWebview<
|
|||||||
resultSetNames,
|
resultSetNames,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
await this.postMessage({
|
||||||
|
t: "setUserSettings",
|
||||||
|
userSettings: {
|
||||||
|
// Only show provenance info in canary mode for now.
|
||||||
|
shouldShowProvenance: isCanary(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
await this.postMessage({
|
await this.postMessage({
|
||||||
t: "setState",
|
t: "setState",
|
||||||
interpretation: interpretationPage,
|
interpretation: interpretationPage,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type {
|
|||||||
ToCompareViewMessage,
|
ToCompareViewMessage,
|
||||||
SetComparisonsMessage,
|
SetComparisonsMessage,
|
||||||
SetComparisonQueryInfoMessage,
|
SetComparisonQueryInfoMessage,
|
||||||
|
UserSettings,
|
||||||
} from "../../common/interface-types";
|
} from "../../common/interface-types";
|
||||||
import CompareSelector from "./CompareSelector";
|
import CompareSelector from "./CompareSelector";
|
||||||
import { vscode } from "../vscode-api";
|
import { vscode } from "../vscode-api";
|
||||||
@@ -31,6 +32,9 @@ export function Compare(_: Record<string, never>): React.JSX.Element {
|
|||||||
const [comparison, setComparison] = useState<SetComparisonsMessage | null>(
|
const [comparison, setComparison] = useState<SetComparisonsMessage | null>(
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
const [userSettings, setUserSettings] = useState<UserSettings>({
|
||||||
|
shouldShowProvenance: false,
|
||||||
|
});
|
||||||
|
|
||||||
const message = comparison?.message || "Empty comparison";
|
const message = comparison?.message || "Empty comparison";
|
||||||
const hasRows =
|
const hasRows =
|
||||||
@@ -48,6 +52,9 @@ export function Compare(_: Record<string, never>): React.JSX.Element {
|
|||||||
case "setComparisons":
|
case "setComparisons":
|
||||||
setComparison(msg);
|
setComparison(msg);
|
||||||
break;
|
break;
|
||||||
|
case "setUserSettings":
|
||||||
|
setUserSettings(msg.userSettings);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
assertNever(msg);
|
assertNever(msg);
|
||||||
}
|
}
|
||||||
@@ -85,6 +92,7 @@ export function Compare(_: Record<string, never>): React.JSX.Element {
|
|||||||
<CompareTable
|
<CompareTable
|
||||||
queryInfo={queryInfo}
|
queryInfo={queryInfo}
|
||||||
comparison={comparison}
|
comparison={comparison}
|
||||||
|
userSettings={userSettings}
|
||||||
></CompareTable>
|
></CompareTable>
|
||||||
) : (
|
) : (
|
||||||
<Message>{message}</Message>
|
<Message>{message}</Message>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type {
|
import type {
|
||||||
SetComparisonQueryInfoMessage,
|
SetComparisonQueryInfoMessage,
|
||||||
SetComparisonsMessage,
|
SetComparisonsMessage,
|
||||||
|
UserSettings,
|
||||||
} from "../../common/interface-types";
|
} from "../../common/interface-types";
|
||||||
import { className } from "../results/result-table-utils";
|
import { className } from "../results/result-table-utils";
|
||||||
import { vscode } from "../vscode-api";
|
import { vscode } from "../vscode-api";
|
||||||
@@ -12,6 +13,7 @@ import { InterpretedCompareResultTable } from "./InterpretedCompareResultTable";
|
|||||||
interface Props {
|
interface Props {
|
||||||
queryInfo: SetComparisonQueryInfoMessage;
|
queryInfo: SetComparisonQueryInfoMessage;
|
||||||
comparison: SetComparisonsMessage;
|
comparison: SetComparisonsMessage;
|
||||||
|
userSettings: UserSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
const OpenButton = styled(TextButton)`
|
const OpenButton = styled(TextButton)`
|
||||||
@@ -29,7 +31,11 @@ const Table = styled.table`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default function CompareTable({ queryInfo, comparison }: Props) {
|
export default function CompareTable({
|
||||||
|
queryInfo,
|
||||||
|
comparison,
|
||||||
|
userSettings,
|
||||||
|
}: Props) {
|
||||||
const result = comparison.result!;
|
const result = comparison.result!;
|
||||||
|
|
||||||
async function openQuery(kind: "from" | "to") {
|
async function openQuery(kind: "from" | "to") {
|
||||||
@@ -78,6 +84,7 @@ export default function CompareTable({ queryInfo, comparison }: Props) {
|
|||||||
{result.kind === "interpreted" && (
|
{result.kind === "interpreted" && (
|
||||||
<InterpretedCompareResultTable
|
<InterpretedCompareResultTable
|
||||||
results={result.from}
|
results={result.from}
|
||||||
|
userSettings={userSettings}
|
||||||
databaseUri={queryInfo.databaseUri}
|
databaseUri={queryInfo.databaseUri}
|
||||||
sourceLocationPrefix={result.sourceLocationPrefix}
|
sourceLocationPrefix={result.sourceLocationPrefix}
|
||||||
/>
|
/>
|
||||||
@@ -96,6 +103,7 @@ export default function CompareTable({ queryInfo, comparison }: Props) {
|
|||||||
{result.kind === "interpreted" && (
|
{result.kind === "interpreted" && (
|
||||||
<InterpretedCompareResultTable
|
<InterpretedCompareResultTable
|
||||||
results={result.to}
|
results={result.to}
|
||||||
|
userSettings={userSettings}
|
||||||
databaseUri={queryInfo.databaseUri}
|
databaseUri={queryInfo.databaseUri}
|
||||||
sourceLocationPrefix={result.sourceLocationPrefix}
|
sourceLocationPrefix={result.sourceLocationPrefix}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,27 +1,32 @@
|
|||||||
import type { Result } from "sarif";
|
import type { Result, Run } from "sarif";
|
||||||
import { AlertTable } from "../results/AlertTable";
|
import { AlertTable } from "../results/AlertTable";
|
||||||
|
import type { UserSettings } from "../../common/interface-types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
results: Result[];
|
results: Result[];
|
||||||
databaseUri: string;
|
databaseUri: string;
|
||||||
sourceLocationPrefix: string;
|
sourceLocationPrefix: string;
|
||||||
|
run?: Run;
|
||||||
|
userSettings: UserSettings;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const InterpretedCompareResultTable = ({
|
export const InterpretedCompareResultTable = ({
|
||||||
results,
|
results,
|
||||||
databaseUri,
|
databaseUri,
|
||||||
sourceLocationPrefix,
|
sourceLocationPrefix,
|
||||||
|
userSettings,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
return (
|
return (
|
||||||
<AlertTable
|
<AlertTable
|
||||||
results={results}
|
results={results}
|
||||||
|
userSettings={userSettings}
|
||||||
databaseUri={databaseUri}
|
databaseUri={databaseUri}
|
||||||
sourceLocationPrefix={sourceLocationPrefix}
|
sourceLocationPrefix={sourceLocationPrefix}
|
||||||
header={
|
header={
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th colSpan={2}></th>
|
<th colSpan={2}></th>
|
||||||
<th className={`vscode-codeql__alert-message-cell`} colSpan={3}>
|
<th className={`vscode-codeql__alert-message-cell`} colSpan={4}>
|
||||||
Message
|
Message
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Location, Result } from "sarif";
|
import type { Location, Result, Run } from "sarif";
|
||||||
import type {
|
import type {
|
||||||
PathNode,
|
PathNode,
|
||||||
Result as ResultKeysResult,
|
Result as ResultKeysResult,
|
||||||
@@ -7,7 +7,7 @@ import type {
|
|||||||
import { getPath, getPathNode, getResult, keyToString } from "./result-keys";
|
import { getPath, getPathNode, getResult, keyToString } from "./result-keys";
|
||||||
import { className, jumpToLocation } from "./result-table-utils";
|
import { className, jumpToLocation } from "./result-table-utils";
|
||||||
import { onNavigation } from "./navigation";
|
import { onNavigation } from "./navigation";
|
||||||
import type { NavigateMsg } from "../../common/interface-types";
|
import type { NavigateMsg, UserSettings } from "../../common/interface-types";
|
||||||
import { NavigationDirection } from "../../common/interface-types";
|
import { NavigationDirection } from "../../common/interface-types";
|
||||||
import { isNoLocation, parseSarifLocation } from "../../common/sarif-utils";
|
import { isNoLocation, parseSarifLocation } from "../../common/sarif-utils";
|
||||||
import { sendTelemetry } from "../common/telemetry";
|
import { sendTelemetry } from "../common/telemetry";
|
||||||
@@ -21,6 +21,8 @@ type Props = {
|
|||||||
results: Result[];
|
results: Result[];
|
||||||
databaseUri: string;
|
databaseUri: string;
|
||||||
sourceLocationPrefix: string;
|
sourceLocationPrefix: string;
|
||||||
|
run?: Run;
|
||||||
|
userSettings: UserSettings;
|
||||||
numTruncatedResults?: number;
|
numTruncatedResults?: number;
|
||||||
|
|
||||||
header: ReactNode;
|
header: ReactNode;
|
||||||
@@ -31,6 +33,8 @@ export function AlertTable({
|
|||||||
results,
|
results,
|
||||||
databaseUri,
|
databaseUri,
|
||||||
sourceLocationPrefix,
|
sourceLocationPrefix,
|
||||||
|
run,
|
||||||
|
userSettings,
|
||||||
numTruncatedResults,
|
numTruncatedResults,
|
||||||
header,
|
header,
|
||||||
noResults,
|
noResults,
|
||||||
@@ -202,6 +206,8 @@ export function AlertTable({
|
|||||||
selectedItem={selectedItem}
|
selectedItem={selectedItem}
|
||||||
selectedItemRef={selectedItemRef}
|
selectedItemRef={selectedItemRef}
|
||||||
databaseUri={databaseUri}
|
databaseUri={databaseUri}
|
||||||
|
run={run}
|
||||||
|
userSettings={userSettings}
|
||||||
sourceLocationPrefix={sourceLocationPrefix}
|
sourceLocationPrefix={sourceLocationPrefix}
|
||||||
updateSelectionCallback={updateSelectionCallback}
|
updateSelectionCallback={updateSelectionCallback}
|
||||||
toggleExpanded={toggle}
|
toggleExpanded={toggle}
|
||||||
|
|||||||
@@ -1,4 +1,12 @@
|
|||||||
import type { ThreadFlowLocation } from "sarif";
|
import type {
|
||||||
|
Location as SarifLogLocation,
|
||||||
|
ReportingDescriptorReference,
|
||||||
|
ThreadFlowLocation,
|
||||||
|
Run,
|
||||||
|
PhysicalLocation,
|
||||||
|
ArtifactLocation,
|
||||||
|
ToolComponent,
|
||||||
|
} from "sarif";
|
||||||
import type {
|
import type {
|
||||||
PathNode,
|
PathNode,
|
||||||
Result as ResultKeysResult,
|
Result as ResultKeysResult,
|
||||||
@@ -9,6 +17,151 @@ import { SarifLocation } from "./locations/SarifLocation";
|
|||||||
import { selectableZebraStripe } from "./result-table-utils";
|
import { selectableZebraStripe } from "./result-table-utils";
|
||||||
import { useCallback, useMemo } from "react";
|
import { useCallback, useMemo } from "react";
|
||||||
import { VerticalRule } from "../common/VerticalRule";
|
import { VerticalRule } from "../common/VerticalRule";
|
||||||
|
import type { UserSettings } from "../../common/interface-types";
|
||||||
|
|
||||||
|
/** The definition of a taxon for a data extension model row. */
|
||||||
|
interface ModelTaxon {
|
||||||
|
location: SarifLogLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Resolve an `ArtifactLocation` that might contain a relative reference instead of an absolute
|
||||||
|
* URI.
|
||||||
|
*/
|
||||||
|
function resolveArtifactLocation(
|
||||||
|
location: ArtifactLocation,
|
||||||
|
baseUri: URL,
|
||||||
|
): ArtifactLocation {
|
||||||
|
if (location.uri === undefined) {
|
||||||
|
// No URI at all. Just return the original location.
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...location,
|
||||||
|
uri: new URL(location.uri, baseUri).toString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get the URI of the pack's local root directory, if available. */
|
||||||
|
function getLocalPackUri(extension: ToolComponent): URL | undefined {
|
||||||
|
if (extension.locations === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const localPackLocation = extension.locations.find(
|
||||||
|
(loc) =>
|
||||||
|
loc.properties !== undefined &&
|
||||||
|
loc.properties.tags !== undefined &&
|
||||||
|
loc.properties.tags.includes("CodeQL/LocalPackRoot"),
|
||||||
|
);
|
||||||
|
if (localPackLocation === undefined || localPackLocation.uri === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new URL(localPackLocation.uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Resolve a `ReportingDescriptorReference` to the `ReportingDescriptor` for the taxon that it
|
||||||
|
* refers to.
|
||||||
|
*/
|
||||||
|
function resolveTaxonDefinition(
|
||||||
|
run: Run,
|
||||||
|
taxonRef: ReportingDescriptorReference,
|
||||||
|
): ModelTaxon | undefined {
|
||||||
|
const extensions = run.tool.extensions;
|
||||||
|
if (extensions === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const extensionIndex = taxonRef.toolComponent?.index;
|
||||||
|
if (
|
||||||
|
extensionIndex === undefined ||
|
||||||
|
extensionIndex < 0 ||
|
||||||
|
extensionIndex >= extensions.length
|
||||||
|
) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const extension = extensions[extensionIndex];
|
||||||
|
if (extension.taxa === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const localPackUri = getLocalPackUri(extension);
|
||||||
|
if (localPackUri === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const taxonIndex = taxonRef.index;
|
||||||
|
if (
|
||||||
|
taxonIndex === undefined ||
|
||||||
|
taxonIndex < 0 ||
|
||||||
|
taxonIndex >= extension.taxa.length
|
||||||
|
) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const taxonDef = extension.taxa[taxonIndex];
|
||||||
|
if (taxonDef.properties === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const location: PhysicalLocation =
|
||||||
|
taxonDef.properties["CodeQL/DataExtensionLocation"];
|
||||||
|
if (location === undefined || location.artifactLocation === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
location: {
|
||||||
|
physicalLocation: {
|
||||||
|
...location,
|
||||||
|
artifactLocation: resolveArtifactLocation(
|
||||||
|
location.artifactLocation,
|
||||||
|
localPackUri,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Generate the React elements for each taxon. */
|
||||||
|
function taxaLocations(
|
||||||
|
taxa: ReportingDescriptorReference[] | undefined,
|
||||||
|
run: Run | undefined,
|
||||||
|
onClick: () => void,
|
||||||
|
) {
|
||||||
|
if (taxa === undefined || taxa.length === 0 || run === undefined) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return taxa.flatMap((taxonRef, index) => {
|
||||||
|
if (taxonRef.properties === undefined) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const role = taxonRef.properties["CodeQL/DataflowRole"];
|
||||||
|
if (typeof role !== "string") {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const taxonDef = resolveTaxonDefinition(run, taxonRef);
|
||||||
|
if (taxonDef === undefined) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={index}>
|
||||||
|
{`(${role}) `}
|
||||||
|
<SarifLocation
|
||||||
|
loc={taxonDef.location}
|
||||||
|
databaseUri={undefined}
|
||||||
|
sourceLocationPrefix=""
|
||||||
|
onClick={onClick}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
step: ThreadFlowLocation;
|
step: ThreadFlowLocation;
|
||||||
@@ -19,6 +172,8 @@ interface Props {
|
|||||||
selectedItemRef: React.RefObject<HTMLTableRowElement>;
|
selectedItemRef: React.RefObject<HTMLTableRowElement>;
|
||||||
databaseUri: string;
|
databaseUri: string;
|
||||||
sourceLocationPrefix: string;
|
sourceLocationPrefix: string;
|
||||||
|
run?: Run;
|
||||||
|
userSettings: UserSettings;
|
||||||
updateSelectionCallback: (
|
updateSelectionCallback: (
|
||||||
resultKey: PathNode | ResultKeysResult | undefined,
|
resultKey: PathNode | ResultKeysResult | undefined,
|
||||||
) => void;
|
) => void;
|
||||||
@@ -34,6 +189,8 @@ export function AlertTablePathNodeRow(props: Props) {
|
|||||||
selectedItemRef,
|
selectedItemRef,
|
||||||
databaseUri,
|
databaseUri,
|
||||||
sourceLocationPrefix,
|
sourceLocationPrefix,
|
||||||
|
run,
|
||||||
|
userSettings,
|
||||||
updateSelectionCallback,
|
updateSelectionCallback,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
@@ -86,7 +243,21 @@ export function AlertTablePathNodeRow(props: Props) {
|
|||||||
"[no location]"
|
"[no location]"
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
<td {...selectableZebraStripe(isSelected, zebraIndex)}>{"model"}</td>
|
<td
|
||||||
|
{...selectableZebraStripe(
|
||||||
|
isSelected,
|
||||||
|
zebraIndex,
|
||||||
|
"vscode-codeql__taxa-cell",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{userSettings.shouldShowProvenance ? (
|
||||||
|
<div className="vscode-codeql__taxa-cell-div">
|
||||||
|
{taxaLocations(step.taxa, run, handleSarifLocationClicked)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
[]
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
<td
|
<td
|
||||||
{...selectableZebraStripe(
|
{...selectableZebraStripe(
|
||||||
isSelected,
|
isSelected,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { ThreadFlow } from "sarif";
|
import type { Run, ThreadFlow } from "sarif";
|
||||||
import type {
|
import type {
|
||||||
PathNode,
|
PathNode,
|
||||||
Result as ResultKeysResult,
|
Result as ResultKeysResult,
|
||||||
@@ -10,6 +10,7 @@ import { AlertTablePathNodeRow } from "./AlertTablePathNodeRow";
|
|||||||
import { AlertTableDropdownIndicatorCell } from "./AlertTableDropdownIndicatorCell";
|
import { AlertTableDropdownIndicatorCell } from "./AlertTableDropdownIndicatorCell";
|
||||||
import { useCallback, useMemo } from "react";
|
import { useCallback, useMemo } from "react";
|
||||||
import { VerticalRule } from "../common/VerticalRule";
|
import { VerticalRule } from "../common/VerticalRule";
|
||||||
|
import type { UserSettings } from "../../common/interface-types";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
path: ThreadFlow;
|
path: ThreadFlow;
|
||||||
@@ -20,6 +21,8 @@ interface Props {
|
|||||||
selectedItemRef: React.RefObject<HTMLTableRowElement>;
|
selectedItemRef: React.RefObject<HTMLTableRowElement>;
|
||||||
databaseUri: string;
|
databaseUri: string;
|
||||||
sourceLocationPrefix: string;
|
sourceLocationPrefix: string;
|
||||||
|
run?: Run;
|
||||||
|
userSettings: UserSettings;
|
||||||
updateSelectionCallback: (
|
updateSelectionCallback: (
|
||||||
resultKey: PathNode | ResultKeysResult | undefined,
|
resultKey: PathNode | ResultKeysResult | undefined,
|
||||||
) => void;
|
) => void;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Result } from "sarif";
|
import type { Result, Run } from "sarif";
|
||||||
import type {
|
import type {
|
||||||
PathNode,
|
PathNode,
|
||||||
Result as ResultKeysResult,
|
Result as ResultKeysResult,
|
||||||
@@ -12,6 +12,7 @@ import { useCallback, useMemo } from "react";
|
|||||||
import { SarifLocation } from "./locations/SarifLocation";
|
import { SarifLocation } from "./locations/SarifLocation";
|
||||||
import { SarifMessageWithLocations } from "./locations/SarifMessageWithLocations";
|
import { SarifMessageWithLocations } from "./locations/SarifMessageWithLocations";
|
||||||
import { AlertTablePathRow } from "./AlertTablePathRow";
|
import { AlertTablePathRow } from "./AlertTablePathRow";
|
||||||
|
import type { UserSettings } from "../../common/interface-types";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
result: Result;
|
result: Result;
|
||||||
@@ -21,6 +22,8 @@ interface Props {
|
|||||||
selectedItemRef: React.RefObject<HTMLTableRowElement>;
|
selectedItemRef: React.RefObject<HTMLTableRowElement>;
|
||||||
databaseUri: string;
|
databaseUri: string;
|
||||||
sourceLocationPrefix: string;
|
sourceLocationPrefix: string;
|
||||||
|
run?: Run;
|
||||||
|
userSettings: UserSettings;
|
||||||
updateSelectionCallback: (
|
updateSelectionCallback: (
|
||||||
resultKey: PathNode | ResultKeysResult | undefined,
|
resultKey: PathNode | ResultKeysResult | undefined,
|
||||||
) => void;
|
) => void;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { AlertTableNoResults } from "./AlertTableNoResults";
|
|||||||
import { AlertTableHeader } from "./AlertTableHeader";
|
import { AlertTableHeader } from "./AlertTableHeader";
|
||||||
|
|
||||||
export function ResultTable(props: ResultTableProps) {
|
export function ResultTable(props: ResultTableProps) {
|
||||||
const { resultSet } = props;
|
const { resultSet, userSettings } = props;
|
||||||
switch (resultSet.t) {
|
switch (resultSet.t) {
|
||||||
case "RawResultSet":
|
case "RawResultSet":
|
||||||
return <RawTable {...props} resultSet={resultSet.resultSet} />;
|
return <RawTable {...props} resultSet={resultSet.resultSet} />;
|
||||||
@@ -21,6 +21,8 @@ export function ResultTable(props: ResultTableProps) {
|
|||||||
sourceLocationPrefix={
|
sourceLocationPrefix={
|
||||||
resultSet.interpretation.sourceLocationPrefix
|
resultSet.interpretation.sourceLocationPrefix
|
||||||
}
|
}
|
||||||
|
run={data.runs[0]}
|
||||||
|
userSettings={userSettings}
|
||||||
numTruncatedResults={resultSet.interpretation.numTruncatedResults}
|
numTruncatedResults={resultSet.interpretation.numTruncatedResults}
|
||||||
header={<AlertTableHeader sortState={data.sortState} />}
|
header={<AlertTableHeader sortState={data.sortState} />}
|
||||||
noResults={
|
noResults={
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import type {
|
|||||||
ResultSet,
|
ResultSet,
|
||||||
ParsedResultSets,
|
ParsedResultSets,
|
||||||
IntoResultsViewMsg,
|
IntoResultsViewMsg,
|
||||||
|
UserSettings,
|
||||||
} from "../../common/interface-types";
|
} from "../../common/interface-types";
|
||||||
import {
|
import {
|
||||||
ALERTS_TABLE_NAME,
|
ALERTS_TABLE_NAME,
|
||||||
@@ -33,6 +34,7 @@ interface ResultTablesProps {
|
|||||||
rawResultSets: readonly ResultSet[];
|
rawResultSets: readonly ResultSet[];
|
||||||
interpretation: Interpretation | undefined;
|
interpretation: Interpretation | undefined;
|
||||||
database: DatabaseInfo;
|
database: DatabaseInfo;
|
||||||
|
userSettings: UserSettings;
|
||||||
metadata?: QueryMetadata;
|
metadata?: QueryMetadata;
|
||||||
resultsPath: string;
|
resultsPath: string;
|
||||||
origResultsPaths: ResultsPaths;
|
origResultsPaths: ResultsPaths;
|
||||||
@@ -94,6 +96,7 @@ export function ResultTables(props: ResultTablesProps) {
|
|||||||
interpretation,
|
interpretation,
|
||||||
database,
|
database,
|
||||||
resultsPath,
|
resultsPath,
|
||||||
|
userSettings,
|
||||||
metadata,
|
metadata,
|
||||||
origResultsPaths,
|
origResultsPaths,
|
||||||
isLoadingNewResults,
|
isLoadingNewResults,
|
||||||
@@ -242,6 +245,7 @@ export function ResultTables(props: ResultTablesProps) {
|
|||||||
<ResultTable
|
<ResultTable
|
||||||
key={resultSetName}
|
key={resultSetName}
|
||||||
resultSet={resultSet}
|
resultSet={resultSet}
|
||||||
|
userSettings={userSettings}
|
||||||
databaseUri={database.databaseUri}
|
databaseUri={database.databaseUri}
|
||||||
resultsPath={resultsPath}
|
resultsPath={resultsPath}
|
||||||
sortState={sortStates.get(resultSetName)}
|
sortState={sortStates.get(resultSetName)}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import type {
|
|||||||
ResultsPaths,
|
ResultsPaths,
|
||||||
ParsedResultSets,
|
ParsedResultSets,
|
||||||
ResultSet,
|
ResultSet,
|
||||||
|
UserSettings,
|
||||||
} from "../../common/interface-types";
|
} from "../../common/interface-types";
|
||||||
import {
|
import {
|
||||||
ALERTS_TABLE_NAME,
|
ALERTS_TABLE_NAME,
|
||||||
@@ -77,6 +78,10 @@ export function ResultsApp() {
|
|||||||
isExpectingResultsUpdate: true,
|
isExpectingResultsUpdate: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [userSettings, setUserSettings] = useState<UserSettings>({
|
||||||
|
shouldShowProvenance: false,
|
||||||
|
});
|
||||||
|
|
||||||
const updateStateWithNewResultsInfo = useCallback(
|
const updateStateWithNewResultsInfo = useCallback(
|
||||||
(resultsInfo: ResultsInfo): void => {
|
(resultsInfo: ResultsInfo): void => {
|
||||||
let results: Results | null = null;
|
let results: Results | null = null;
|
||||||
@@ -110,6 +115,10 @@ export function ResultsApp() {
|
|||||||
const handleMessage = useCallback(
|
const handleMessage = useCallback(
|
||||||
(msg: IntoResultsViewMsg): void => {
|
(msg: IntoResultsViewMsg): void => {
|
||||||
switch (msg.t) {
|
switch (msg.t) {
|
||||||
|
case "setUserSettings":
|
||||||
|
setUserSettings(msg.userSettings);
|
||||||
|
break;
|
||||||
|
|
||||||
case "setState":
|
case "setState":
|
||||||
updateStateWithNewResultsInfo({
|
updateStateWithNewResultsInfo({
|
||||||
resultsPath: msg.resultsPath,
|
resultsPath: msg.resultsPath,
|
||||||
@@ -217,6 +226,7 @@ export function ResultsApp() {
|
|||||||
? displayedResults.resultsInfo.interpretation
|
? displayedResults.resultsInfo.interpretation
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
|
userSettings={userSettings}
|
||||||
database={displayedResults.results.database}
|
database={displayedResults.results.database}
|
||||||
origResultsPaths={displayedResults.resultsInfo.origResultsPaths}
|
origResultsPaths={displayedResults.resultsInfo.origResultsPaths}
|
||||||
resultsPath={displayedResults.resultsInfo.resultsPath}
|
resultsPath={displayedResults.resultsInfo.resultsPath}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import type { UrlValueResolvable } from "../../../common/raw-result-types";
|
|||||||
interface Props {
|
interface Props {
|
||||||
loc: UrlValueResolvable;
|
loc: UrlValueResolvable;
|
||||||
label: string;
|
label: string;
|
||||||
databaseUri: string;
|
databaseUri: string | undefined;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,17 +26,13 @@ export function Location({
|
|||||||
const displayLabel = useMemo(() => convertNonPrintableChars(label), [label]);
|
const displayLabel = useMemo(() => convertNonPrintableChars(label), [label]);
|
||||||
|
|
||||||
if (loc === undefined) {
|
if (loc === undefined) {
|
||||||
return <NonClickableLocation msg={displayLabel} />;
|
return <NonClickableLocation msg={displayLabel} locationHint={title} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loc.type === "string") {
|
if (loc.type === "string") {
|
||||||
return <a href={loc.value}>{loc.value}</a>;
|
return <a href={loc.value}>{loc.value}</a>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (databaseUri === undefined) {
|
|
||||||
return <NonClickableLocation msg={displayLabel} locationHint={title} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ClickableLocation
|
<ClickableLocation
|
||||||
loc={loc}
|
loc={loc}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ interface Props {
|
|||||||
text?: string;
|
text?: string;
|
||||||
loc?: SarifLogLocation;
|
loc?: SarifLogLocation;
|
||||||
sourceLocationPrefix: string;
|
sourceLocationPrefix: string;
|
||||||
databaseUri: string;
|
databaseUri: string | undefined;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type {
|
|||||||
QueryMetadata,
|
QueryMetadata,
|
||||||
RawResultsSortState,
|
RawResultsSortState,
|
||||||
ResultSet,
|
ResultSet,
|
||||||
|
UserSettings,
|
||||||
} from "../../common/interface-types";
|
} from "../../common/interface-types";
|
||||||
import { SortDirection } from "../../common/interface-types";
|
import { SortDirection } from "../../common/interface-types";
|
||||||
import { assertNever } from "../../common/helpers-pure";
|
import { assertNever } from "../../common/helpers-pure";
|
||||||
@@ -11,6 +12,7 @@ import type { UrlValueResolvable } from "../../common/raw-result-types";
|
|||||||
export interface ResultTableProps {
|
export interface ResultTableProps {
|
||||||
resultSet: ResultSet;
|
resultSet: ResultSet;
|
||||||
databaseUri: string;
|
databaseUri: string;
|
||||||
|
userSettings: UserSettings;
|
||||||
metadata?: QueryMetadata;
|
metadata?: QueryMetadata;
|
||||||
resultsPath: string | undefined;
|
resultsPath: string | undefined;
|
||||||
sortState?: RawResultsSortState;
|
sortState?: RawResultsSortState;
|
||||||
@@ -41,7 +43,7 @@ export const selectedRowClassName = "vscode-codeql__result-table-row--selected";
|
|||||||
|
|
||||||
export function jumpToLocation(
|
export function jumpToLocation(
|
||||||
loc: UrlValueResolvable,
|
loc: UrlValueResolvable,
|
||||||
databaseUri: string,
|
databaseUri: string | undefined,
|
||||||
): void {
|
): void {
|
||||||
vscode.postMessage({
|
vscode.postMessage({
|
||||||
t: "viewSourceFile",
|
t: "viewSourceFile",
|
||||||
|
|||||||
@@ -144,3 +144,12 @@ td.vscode-codeql__path-index-cell {
|
|||||||
.vscode-codeql__location-cell {
|
.vscode-codeql__location-cell {
|
||||||
text-align: right !important;
|
text-align: right !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vscode-codeql__taxa-cell {
|
||||||
|
text-align: left !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vscode-codeql__taxa-cell-div {
|
||||||
|
background-color: transparent;
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user