Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
ca40963
Move AlertTableNoResults to a new component in a new file
robertbrignull Aug 15, 2023
cff7170
Move AlertTableTruncatedMessage to a new component in a new file
robertbrignull Aug 16, 2023
7e31b6e
Construct rows using maps instead of mutating array from multiple places
robertbrignull Aug 16, 2023
91e69c9
Pull out AlertTablePathNodeRow to a separate component
robertbrignull Aug 16, 2023
443c9ac
Inline some rendering in AlertTablePathNodeRow
robertbrignull Aug 16, 2023
ba0cf9a
Use useMemo for callback passed to SarifLocation.onClick in AlertTabl…
robertbrignull Aug 16, 2023
acb76a6
Pull out AlertTablePathRow to a separate component
robertbrignull Aug 16, 2023
3e7577c
Use useMemo for callback passed to AlertTableDropdownIndicatorCell.on…
robertbrignull Aug 16, 2023
a576dac
Do all rendering in AlertTablePathRow in one place
robertbrignull Aug 16, 2023
8945abc
Pull out AlertTableResultRow to a separate component
robertbrignull Aug 16, 2023
23d815f
Use useMemo for callback passed to AlertTableDropdownIndicatorCell.on…
robertbrignull Aug 16, 2023
1794c6b
Simplify rendering in AlertTable
robertbrignull Aug 16, 2023
fe4d87d
Teach AlertTableResultRow how to render results without codeFlows
robertbrignull Aug 16, 2023
636d0bf
Move rendering of locationCells to inside AlertTableResuiltRow
robertbrignull Aug 16, 2023
3983087
Use useMemo for callback passed to SarifLocation.onClick in AlertTabl…
robertbrignull Aug 16, 2023
9ecf971
Move rendering of msg to inside AlertTableResuiltRow
robertbrignull Aug 16, 2023
21b1c99
Move rendering of AlertTablePathRow to inside of AlertTableResultRow
robertbrignull Aug 16, 2023
e3596db
Remove rows array and render AlertTableResultRow inline
robertbrignull Aug 16, 2023
bd5d361
Pass around toggle instead of toggler
robertbrignull Aug 16, 2023
eebe11e
Simplify passing similar props
robertbrignull Aug 21, 2023
d65ce3e
Use useCallback when calling toggleExpanded
robertbrignull Aug 21, 2023
af1af07
Convert updateSelectionCallback to not use double-nested methods
robertbrignull Aug 21, 2023
bee7d81
Merge branch 'main' into robertbrignull/AlertTable-decompose
robertbrignull Aug 22, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
277 changes: 30 additions & 247 deletions extensions/ql-vscode/src/view/results/AlertTable.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import * as React from "react";
import * as Sarif from "sarif";
import * as Keys from "./result-keys";
import { info, listUnordered } from "./octicons";
import {
className,
ResultTableProps,
selectableZebraStripe,
jumpToLocation,
} from "./result-table-utils";
import { onNavigation } from "./ResultsApp";
Expand All @@ -19,11 +17,9 @@ import { parseSarifLocation, isNoLocation } from "../../common/sarif-utils";
import { ScrollIntoViewHelper } from "./scroll-into-view-helper";
import { sendTelemetry } from "../common/telemetry";
import { AlertTableHeader } from "./AlertTableHeader";
import { SarifMessageWithLocations } from "./locations/SarifMessageWithLocations";
import { SarifLocation } from "./locations/SarifLocation";
import { EmptyQueryResultsMessage } from "./EmptyQueryResultsMessage";
import TextButton from "../common/TextButton";
import { AlertTableDropdownIndicatorCell } from "./AlertTableDropdownIndicatorCell";
import { AlertTableNoResults } from "./AlertTableNoResults";
import { AlertTableTruncatedMessage } from "./AlertTableTruncatedMessage";
import { AlertTableResultRow } from "./AlertTableResultRow";

type AlertTableProps = ResultTableProps & {
resultSet: InterpretedResultSet<SarifInterpretationData>;
Expand Down Expand Up @@ -70,263 +66,50 @@ export class AlertTable extends React.Component<
e.preventDefault();
}

