Skip to content

Commit 6eb873d

Browse files
committed
Change sorting interface to table header on alerts.
1 parent 68f14d1 commit 6eb873d

File tree

8 files changed

+96
-61
lines changed

8 files changed

+96
-61
lines changed

extensions/ql-vscode/src/interface-types.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export interface PreviousExecution {
3434
export interface Interpretation {
3535
sourceLocationPrefix: string;
3636
numTruncatedResults: number;
37-
sortState: InterpretedResultsSortState;
37+
sortState?: InterpretedResultsSortState;
3838
sarif: sarif.Log;
3939
}
4040

@@ -120,11 +120,12 @@ export interface RawResultsSortState {
120120
direction: SortDirection;
121121
}
122122

123-
export type InterpretedResultsSortOrder =
123+
export type InterpretedResultsSortColumn =
124124
'file-position' | 'alert-message';
125125

126126
export interface InterpretedResultsSortState {
127-
sortBy: InterpretedResultsSortOrder;
127+
sortBy: InterpretedResultsSortColumn;
128+
sortDirection: SortDirection;
128129
}
129130

130131
interface ChangeRawResultsSortMsg {

extensions/ql-vscode/src/interface.ts

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { CodeQLCliServer } from './cli';
1010
import { DatabaseItem, DatabaseManager } from './databases';
1111
import { showAndLogErrorMessage } from './helpers';
1212
import { assertNever } from './helpers-pure';
13-
import { FromResultsViewMsg, Interpretation, INTERPRETED_RESULTS_PER_RUN_LIMIT, IntoResultsViewMsg, QueryMetadata, ResultsPaths, SortedResultSetInfo, SortedResultsMap, InterpretedResultsSortState } from './interface-types';
13+
import { FromResultsViewMsg, Interpretation, INTERPRETED_RESULTS_PER_RUN_LIMIT, IntoResultsViewMsg, QueryMetadata, ResultsPaths, SortedResultSetInfo, SortedResultsMap, InterpretedResultsSortState, SortDirection } from './interface-types';
1414
import { Logger } from './logging';
1515
import * as messages from './messages';
1616
import { CompletedQuery, interpretResults } from './query-results';
@@ -86,19 +86,28 @@ export function webviewUriToFileUri(webviewUri: string): Uri {
8686
return Uri.file(path);
8787
}
8888

89-
function sortInterpretedResults(results: Sarif.Result[], sortState: InterpretedResultsSortState): void {
90-
switch (sortState.sortBy) {
91-
case 'alert-message':
92-
results.sort((a, b) =>
93-
a.message.text === undefined ? 0 :
94-
b.message.text === undefined ? 0 :
95-
a.message.text?.localeCompare(b.message.text));
96-
break;
97-
case 'file-position':
98-
// default to the order found in the sarif file
99-
break;
100-
default:
101-
assertNever(sortState.sortBy);
89+
function sortInterpretedResults(results: Sarif.Result[], sortState: InterpretedResultsSortState | undefined): void {
90+
function locToString(locs: Sarif.Location[] | undefined): string {
91+
if (locs === undefined) return '';
92+
return JSON.stringify(locs[0]) || '';
93+
}
94+
95+
if (sortState !== undefined) {
96+
const direction = sortState.sortDirection === SortDirection.asc ? 1 : -1;
97+
switch (sortState.sortBy) {
98+
case 'alert-message':
99+
results.sort((a, b) =>
100+
a.message.text === undefined ? 0 :
101+
b.message.text === undefined ? 0 :
102+
direction * (a.message.text?.localeCompare(b.message.text)));
103+
break;
104+
case 'file-position':
105+
results.sort((a, b) =>
106+
direction * locToString(a.locations).localeCompare(locToString(b.locations)));
107+
break;
108+
default:
109+
assertNever(sortState.sortBy);
110+
}
102111
}
103112
}
104113

@@ -295,7 +304,7 @@ export class InterfaceManager extends DisposableObject {
295304
});
296305
}
297306

298-
private async getTruncatedResults(metadata: QueryMetadata | undefined, resultsPaths: ResultsPaths, sourceInfo: cli.SourceInfo | undefined, sourceLocationPrefix: string, sortState: InterpretedResultsSortState): Promise<Interpretation> {
307+
private async getTruncatedResults(metadata: QueryMetadata | undefined, resultsPaths: ResultsPaths, sourceInfo: cli.SourceInfo | undefined, sourceLocationPrefix: string, sortState: InterpretedResultsSortState | undefined): Promise<Interpretation> {
299308
const sarif = await interpretResults(this.cliServer, metadata, resultsPaths.resultsPath, sourceInfo);
300309
// For performance reasons, limit the number of results we try
301310
// to serialize and send to the webview. TODO: possibly also
@@ -317,7 +326,7 @@ export class InterfaceManager extends DisposableObject {
317326
return { sarif, sourceLocationPrefix, numTruncatedResults, sortState };
318327
}
319328

320-
private async interpretResultsInfo(query: QueryInfo, sortState: InterpretedResultsSortState): Promise<Interpretation | undefined> {
329+
private async interpretResultsInfo(query: QueryInfo, sortState: InterpretedResultsSortState | undefined): Promise<Interpretation | undefined> {
321330
let interpretation: Interpretation | undefined = undefined;
322331
if (await query.hasInterpretedResults()
323332
&& query.quickEvalPosition === undefined // never do results interpretation if quickEval
@@ -351,7 +360,7 @@ export class InterfaceManager extends DisposableObject {
351360
resultsInfo,
352361
sourceInfo,
353362
sourceLocationPrefix,
354-
{ sortBy: 'file-position' } // sort order doesn't matter for showing diagnostics in parallel
363+
undefined,
355364
);
356365

357366
try {

extensions/ql-vscode/src/query-results.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@ export class CompletedQuery implements QueryWithResults {
2525
* How we're currently sorting alerts. This is not mere interface
2626
* state due to truncation; on re-sort, we want to read in the file
2727
* again, sort it, and only ship off a reasonable number of results
28-
* to the webview.
28+
* to the webview. Undefined means to use whatever order is in the
29+
* sarif file.
2930
*/
30-
interpretedResultsSortState: InterpretedResultsSortState = { sortBy: 'file-position' };
31+
interpretedResultsSortState: InterpretedResultsSortState | undefined;
3132

3233
constructor(
3334
evalaution: QueryWithResults,

extensions/ql-vscode/src/view/alert-table.tsx

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ import * as Sarif from 'sarif';
44
import * as Keys from '../result-keys';
55
import { LocationStyle } from 'semmle-bqrs';
66
import * as octicons from './octicons';
7-
import { className, renderLocation, ResultTableProps, zebraStripe, selectableZebraStripe, jumpToLocation } from './result-table-utils';
8-
import { PathTableResultSet, onNavigation, NavigationEvent } from './results';
7+
import { className, renderLocation, ResultTableProps, zebraStripe, selectableZebraStripe, jumpToLocation, nextSortDirection } from './result-table-utils';
8+
import { PathTableResultSet, onNavigation, NavigationEvent, vscode } from './results';
99
import { parseSarifPlainTextMessage, parseSarifLocation } from '../sarif-utils';
10+
import { InterpretedResultsSortColumn, SortDirection, InterpretedResultsSortState } from '../interface-types';
1011

1112
export type PathTableProps = ResultTableProps & { resultSet: PathTableResultSet };
1213
export interface PathTableState {
@@ -43,9 +44,40 @@ export class PathTable extends React.Component<PathTableProps, PathTableState> {
4344
e.preventDefault();
4445
}
4546

47+
sortClass(column: InterpretedResultsSortColumn): string {
48+
const sortState = this.props.resultSet.sortState;
49+
if (sortState !== undefined && sortState.sortBy === column) {
50+
return sortState.sortDirection === SortDirection.asc ? 'sort-asc' : 'sort-desc';
51+
}
52+
else {
53+
return 'sort-none';
54+
}
55+
}
56+
57+
toggleSortStateForColumn(column: InterpretedResultsSortColumn): void {
58+
const oldSortState = this.props.resultSet.sortState;
59+
const prevDirection = oldSortState && oldSortState.sortBy === column ? oldSortState.sortDirection : undefined;
60+
const nextDirection = nextSortDirection(prevDirection);
61+
const sortState: InterpretedResultsSortState | undefined =
62+
nextDirection === undefined ? undefined :
63+
{ sortBy: column, sortDirection: nextDirection };
64+
vscode.postMessage({
65+
t: 'changeInterpretedSort',
66+
sortState,
67+
});
68+
}
69+
4670
render(): JSX.Element {
4771
const { databaseUri, resultSet } = this.props;
4872

73+
const header = <thead>
74+
<tr>
75+
<th colSpan={2}></th>
76+
<th className={this.sortClass('alert-message') + ' vscode-codeql__alert-message-cell'} colSpan={2} onClick={() => this.toggleSortStateForColumn('alert-message')}>Message</th>
77+
<th className={this.sortClass('file-position') + ' vscode-codeql__location-cell'} onClick={() => this.toggleSortStateForColumn('file-position')}>Location</th>
78+
</tr>
79+
</thead>;
80+
4981
const rows: JSX.Element[] = [];
5082
const { numTruncatedResults, sourceLocationPrefix } = resultSet;
5183

@@ -65,7 +97,7 @@ export class PathTable extends React.Component<PathTableProps, PathTableState> {
6597
result.push(<span>{part} </span>);
6698
} else {
6799
const renderedLocation = renderSarifLocationWithText(part.text, relatedLocationsById[part.dest],
68-
undefined);
100+
undefined);
69101
result.push(<span>{renderedLocation} </span>);
70102
}
71103
} return result;
@@ -93,7 +125,7 @@ export class PathTable extends React.Component<PathTableProps, PathTableState> {
93125
return renderNonLocation(text, parsedLoc.hint);
94126
case LocationStyle.FivePart:
95127
case LocationStyle.WholeFile:
96-
return renderLocation(parsedLoc, text, databaseUri, undefined, updateSelectionCallback(pathNodeKey));
128+
return renderLocation(parsedLoc, text, databaseUri, undefined, updateSelectionCallback(pathNodeKey));
97129
}
98130
return undefined;
99131
}
@@ -231,6 +263,7 @@ export class PathTable extends React.Component<PathTableProps, PathTableState> {
231263
}
232264

233265
return <table className={className}>
266+
{header}
234267
<tbody>{rows}</tbody>
235268
</table>;
236269
}

extensions/ql-vscode/src/view/raw-results-table.tsx

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import * as React from "react";
2-
import { renderLocation, ResultTableProps, zebraStripe, className } from "./result-table-utils";
2+
import { renderLocation, ResultTableProps, zebraStripe, className, nextSortDirection } from "./result-table-utils";
33
import { RawTableResultSet, ResultValue, vscode } from "./results";
4-
import { assertNever } from "../helpers-pure";
54
import { SortDirection, RAW_RESULTS_LIMIT, RawResultsSortState } from "../interface-types";
65

76
export type RawTableProps = ResultTableProps & {
@@ -84,7 +83,6 @@ export class RawTable extends React.Component<RawTableProps, {}> {
8483
}
8584
}
8685

87-
8886
/**
8987
* Render one column of a tuple.
9088
*/
@@ -99,15 +97,3 @@ function renderTupleValue(v: ResultValue, databaseUri: string): JSX.Element {
9997
return renderLocation(v.location, v.label, databaseUri);
10098
}
10199
}
102-
103-
function nextSortDirection(direction: SortDirection | undefined): SortDirection {
104-
switch (direction) {
105-
case SortDirection.asc:
106-
return SortDirection.desc;
107-
case SortDirection.desc:
108-
case undefined:
109-
return SortDirection.asc;
110-
default:
111-
return assertNever(direction);
112-
}
113-
}

extensions/ql-vscode/src/view/result-table-utils.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import * as React from 'react';
22
import { LocationValue, ResolvableLocationValue, tryGetResolvableLocation } from 'semmle-bqrs';
3-
import { RawResultsSortState, QueryMetadata } from '../interface-types';
3+
import { RawResultsSortState, QueryMetadata, SortDirection } from '../interface-types';
44
import { ResultSet, vscode } from './results';
5+
import { assertNever } from '../helpers-pure';
56

67
export interface ResultTableProps {
78
resultSet: ResultSet;
@@ -84,3 +85,18 @@ export function selectableZebraStripe(isSelected: boolean, index: number, ...oth
8485
? { className: [selectedRowClassName, ...otherClasses].join(' ') }
8586
: zebraStripe(index, ...otherClasses)
8687
}
88+
89+
/**
90+
* Returns the next sort direction when cycling through sort directions while clicking.
91+
*/
92+
export function nextSortDirection(direction: SortDirection | undefined): SortDirection {
93+
switch (direction) {
94+
case SortDirection.asc:
95+
return SortDirection.desc;
96+
case SortDirection.desc:
97+
case undefined:
98+
return SortDirection.asc;
99+
default:
100+
return assertNever(direction);
101+
}
102+
}

extensions/ql-vscode/src/view/result-tables.tsx

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as React from 'react';
2-
import { DatabaseInfo, Interpretation, RawResultsSortState, QueryMetadata, ResultsPaths, InterpretedResultsSortOrder, InterpretedResultsSortState } from '../interface-types';
2+
import { DatabaseInfo, Interpretation, RawResultsSortState, QueryMetadata, ResultsPaths, InterpretedResultsSortState } from '../interface-types';
33
import { PathTable } from './alert-table';
44
import { RawTable } from './raw-results-table';
55
import { ResultTableProps, tableSelectionHeaderClassName, toggleDiagnosticsClassName, alertExtrasClassName } from './result-table-utils';
@@ -94,15 +94,8 @@ export class ResultTables
9494
this.setState({ selectedTable: event.target.value });
9595
}
9696

97-
private onSortChange = (event: React.ChangeEvent<HTMLSelectElement>): void => {
98-
vscode.postMessage({
99-
t: 'changeInterpretedSort',
100-
sortState: { sortBy: event.target.value as InterpretedResultsSortOrder },
101-
});
102-
}
103-
10497
private alertTableExtras(): JSX.Element | undefined {
105-
const { database, resultsPath, metadata, origResultsPaths, interpretedSortState } = this.props;
98+
const { database, resultsPath, metadata, origResultsPaths } = this.props;
10699

107100
const displayProblemsAsAlertsToggle =
108101
<div className={toggleDiagnosticsClassName}>
@@ -120,15 +113,7 @@ export class ResultTables
120113
<label htmlFor="toggle-diagnostics">Show results in Problems view</label>
121114
</div>;
122115

123-
const interpretedResultsSortSelect = <select value={interpretedSortState?.sortBy || 'file-position'}
124-
onChange={this.onSortChange}>
125-
<option value={'file-position'}>Source File Position</option>
126-
<option value={'alert-message'}>Alert Message</option>
127-
</select>;
128-
129116
return <div className={alertExtrasClassName}>
130-
Sort:
131-
{interpretedResultsSortSelect}
132117
{displayProblemsAsAlertsToggle}
133118
</div>
134119
}

extensions/ql-vscode/src/view/resultsView.css

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,12 @@ td.vscode-codeql__path-index-cell {
112112
text-align: right;
113113
}
114114

115-
td.vscode-codeql__location-cell {
116-
text-align: right;
115+
.vscode-codeql__alert-message-cell {
116+
text-align: left !important;
117+
}
118+
119+
.vscode-codeql__location-cell {
120+
text-align: right !important;
117121
}
118122

119123
.vscode-codeql__vertical-rule {

0 commit comments

Comments
 (0)