Display path provenance information in results view
This commit is contained in:
@@ -147,6 +147,17 @@ interface SetStateMsg {
|
||||
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
|
||||
* results.
|
||||
@@ -191,6 +202,7 @@ interface UntoggleShowProblemsMsg {
|
||||
export type IntoResultsViewMsg =
|
||||
| ResultsUpdatingMsg
|
||||
| SetStateMsg
|
||||
| SetUserSettingsMsg
|
||||
| ShowInterpretedPageMsg
|
||||
| NavigateMsg
|
||||
| UntoggleShowProblemsMsg;
|
||||
@@ -208,13 +220,15 @@ export type FromResultsViewMsg =
|
||||
| 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.
|
||||
*/
|
||||
interface ViewSourceFileMsg {
|
||||
t: "viewSourceFile";
|
||||
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 =
|
||||
| SetComparisonQueryInfoMessage
|
||||
| SetComparisonsMessage;
|
||||
| SetComparisonsMessage
|
||||
| SetUserSettingsMsg;
|
||||
|
||||
/**
|
||||
* 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 { connectTo } from "stream-json/Assembler";
|
||||
import { getErrorMessage } from "./helpers-pure";
|
||||
import { withParser } from "stream-json/filters/Pick";
|
||||
|
||||
const DUMMY_TOOL: Tool = { driver: { name: "" } };
|
||||
import { withParser } from "stream-json/filters/Ignore";
|
||||
|
||||
export async function sarifParser(
|
||||
interpretedResultsPath: string,
|
||||
): Promise<Log> {
|
||||
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(
|
||||
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
|
||||
@@ -38,15 +40,7 @@ export async function sarifParser(
|
||||
});
|
||||
|
||||
asm.on("done", (asm) => {
|
||||
const log: Log = {
|
||||
version: "2.1.0",
|
||||
runs: [
|
||||
{
|
||||
tool: DUMMY_TOOL,
|
||||
results: asm.current ?? [],
|
||||
},
|
||||
],
|
||||
};
|
||||
const log: Log = asm.current;
|
||||
|
||||
resolve(log);
|
||||
alreadyDone = true;
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
getResultSetNames,
|
||||
} from "./result-set-names";
|
||||
import { compareInterpretedResults } from "./interpreted-results";
|
||||
import { isCanary } from "../config";
|
||||
|
||||
interface ComparePair {
|
||||
from: CompletedLocalQueryInfo;
|
||||
@@ -116,6 +117,13 @@ export class CompareView extends AbstractWebview<
|
||||
panel.reveal(undefined, true);
|
||||
await this.waitForPanelLoaded();
|
||||
|
||||
await this.postMessage({
|
||||
t: "setUserSettings",
|
||||
userSettings: {
|
||||
shouldShowProvenance: isCanary(),
|
||||
},
|
||||
});
|
||||
|
||||
await this.postMessage({
|
||||
t: "setComparisonQueryInfo",
|
||||
stats: {
|
||||
|
||||
@@ -37,11 +37,12 @@ export const shownLocationLineDecoration =
|
||||
/**
|
||||
* 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 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(
|
||||
loc: UrlValueLineColumnLocation,
|
||||
databaseItem: DatabaseItem,
|
||||
databaseItem: DatabaseItem | undefined,
|
||||
): Location {
|
||||
// `Range` is a half-open interval, and is zero-based. CodeQL locations are closed intervals, and
|
||||
// are one-based. Adjust accordingly.
|
||||
@@ -52,7 +53,10 @@ function resolveFivePartLocation(
|
||||
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(
|
||||
loc: UrlValueWholeFileLocation,
|
||||
databaseItem: DatabaseItem,
|
||||
databaseItem: DatabaseItem | undefined,
|
||||
): Location {
|
||||
// A location corresponding to the start of the file.
|
||||
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
|
||||
* can be resolved, returns `undefined`.
|
||||
* @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(
|
||||
loc: UrlValueResolvable | undefined,
|
||||
databaseItem: DatabaseItem,
|
||||
databaseItem: DatabaseItem | undefined,
|
||||
): Location | undefined {
|
||||
if (!loc) {
|
||||
return;
|
||||
@@ -95,7 +103,7 @@ export function tryResolveLocation(
|
||||
|
||||
export async function showResolvableLocation(
|
||||
loc: UrlValueResolvable,
|
||||
databaseItem: DatabaseItem,
|
||||
databaseItem: DatabaseItem | undefined,
|
||||
logger: Logger,
|
||||
): Promise<void> {
|
||||
try {
|
||||
@@ -151,13 +159,14 @@ export async function showLocation(location?: Location) {
|
||||
}
|
||||
|
||||
export async function jumpToLocation(
|
||||
databaseUri: string,
|
||||
databaseUri: string | undefined,
|
||||
loc: UrlValueResolvable,
|
||||
databaseManager: DatabaseManager,
|
||||
logger: Logger,
|
||||
) {
|
||||
const databaseItem = databaseManager.findDatabaseItem(Uri.parse(databaseUri));
|
||||
if (databaseItem !== undefined) {
|
||||
await showResolvableLocation(loc, databaseItem, logger);
|
||||
}
|
||||
const databaseItem =
|
||||
databaseUri !== undefined
|
||||
? databaseManager.findDatabaseItem(Uri.parse(databaseUri))
|
||||
: undefined;
|
||||
await showResolvableLocation(loc, databaseItem, logger);
|
||||
}
|
||||
|
||||
@@ -537,6 +537,14 @@ export class ResultsView extends AbstractWebview<
|
||||
resultSetNames,
|
||||
};
|
||||
|
||||
await this.postMessage({
|
||||
t: "setUserSettings",
|
||||
userSettings: {
|
||||
// Only show provenance info in canary mode for now.
|
||||
shouldShowProvenance: isCanary(),
|
||||
},
|
||||
});
|
||||
|
||||
await this.postMessage({
|
||||
t: "setState",
|
||||
interpretation: interpretationPage,
|
||||
|
||||
@@ -5,6 +5,7 @@ import type {
|
||||
ToCompareViewMessage,
|
||||
SetComparisonsMessage,
|
||||
SetComparisonQueryInfoMessage,
|
||||
UserSettings,
|
||||
} from "../../common/interface-types";
|
||||
import CompareSelector from "./CompareSelector";
|
||||
import { vscode } from "../vscode-api";
|
||||
@@ -31,6 +32,9 @@ export function Compare(_: Record<string, never>): React.JSX.Element {
|
||||
const [comparison, setComparison] = useState<SetComparisonsMessage | null>(
|
||||
null,
|
||||
);
|
||||
const [userSettings, setUserSettings] = useState<UserSettings>({
|
||||
shouldShowProvenance: false,
|
||||
});
|
||||
|
||||
const message = comparison?.message || "Empty comparison";
|
||||
const hasRows =
|
||||
@@ -48,6 +52,9 @@ export function Compare(_: Record<string, never>): React.JSX.Element {
|
||||
case "setComparisons":
|
||||
setComparison(msg);
|
||||
break;
|
||||
case "setUserSettings":
|
||||
setUserSettings(msg.userSettings);
|
||||
break;
|
||||
default:
|
||||
assertNever(msg);
|
||||
}
|
||||
@@ -85,6 +92,7 @@ export function Compare(_: Record<string, never>): React.JSX.Element {
|
||||
<CompareTable
|
||||
queryInfo={queryInfo}
|
||||
comparison={comparison}
|
||||
userSettings={userSettings}
|
||||
></CompareTable>
|
||||
) : (
|
||||
<Message>{message}</Message>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type {
|
||||
SetComparisonQueryInfoMessage,
|
||||
SetComparisonsMessage,
|
||||
UserSettings,
|
||||
} from "../../common/interface-types";
|
||||
import { className } from "../results/result-table-utils";
|
||||
import { vscode } from "../vscode-api";
|
||||
@@ -12,6 +13,7 @@ import { InterpretedCompareResultTable } from "./InterpretedCompareResultTable";
|
||||
interface Props {
|
||||
queryInfo: SetComparisonQueryInfoMessage;
|
||||
comparison: SetComparisonsMessage;
|
||||
userSettings: UserSettings;
|
||||
}
|
||||
|
||||
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!;
|
||||
|
||||
async function openQuery(kind: "from" | "to") {
|
||||
@@ -78,6 +84,7 @@ export default function CompareTable({ queryInfo, comparison }: Props) {
|
||||
{result.kind === "interpreted" && (
|
||||
<InterpretedCompareResultTable
|
||||
results={result.from}
|
||||
userSettings={userSettings}
|
||||
databaseUri={queryInfo.databaseUri}
|
||||
sourceLocationPrefix={result.sourceLocationPrefix}
|
||||
/>
|
||||
@@ -96,6 +103,7 @@ export default function CompareTable({ queryInfo, comparison }: Props) {
|
||||
{result.kind === "interpreted" && (
|
||||
<InterpretedCompareResultTable
|
||||
results={result.to}
|
||||
userSettings={userSettings}
|
||||
databaseUri={queryInfo.databaseUri}
|
||||
sourceLocationPrefix={result.sourceLocationPrefix}
|
||||
/>
|
||||
|
||||
@@ -1,27 +1,32 @@
|
||||
import type { Result } from "sarif";
|
||||
import type { Result, Run } from "sarif";
|
||||
import { AlertTable } from "../results/AlertTable";
|
||||
import type { UserSettings } from "../../common/interface-types";
|
||||
|
||||
type Props = {
|
||||
results: Result[];
|
||||
databaseUri: string;
|
||||
sourceLocationPrefix: string;
|
||||
run?: Run;
|
||||
userSettings: UserSettings;
|
||||
};
|
||||
|
||||
export const InterpretedCompareResultTable = ({
|
||||
results,
|
||||
databaseUri,
|
||||
sourceLocationPrefix,
|
||||
userSettings,
|
||||
}: Props) => {
|
||||
return (
|
||||
<AlertTable
|
||||
results={results}
|
||||
userSettings={userSettings}
|
||||
databaseUri={databaseUri}
|
||||
sourceLocationPrefix={sourceLocationPrefix}
|
||||
header={
|
||||
<thead>
|
||||
<tr>
|
||||
<th colSpan={2}></th>
|
||||
<th className={`vscode-codeql__alert-message-cell`} colSpan={3}>
|
||||
<th className={`vscode-codeql__alert-message-cell`} colSpan={4}>
|
||||
Message
|
||||
</th>
|
||||
</tr>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Location, Result } from "sarif";
|
||||
import type { Location, Result, Run } from "sarif";
|
||||
import type {
|
||||
PathNode,
|
||||
Result as ResultKeysResult,
|
||||
@@ -7,7 +7,7 @@ import type {
|
||||
import { getPath, getPathNode, getResult, keyToString } from "./result-keys";
|
||||
import { className, jumpToLocation } from "./result-table-utils";
|
||||
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 { isNoLocation, parseSarifLocation } from "../../common/sarif-utils";
|
||||
import { sendTelemetry } from "../common/telemetry";
|
||||
@@ -21,6 +21,8 @@ type Props = {
|
||||
results: Result[];
|
||||
databaseUri: string;
|
||||
sourceLocationPrefix: string;
|
||||
run?: Run;
|
||||
userSettings: UserSettings;
|
||||
numTruncatedResults?: number;
|
||||
|
||||
header: ReactNode;
|
||||
@@ -31,6 +33,8 @@ export function AlertTable({
|
||||
results,
|
||||
databaseUri,
|
||||
sourceLocationPrefix,
|
||||
run,
|
||||
userSettings,
|
||||
numTruncatedResults,
|
||||
header,
|
||||
noResults,
|
||||
@@ -202,6 +206,8 @@ export function AlertTable({
|
||||
selectedItem={selectedItem}
|
||||
selectedItemRef={selectedItemRef}
|
||||
databaseUri={databaseUri}
|
||||
run={run}
|
||||
userSettings={userSettings}
|
||||
sourceLocationPrefix={sourceLocationPrefix}
|
||||
updateSelectionCallback={updateSelectionCallback}
|
||||
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 {
|
||||
PathNode,
|
||||
Result as ResultKeysResult,
|
||||
@@ -9,6 +17,151 @@ import { SarifLocation } from "./locations/SarifLocation";
|
||||
import { selectableZebraStripe } from "./result-table-utils";
|
||||
import { useCallback, useMemo } from "react";
|
||||
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 {
|
||||
step: ThreadFlowLocation;
|
||||
@@ -19,6 +172,8 @@ interface Props {
|
||||
selectedItemRef: React.RefObject<HTMLTableRowElement>;
|
||||
databaseUri: string;
|
||||
sourceLocationPrefix: string;
|
||||
run?: Run;
|
||||
userSettings: UserSettings;
|
||||
updateSelectionCallback: (
|
||||
resultKey: PathNode | ResultKeysResult | undefined,
|
||||
) => void;
|
||||
@@ -34,6 +189,8 @@ export function AlertTablePathNodeRow(props: Props) {
|
||||
selectedItemRef,
|
||||
databaseUri,
|
||||
sourceLocationPrefix,
|
||||
run,
|
||||
userSettings,
|
||||
updateSelectionCallback,
|
||||
} = props;
|
||||
|
||||
@@ -86,7 +243,21 @@ export function AlertTablePathNodeRow(props: Props) {
|
||||
"[no location]"
|
||||
)}
|
||||
</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
|
||||
{...selectableZebraStripe(
|
||||
isSelected,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ThreadFlow } from "sarif";
|
||||
import type { Run, ThreadFlow } from "sarif";
|
||||
import type {
|
||||
PathNode,
|
||||
Result as ResultKeysResult,
|
||||
@@ -10,6 +10,7 @@ import { AlertTablePathNodeRow } from "./AlertTablePathNodeRow";
|
||||
import { AlertTableDropdownIndicatorCell } from "./AlertTableDropdownIndicatorCell";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { VerticalRule } from "../common/VerticalRule";
|
||||
import type { UserSettings } from "../../common/interface-types";
|
||||
|
||||
interface Props {
|
||||
path: ThreadFlow;
|
||||
@@ -20,6 +21,8 @@ interface Props {
|
||||
selectedItemRef: React.RefObject<HTMLTableRowElement>;
|
||||
databaseUri: string;
|
||||
sourceLocationPrefix: string;
|
||||
run?: Run;
|
||||
userSettings: UserSettings;
|
||||
updateSelectionCallback: (
|
||||
resultKey: PathNode | ResultKeysResult | undefined,
|
||||
) => void;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Result } from "sarif";
|
||||
import type { Result, Run } from "sarif";
|
||||
import type {
|
||||
PathNode,
|
||||
Result as ResultKeysResult,
|
||||
@@ -12,6 +12,7 @@ import { useCallback, useMemo } from "react";
|
||||
import { SarifLocation } from "./locations/SarifLocation";
|
||||
import { SarifMessageWithLocations } from "./locations/SarifMessageWithLocations";
|
||||
import { AlertTablePathRow } from "./AlertTablePathRow";
|
||||
import type { UserSettings } from "../../common/interface-types";
|
||||
|
||||
interface Props {
|
||||
result: Result;
|
||||
@@ -21,6 +22,8 @@ interface Props {
|
||||
selectedItemRef: React.RefObject<HTMLTableRowElement>;
|
||||
databaseUri: string;
|
||||
sourceLocationPrefix: string;
|
||||
run?: Run;
|
||||
userSettings: UserSettings;
|
||||
updateSelectionCallback: (
|
||||
resultKey: PathNode | ResultKeysResult | undefined,
|
||||
) => void;
|
||||
|
||||
@@ -6,7 +6,7 @@ import { AlertTableNoResults } from "./AlertTableNoResults";
|
||||
import { AlertTableHeader } from "./AlertTableHeader";
|
||||
|
||||
export function ResultTable(props: ResultTableProps) {
|
||||
const { resultSet } = props;
|
||||
const { resultSet, userSettings } = props;
|
||||
switch (resultSet.t) {
|
||||
case "RawResultSet":
|
||||
return <RawTable {...props} resultSet={resultSet.resultSet} />;
|
||||
@@ -21,6 +21,8 @@ export function ResultTable(props: ResultTableProps) {
|
||||
sourceLocationPrefix={
|
||||
resultSet.interpretation.sourceLocationPrefix
|
||||
}
|
||||
run={data.runs[0]}
|
||||
userSettings={userSettings}
|
||||
numTruncatedResults={resultSet.interpretation.numTruncatedResults}
|
||||
header={<AlertTableHeader sortState={data.sortState} />}
|
||||
noResults={
|
||||
|
||||
@@ -8,6 +8,7 @@ import type {
|
||||
ResultSet,
|
||||
ParsedResultSets,
|
||||
IntoResultsViewMsg,
|
||||
UserSettings,
|
||||
} from "../../common/interface-types";
|
||||
import {
|
||||
ALERTS_TABLE_NAME,
|
||||
@@ -33,6 +34,7 @@ interface ResultTablesProps {
|
||||
rawResultSets: readonly ResultSet[];
|
||||
interpretation: Interpretation | undefined;
|
||||
database: DatabaseInfo;
|
||||
userSettings: UserSettings;
|
||||
metadata?: QueryMetadata;
|
||||
resultsPath: string;
|
||||
origResultsPaths: ResultsPaths;
|
||||
@@ -94,6 +96,7 @@ export function ResultTables(props: ResultTablesProps) {
|
||||
interpretation,
|
||||
database,
|
||||
resultsPath,
|
||||
userSettings,
|
||||
metadata,
|
||||
origResultsPaths,
|
||||
isLoadingNewResults,
|
||||
@@ -242,6 +245,7 @@ export function ResultTables(props: ResultTablesProps) {
|
||||
<ResultTable
|
||||
key={resultSetName}
|
||||
resultSet={resultSet}
|
||||
userSettings={userSettings}
|
||||
databaseUri={database.databaseUri}
|
||||
resultsPath={resultsPath}
|
||||
sortState={sortStates.get(resultSetName)}
|
||||
|
||||
@@ -9,6 +9,7 @@ import type {
|
||||
ResultsPaths,
|
||||
ParsedResultSets,
|
||||
ResultSet,
|
||||
UserSettings,
|
||||
} from "../../common/interface-types";
|
||||
import {
|
||||
ALERTS_TABLE_NAME,
|
||||
@@ -77,6 +78,10 @@ export function ResultsApp() {
|
||||
isExpectingResultsUpdate: true,
|
||||
});
|
||||
|
||||
const [userSettings, setUserSettings] = useState<UserSettings>({
|
||||
shouldShowProvenance: false,
|
||||
});
|
||||
|
||||
const updateStateWithNewResultsInfo = useCallback(
|
||||
(resultsInfo: ResultsInfo): void => {
|
||||
let results: Results | null = null;
|
||||
@@ -110,6 +115,10 @@ export function ResultsApp() {
|
||||
const handleMessage = useCallback(
|
||||
(msg: IntoResultsViewMsg): void => {
|
||||
switch (msg.t) {
|
||||
case "setUserSettings":
|
||||
setUserSettings(msg.userSettings);
|
||||
break;
|
||||
|
||||
case "setState":
|
||||
updateStateWithNewResultsInfo({
|
||||
resultsPath: msg.resultsPath,
|
||||
@@ -217,6 +226,7 @@ export function ResultsApp() {
|
||||
? displayedResults.resultsInfo.interpretation
|
||||
: undefined
|
||||
}
|
||||
userSettings={userSettings}
|
||||
database={displayedResults.results.database}
|
||||
origResultsPaths={displayedResults.resultsInfo.origResultsPaths}
|
||||
resultsPath={displayedResults.resultsInfo.resultsPath}
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { UrlValueResolvable } from "../../../common/raw-result-types";
|
||||
interface Props {
|
||||
loc: UrlValueResolvable;
|
||||
label: string;
|
||||
databaseUri: string;
|
||||
databaseUri: string | undefined;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,17 +26,13 @@ export function Location({
|
||||
const displayLabel = useMemo(() => convertNonPrintableChars(label), [label]);
|
||||
|
||||
if (loc === undefined) {
|
||||
return <NonClickableLocation msg={displayLabel} />;
|
||||
return <NonClickableLocation msg={displayLabel} locationHint={title} />;
|
||||
}
|
||||
|
||||
if (loc.type === "string") {
|
||||
return <a href={loc.value}>{loc.value}</a>;
|
||||
}
|
||||
|
||||
if (databaseUri === undefined) {
|
||||
return <NonClickableLocation msg={displayLabel} locationHint={title} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<ClickableLocation
|
||||
loc={loc}
|
||||
|
||||
@@ -8,7 +8,7 @@ interface Props {
|
||||
text?: string;
|
||||
loc?: SarifLogLocation;
|
||||
sourceLocationPrefix: string;
|
||||
databaseUri: string;
|
||||
databaseUri: string | undefined;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import type {
|
||||
QueryMetadata,
|
||||
RawResultsSortState,
|
||||
ResultSet,
|
||||
UserSettings,
|
||||
} from "../../common/interface-types";
|
||||
import { SortDirection } from "../../common/interface-types";
|
||||
import { assertNever } from "../../common/helpers-pure";
|
||||
@@ -11,6 +12,7 @@ import type { UrlValueResolvable } from "../../common/raw-result-types";
|
||||
export interface ResultTableProps {
|
||||
resultSet: ResultSet;
|
||||
databaseUri: string;
|
||||
userSettings: UserSettings;
|
||||
metadata?: QueryMetadata;
|
||||
resultsPath: string | undefined;
|
||||
sortState?: RawResultsSortState;
|
||||
@@ -41,7 +43,7 @@ export const selectedRowClassName = "vscode-codeql__result-table-row--selected";
|
||||
|
||||
export function jumpToLocation(
|
||||
loc: UrlValueResolvable,
|
||||
databaseUri: string,
|
||||
databaseUri: string | undefined,
|
||||
): void {
|
||||
vscode.postMessage({
|
||||
t: "viewSourceFile",
|
||||
|
||||
@@ -144,3 +144,12 @@ td.vscode-codeql__path-index-cell {
|
||||
.vscode-codeql__location-cell {
|
||||
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