renderNoResults(): JSX.Element {
if (this.props.nonemptyRawResults) {
return (
<span>
No Alerts. See{" "}
<TextButton onClick={this.props.showRawResults}>
raw results
</TextButton>
.
</span>
);
} else {
return <EmptyQueryResultsMessage />;
}
}

render(): JSX.Element {
const { databaseUri, resultSet } = this.props;

const rows: JSX.Element[] = [];
const { numTruncatedResults, sourceLocationPrefix } =
resultSet.interpretation;

const updateSelectionCallback = (
resultKey: Keys.PathNode | Keys.Result | undefined,
) => {
return () => {
this.setState((previousState) => ({
...previousState,
selectedItem: resultKey,
}));
sendTelemetry("local-results-alert-table-path-selected");
};
};

const toggler: (keys: Keys.ResultKey[]) => (e: React.MouseEvent) => void = (
indices,
) => {
return (e) => this.toggle(e, indices);
this.setState((previousState) => ({
...previousState,
selectedItem: resultKey,
}));
sendTelemetry("local-results-alert-table-path-selected");
};

if (!resultSet.interpretation.data.runs?.[0]?.results?.length) {
return this.renderNoResults();
}

resultSet.interpretation.data.runs[0].results.forEach(
(result, resultIndex) => {
const resultKey: Keys.Result = { resultIndex };
const text = result.message.text || "[no text]";
const msg =
result.relatedLocations === undefined ? (
<span key="0">{text}</span>
) : (
<SarifMessageWithLocations
msg={text}
relatedLocations={result.relatedLocations}
sourceLocationPrefix={sourceLocationPrefix}
databaseUri={databaseUri}
onClick={updateSelectionCallback(resultKey)}
/>
);

const currentResultExpanded = this.state.expanded.has(
Keys.keyToString(resultKey),
);
const location = result.locations !== undefined &&
result.locations.length > 0 && (
<SarifLocation
loc={result.locations[0]}
sourceLocationPrefix={sourceLocationPrefix}
databaseUri={databaseUri}
onClick={updateSelectionCallback(resultKey)}
/>
);
const locationCells = (
<td className="vscode-codeql__location-cell">{location}</td>
);

const selectedItem = this.state.selectedItem;
const resultRowIsSelected =
selectedItem?.resultIndex === resultIndex &&
selectedItem.pathIndex === undefined;

if (result.codeFlows === undefined) {
rows.push(
<tr
ref={this.scroller.ref(resultRowIsSelected)}
key={resultIndex}
{...selectableZebraStripe(resultRowIsSelected, resultIndex)}
>
<td className="vscode-codeql__icon-cell">{info}</td>
<td colSpan={3}>{msg}</td>
{locationCells}
</tr>,
);
} else {
const paths: Sarif.ThreadFlow[] = Keys.getAllPaths(result);

const indices =
paths.length === 1
? [resultKey, { ...resultKey, pathIndex: 0 }]
: /* if there's exactly one path, auto-expand
* the path when expanding the result */
[resultKey];

rows.push(
<tr
ref={this.scroller.ref(resultRowIsSelected)}
{...selectableZebraStripe(resultRowIsSelected, resultIndex)}
key={resultIndex}
>
<AlertTableDropdownIndicatorCell
expanded={currentResultExpanded}
onClick={toggler(indices)}
/>
<td className="vscode-codeql__icon-cell">{listUnordered}</td>
<td colSpan={2}>{msg}</td>
{locationCells}
</tr>,
);

paths.forEach((path, pathIndex) => {
const pathKey = { resultIndex, pathIndex };
const currentPathExpanded = this.state.expanded.has(
Keys.keyToString(pathKey),
);
if (currentResultExpanded) {
const isPathSpecificallySelected = Keys.equalsNotUndefined(
pathKey,
selectedItem,
);
rows.push(
<tr
ref={this.scroller.ref(isPathSpecificallySelected)}
{...selectableZebraStripe(
isPathSpecificallySelected,
resultIndex,
)}
key={`${resultIndex}-${pathIndex}`}
>
<td className="vscode-codeql__icon-cell">
<span className="vscode-codeql__vertical-rule"></span>
</td>
<AlertTableDropdownIndicatorCell
expanded={currentPathExpanded}
onClick={toggler([pathKey])}
/>
<td className="vscode-codeql__text-center" colSpan={3}>
Path
</td>
</tr>,
);
}

if (currentResultExpanded && currentPathExpanded) {
const pathNodes = path.locations;
for (
let pathNodeIndex = 0;
pathNodeIndex < pathNodes.length;
++pathNodeIndex
) {
const pathNodeKey: Keys.PathNode = {
...pathKey,
pathNodeIndex,
};
const step = pathNodes[pathNodeIndex];
const msg =
step.location !== undefined &&
step.location.message !== undefined ? (
<SarifLocation
text={step.location.message.text}
loc={step.location}
sourceLocationPrefix={sourceLocationPrefix}
databaseUri={databaseUri}
onClick={updateSelectionCallback(pathNodeKey)}
/>
) : (
"[no location]"
);
const additionalMsg =
step.location !== undefined ? (
<SarifLocation
loc={step.location}
sourceLocationPrefix={sourceLocationPrefix}
databaseUri={databaseUri}
onClick={updateSelectionCallback(pathNodeKey)}
/>
) : (
""
);
const isSelected = Keys.equalsNotUndefined(
this.state.selectedItem,
pathNodeKey,
);
const stepIndex = pathNodeIndex + 1; // Convert to 1-based
const zebraIndex = resultIndex + stepIndex;
rows.push(
<tr
ref={this.scroller.ref(isSelected)}
className={
isSelected
? "vscode-codeql__selected-path-node"
: undefined
}
key={`${resultIndex}-${pathIndex}-${pathNodeIndex}`}
>
<td className="vscode-codeql__icon-cell">
<span className="vscode-codeql__vertical-rule"></span>
</td>
<td className="vscode-codeql__icon-cell">
<span className="vscode-codeql__vertical-rule"></span>
</td>
<td
{...selectableZebraStripe(
isSelected,
zebraIndex,
"vscode-codeql__path-index-cell",
)}
>
{stepIndex}
</td>
<td {...selectableZebraStripe(isSelected, zebraIndex)}>
{msg}{" "}
</td>
<td
{...selectableZebraStripe(
isSelected,
zebraIndex,
"vscode-codeql__location-cell",
)}
>
{additionalMsg}
</td>
</tr>,
);
}
}
});
}
},
);

if (numTruncatedResults > 0) {
rows.push(
<tr key="truncatd-message">
<td colSpan={5} style={{ textAlign: "center", fontStyle: "italic" }}>
Too many results to show at once. {numTruncatedResults} result(s)
omitted.
</td>
</tr>,
);
return <AlertTableNoResults {...this.props} />;
}

return (
<table className={className}>
<AlertTableHeader sortState={resultSet.interpretation.data.sortState} />
<tbody>{rows}</tbody>
<tbody>
{resultSet.interpretation.data.runs[0].results.map(
(result, resultIndex) => (
<AlertTableResultRow
key={resultIndex}
result={result}
resultIndex={resultIndex}
expanded={this.state.expanded}
selectedItem={this.state.selectedItem}
databaseUri={databaseUri}
sourceLocationPrefix={sourceLocationPrefix}
updateSelectionCallback={updateSelectionCallback}
toggleExpanded={this.toggle.bind(this)}
scroller={this.scroller}
/>
),
)}
<AlertTableTruncatedMessage
numTruncatedResults={numTruncatedResults}
/>
</tbody>
</table>
);
}
Expand Down
21 changes: 21 additions & 0 deletions extensions/ql-vscode/src/view/results/AlertTableNoResults.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import * as React from "react";
import { EmptyQueryResultsMessage } from "./EmptyQueryResultsMessage";
import TextButton from "../common/TextButton";

interface Props {
nonemptyRawResults: boolean;
showRawResults: () => void;
}

export function AlertTableNoResults(props: Props): JSX.Element {
if (props.nonemptyRawResults) {
return (
<span>
No Alerts. See{" "}
<TextButton onClick={props.showRawResults}>raw results</TextButton>.
</span>
);
} else {
return <EmptyQueryResultsMessage />;
}
}
Loading