Skip to content

Commit 5fc3424

Browse files
Merge pull request #2732 from github/robertbrignull/AlertTable-decompose
Split AlertTable into smaller components
2 parents 3094405 + bee7d81 commit 5fc3424

File tree

6 files changed

+379
-247
lines changed

6 files changed

+379
-247
lines changed

extensions/ql-vscode/src/view/results/AlertTable.tsx

Lines changed: 30 additions & 247 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
import * as React from "react";
22
import * as Sarif from "sarif";
33
import * as Keys from "./result-keys";
4-
import { info, listUnordered } from "./octicons";
54
import {
65
className,
76
ResultTableProps,
8-
selectableZebraStripe,
97
jumpToLocation,
108
} from "./result-table-utils";
119
import { onNavigation } from "./ResultsApp";
@@ -19,11 +17,9 @@ import { parseSarifLocation, isNoLocation } from "../../common/sarif-utils";
1917
import { ScrollIntoViewHelper } from "./scroll-into-view-helper";
2018
import { sendTelemetry } from "../common/telemetry";
2119
import { AlertTableHeader } from "./AlertTableHeader";
22-
import { SarifMessageWithLocations } from "./locations/SarifMessageWithLocations";
23-
import { SarifLocation } from "./locations/SarifLocation";
24-
import { EmptyQueryResultsMessage } from "./EmptyQueryResultsMessage";
25-
import TextButton from "../common/TextButton";
26-
import { AlertTableDropdownIndicatorCell } from "./AlertTableDropdownIndicatorCell";
20+
import { AlertTableNoResults } from "./AlertTableNoResults";
21+
import { AlertTableTruncatedMessage } from "./AlertTableTruncatedMessage";
22+
import { AlertTableResultRow } from "./AlertTableResultRow";
2723

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

73-
renderNoResults(): JSX.Element {
74-
if (this.props.nonemptyRawResults) {
75-
return (
76-
<span>
77-
No Alerts. See{" "}
78-
<TextButton onClick={this.props.showRawResults}>
79-
raw results
80-
</TextButton>
81-
.
82-
</span>
83-
);
84-
} else {
85-
return <EmptyQueryResultsMessage />;
86-
}
87-
}
88-
8969
render(): JSX.Element {
9070
const { databaseUri, resultSet } = this.props;
9171

92-
const rows: JSX.Element[] = [];
9372
const { numTruncatedResults, sourceLocationPrefix } =
9473
resultSet.interpretation;
9574

9675
const updateSelectionCallback = (
9776
resultKey: Keys.PathNode | Keys.Result | undefined,
9877
) => {
99-
return () => {
100-
this.setState((previousState) => ({
101-
...previousState,
102-
selectedItem: resultKey,
103-
}));
104-
sendTelemetry("local-results-alert-table-path-selected");
105-
};
106-
};
107-
108-
const toggler: (keys: Keys.ResultKey[]) => (e: React.MouseEvent) => void = (
109-
indices,
110-
) => {
111-
return (e) => this.toggle(e, indices);
78+
this.setState((previousState) => ({
79+
...previousState,
80+
selectedItem: resultKey,
81+
}));
82+
sendTelemetry("local-results-alert-table-path-selected");
11283
};
11384

11485
if (!resultSet.interpretation.data.runs?.[0]?.results?.length) {
115-
return this.renderNoResults();
116-
}
117-
118-
resultSet.interpretation.data.runs[0].results.forEach(
119-
(result, resultIndex) => {
120-
const resultKey: Keys.Result = { resultIndex };
121-
const text = result.message.text || "[no text]";
122-
const msg =
123-
result.relatedLocations === undefined ? (
124-
<span key="0">{text}</span>
125-
) : (
126-
<SarifMessageWithLocations
127-
msg={text}
128-
relatedLocations={result.relatedLocations}
129-
sourceLocationPrefix={sourceLocationPrefix}
130-
databaseUri={databaseUri}
131-
onClick={updateSelectionCallback(resultKey)}
132-
/>
133-
);
134-
135-
const currentResultExpanded = this.state.expanded.has(
136-
Keys.keyToString(resultKey),
137-
);
138-
const location = result.locations !== undefined &&
139-
result.locations.length > 0 && (
140-
<SarifLocation
141-
loc={result.locations[0]}
142-
sourceLocationPrefix={sourceLocationPrefix}
143-
databaseUri={databaseUri}
144-
onClick={updateSelectionCallback(resultKey)}
145-
/>
146-
);
147-
const locationCells = (
148-
<td className="vscode-codeql__location-cell">{location}</td>
149-
);
150-
151-
const selectedItem = this.state.selectedItem;
152-
const resultRowIsSelected =
153-
selectedItem?.resultIndex === resultIndex &&
154-
selectedItem.pathIndex === undefined;
155-
156-
if (result.codeFlows === undefined) {
157-
rows.push(
158-
<tr
159-
ref={this.scroller.ref(resultRowIsSelected)}
160-
key={resultIndex}
161-
{...selectableZebraStripe(resultRowIsSelected, resultIndex)}
162-
>
163-
<td className="vscode-codeql__icon-cell">{info}</td>
164-
<td colSpan={3}>{msg}</td>
165-
{locationCells}
166-
</tr>,
167-
);
168-
} else {
169-
const paths: Sarif.ThreadFlow[] = Keys.getAllPaths(result);
170-
171-
const indices =
172-
paths.length === 1
173-
? [resultKey, { ...resultKey, pathIndex: 0 }]
174-
: /* if there's exactly one path, auto-expand
175-
* the path when expanding the result */
176-
[resultKey];
177-
178-
rows.push(
179-
<tr
180-
ref={this.scroller.ref(resultRowIsSelected)}
181-
{...selectableZebraStripe(resultRowIsSelected, resultIndex)}
182-
key={resultIndex}
183-
>
184-
<AlertTableDropdownIndicatorCell
185-
expanded={currentResultExpanded}
186-
onClick={toggler(indices)}
187-
/>
188-
<td className="vscode-codeql__icon-cell">{listUnordered}</td>
189-
<td colSpan={2}>{msg}</td>
190-
{locationCells}
191-
</tr>,
192-
);
193-
194-
paths.forEach((path, pathIndex) => {
195-
const pathKey = { resultIndex, pathIndex };
196-
const currentPathExpanded = this.state.expanded.has(
197-
Keys.keyToString(pathKey),
198-
);
199-
if (currentResultExpanded) {
200-
const isPathSpecificallySelected = Keys.equalsNotUndefined(
201-
pathKey,
202-
selectedItem,
203-
);
204-
rows.push(
205-
<tr
206-
ref={this.scroller.ref(isPathSpecificallySelected)}
207-
{...selectableZebraStripe(
208-
isPathSpecificallySelected,
209-
resultIndex,
210-
)}
211-
key={`${resultIndex}-${pathIndex}`}
212-
>
213-
<td className="vscode-codeql__icon-cell">
214-
<span className="vscode-codeql__vertical-rule"></span>
215-
</td>
216-
<AlertTableDropdownIndicatorCell
217-
expanded={currentPathExpanded}
218-
onClick={toggler([pathKey])}
219-
/>
220-
<td className="vscode-codeql__text-center" colSpan={3}>
221-
Path
222-
</td>
223-
</tr>,
224-
);
225-
}
226-
227-
if (currentResultExpanded && currentPathExpanded) {
228-
const pathNodes = path.locations;
229-
for (
230-
let pathNodeIndex = 0;
231-
pathNodeIndex < pathNodes.length;
232-
++pathNodeIndex
233-
) {
234-
const pathNodeKey: Keys.PathNode = {
235-
...pathKey,
236-
pathNodeIndex,
237-
};
238-
const step = pathNodes[pathNodeIndex];
239-
const msg =
240-
step.location !== undefined &&
241-
step.location.message !== undefined ? (
242-
<SarifLocation
243-
text={step.location.message.text}
244-
loc={step.location}
245-
sourceLocationPrefix={sourceLocationPrefix}
246-
databaseUri={databaseUri}
247-
onClick={updateSelectionCallback(pathNodeKey)}
248-
/>
249-
) : (
250-
"[no location]"
251-
);
252-
const additionalMsg =
253-
step.location !== undefined ? (
254-
<SarifLocation
255-
loc={step.location}
256-
sourceLocationPrefix={sourceLocationPrefix}
257-
databaseUri={databaseUri}
258-
onClick={updateSelectionCallback(pathNodeKey)}
259-
/>
260-
) : (
261-
""
262-
);
263-
const isSelected = Keys.equalsNotUndefined(
264-
this.state.selectedItem,
265-
pathNodeKey,
266-
);
267-
const stepIndex = pathNodeIndex + 1; // Convert to 1-based
268-
const zebraIndex = resultIndex + stepIndex;
269-
rows.push(
270-
<tr
271-
ref={this.scroller.ref(isSelected)}
272-
className={
273-
isSelected
274-
? "vscode-codeql__selected-path-node"
275-
: undefined
276-
}
277-
key={`${resultIndex}-${pathIndex}-${pathNodeIndex}`}
278-
>
279-
<td className="vscode-codeql__icon-cell">
280-
<span className="vscode-codeql__vertical-rule"></span>
281-
</td>
282-
<td className="vscode-codeql__icon-cell">
283-
<span className="vscode-codeql__vertical-rule"></span>
284-
</td>
285-
<td
286-
{...selectableZebraStripe(
287-
isSelected,
288-
zebraIndex,
289-
"vscode-codeql__path-index-cell",
290-
)}
291-
>
292-
{stepIndex}
293-
</td>
294-
<td {...selectableZebraStripe(isSelected, zebraIndex)}>
295-
{msg}{" "}
296-
</td>
297-
<td
298-
{...selectableZebraStripe(
299-
isSelected,
300-
zebraIndex,
301-
"vscode-codeql__location-cell",
302-
)}
303-
>
304-
{additionalMsg}
305-
</td>
306-
</tr>,
307-
);
308-
}
309-
}
310-
});
311-
}
312-
},
313-
);
314-
315-
if (numTruncatedResults > 0) {
316-
rows.push(
317-
<tr key="truncatd-message">
318-
<td colSpan={5} style={{ textAlign: "center", fontStyle: "italic" }}>
319-
Too many results to show at once. {numTruncatedResults} result(s)
320-
omitted.
321-
</td>
322-
</tr>,
323-
);
86+
return <AlertTableNoResults {...this.props} />;
32487
}
32588

32689
return (
32790
<table className={className}>
32891
<AlertTableHeader sortState={resultSet.interpretation.data.sortState} />
329-
<tbody>{rows}</tbody>
92+
<tbody>
93+
{resultSet.interpretation.data.runs[0].results.map(
94+
(result, resultIndex) => (
95+
<AlertTableResultRow
96+
key={resultIndex}
97+
result={result}
98+
resultIndex={resultIndex}
99+
expanded={this.state.expanded}
100+
selectedItem={this.state.selectedItem}
101+
databaseUri={databaseUri}
102+
sourceLocationPrefix={sourceLocationPrefix}
103+
updateSelectionCallback={updateSelectionCallback}
104+
toggleExpanded={this.toggle.bind(this)}
105+
scroller={this.scroller}
106+
/>
107+
),
108+
)}
109+
<AlertTableTruncatedMessage
110+
numTruncatedResults={numTruncatedResults}
111+
/>
112+
</tbody>
330113
</table>
331114
);
332115
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import * as React from "react";
2+
import { EmptyQueryResultsMessage } from "./EmptyQueryResultsMessage";
3+
import TextButton from "../common/TextButton";
4+
5+
interface Props {
6+
nonemptyRawResults: boolean;
7+
showRawResults: () => void;
8+
}
9+
10+
export function AlertTableNoResults(props: Props): JSX.Element {
11+
if (props.nonemptyRawResults) {
12+
return (
13+
<span>
14+
No Alerts. See{" "}
15+
<TextButton onClick={props.showRawResults}>raw results</TextButton>.
16+
</span>
17+
);
18+
} else {
19+
return <EmptyQueryResultsMessage />;
20+
}
21+
}

0 commit comments

Comments
 (0)