Skip to content

Commit ec168c7

Browse files
committed
Fix sort order and selection
This commit fixes two related issues with the history view. 1. Sort order was changing after a query item completed. The fix is a change in how we fire off the `onDidChangeTreeData` event. When the event is fired with a single item, that item is pushed to the top of the list. I'm not exactly sure why this wasn't happening before, but I suspect it was because we were refreshing the list at the same time as we were inserting the new item. The solution here is to always refresh the entire list, instead of single items. This is fine since re building the list is a trivial operation. See the `refreshTreeView()` method. With this change, the sort order is now stable. 2. Originally reported here: #1093 The problem is that the internal treeView selection was not being updated when a new item was being added. Due to some oddities with the way selection works in the tree view (ie- the visible selection does not always match the internal selection). The solution is to use the current item from the `treeDataProvider` in `determineSelection`. Also, this change makes the sorting more precise and fixes some typos.
1 parent db962a4 commit ec168c7

12 files changed

Lines changed: 64 additions & 56 deletions

File tree

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ export class CompareInterfaceManager extends DisposableObject {
9595
currentResultSetName: currentResultSetName,
9696
rows,
9797
message,
98-
datebaseUri: to.initialInfo.databaseInfo.databaseUri,
98+
databaseUri: to.initialInfo.databaseInfo.databaseUri,
9999
});
100100
}
101101
}

extensions/ql-vscode/src/compare/view/Compare.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const emptyComparison: SetComparisonsMessage = {
1717
columns: [],
1818
commonResultSetNames: [],
1919
currentResultSetName: '',
20-
datebaseUri: '',
20+
databaseUri: '',
2121
message: 'Empty comparison'
2222
};
2323

extensions/ql-vscode/src/compare/view/CompareTable.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ export default function CompareTable(props: Props) {
7676
schemaName={comparison.currentResultSetName}
7777
preventSort={true}
7878
/>
79-
{createRows(rows.from, comparison.datebaseUri)}
79+
{createRows(rows.from, comparison.databaseUri)}
8080
</table>
8181
</td>
8282
<td>
@@ -86,7 +86,7 @@ export default function CompareTable(props: Props) {
8686
schemaName={comparison.currentResultSetName}
8787
preventSort={true}
8888
/>
89-
{createRows(rows.to, comparison.datebaseUri)}
89+
{createRows(rows.to, comparison.databaseUri)}
9090
</table>
9191
</td>
9292
</tr>

extensions/ql-vscode/src/extension.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -499,8 +499,6 @@ async function activateWithInstalledDistribution(
499499
const initialInfo = await createInitialQueryInfo(selectedQuery, databaseInfo, quickEval, range);
500500
const item = new FullQueryInfo(initialInfo, queryHistoryConfigurationListener);
501501
qhm.addQuery(item);
502-
await qhm.refreshTreeView(item);
503-
504502
try {
505503
const completedQueryInfo = await compileAndRunQueryAgainstDatabase(
506504
cliServer,
@@ -518,7 +516,7 @@ async function activateWithInstalledDistribution(
518516
item.failureReason = e.message;
519517
throw e;
520518
} finally {
521-
await qhm.refreshTreeView(item);
519+
qhm.refreshTreeView();
522520
}
523521
}
524522
}

extensions/ql-vscode/src/interface.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import { Logger } from './logging';
3333
import * as messages from './pure/messages';
3434
import { commandRunner } from './commandRunner';
3535
import { CompletedQueryInfo, interpretResults } from './query-results';
36-
import { QueryEvaluatonInfo, tmpDir } from './run-queries';
36+
import { QueryEvaluationInfo, tmpDir } from './run-queries';
3737
import { parseSarifLocation, parseSarifPlainTextMessage } from './pure/sarif-utils';
3838
import {
3939
WebviewReveal,
@@ -644,7 +644,7 @@ export class InterfaceManager extends DisposableObject {
644644
}
645645

646646
private async interpretResultsInfo(
647-
query: QueryEvaluatonInfo,
647+
query: QueryEvaluationInfo,
648648
sortState: InterpretedResultsSortState | undefined
649649
): Promise<Interpretation | undefined> {
650650
if (

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ export interface SetComparisonsMessage {
316316
readonly currentResultSetName: string;
317317
readonly rows: QueryCompareResult | undefined;
318318
readonly message: string | undefined;
319-
readonly datebaseUri: string;
319+
readonly databaseUri: string;
320320
}
321321

322322
export enum DiffKind {

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

Lines changed: 40 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,6 @@ export class HistoryTreeDataProvider extends DisposableObject {
9696

9797
private localSuccessIconPath: string;
9898

99-
/**
100-
* When not undefined, must be reference-equal to an item in `this.databases`.
101-
*/
10299
private current: FullQueryInfo | undefined;
103100

104101
constructor(extensionPath: string) {
@@ -152,8 +149,8 @@ export class HistoryTreeDataProvider extends DisposableObject {
152149
element?: FullQueryInfo
153150
): ProviderResult<FullQueryInfo[]> {
154151
return element ? [] : this.history.sort((h1, h2) => {
155-
const q1 = h1.completedQuery;
156-
const q2 = h2.completedQuery;
152+
const resultCount1 = h1.completedQuery?.resultCount ?? -1;
153+
const resultCount2 = h2.completedQuery?.resultCount ?? -1;
157154

158155
switch (this.sortOrder) {
159156
case SortOrder.NameAsc:
@@ -165,9 +162,15 @@ export class HistoryTreeDataProvider extends DisposableObject {
165162
case SortOrder.DateDesc:
166163
return h2.initialInfo.start.getTime() - h1.initialInfo.start.getTime();
167164
case SortOrder.CountAsc:
168-
return (!q1 || !q2) ? 0 : q1.resultCount - q2.resultCount;
165+
// If the result counts are equal, sort by name.
166+
return resultCount1 - resultCount2 === 0
167+
? h1.label.localeCompare(h2.label, env.language)
168+
: resultCount1 - resultCount2;
169169
case SortOrder.CountDesc:
170-
return (!q1 || !q2) ? 0 : q2.resultCount - q1.resultCount;
170+
// If the result counts are equal, sort by name.
171+
return resultCount2 - resultCount1 === 0
172+
? h2.label.localeCompare(h1.label, env.language)
173+
: resultCount2 - resultCount1;
171174
default:
172175
assertNever(this.sortOrder);
173176
}
@@ -183,8 +186,8 @@ export class HistoryTreeDataProvider extends DisposableObject {
183186
}
184187

185188
pushQuery(item: FullQueryInfo): void {
186-
this.current = item;
187189
this.history.push(item);
190+
this.setCurrentItem(item);
188191
this.refresh();
189192
}
190193

@@ -193,16 +196,17 @@ export class HistoryTreeDataProvider extends DisposableObject {
193196
}
194197

195198
remove(item: FullQueryInfo) {
196-
if (this.current === item) {
197-
this.current = undefined;
199+
const isCurrent = this.current === item;
200+
if (isCurrent) {
201+
this.setCurrentItem();
198202
}
199203
const index = this.history.findIndex((i) => i === item);
200204
if (index >= 0) {
201205
this.history.splice(index, 1);
202-
if (this.current === undefined && this.history.length > 0) {
206+
if (isCurrent && this.history.length > 0) {
203207
// Try to keep a current item, near the deleted item if there
204208
// are any available.
205-
this.current = this.history[Math.min(index, this.history.length - 1)];
209+
this.setCurrentItem(this.history[Math.min(index, this.history.length - 1)]);
206210
}
207211
this.refresh();
208212
}
@@ -212,8 +216,8 @@ export class HistoryTreeDataProvider extends DisposableObject {
212216
return this.history;
213217
}
214218

215-
refresh(item?: FullQueryInfo) {
216-
this._onDidChangeTreeData.fire(item);
219+
refresh() {
220+
this._onDidChangeTreeData.fire(undefined);
217221
}
218222

219223
public get sortOrder() {
@@ -267,11 +271,13 @@ export class QueryHistoryManager extends DisposableObject {
267271
this.updateTreeViewSelectionIfVisible()
268272
)
269273
);
270-
// Don't allow the selection to become empty
271274
this.push(
272275
this.treeView.onDidChangeSelection(async (ev) => {
273-
if (ev.selection.length == 0) {
276+
if (ev.selection.length === 0) {
277+
// Don't allow the selection to become empty
274278
this.updateTreeViewSelectionIfVisible();
279+
} else {
280+
this.treeDataProvider.setCurrentItem(ev.selection[0]);
275281
}
276282
this.updateCompareWith(ev.selection);
277283
})
@@ -433,12 +439,15 @@ export class QueryHistoryManager extends DisposableObject {
433439
const { finalSingleItem, finalMultiSelect } = this.determineSelection(singleItem, multiSelect);
434440

435441
(finalMultiSelect || [finalSingleItem]).forEach((item) => {
436-
this.treeDataProvider.remove(item);
437-
item.completedQuery?.dispose();
442+
// TODO: Removing in progress queries is not supported yet
443+
if (item.status !== QueryStatus.InProgress) {
444+
this.treeDataProvider.remove(item);
445+
item.completedQuery?.dispose();
446+
}
438447
});
439448
const current = this.treeDataProvider.getCurrent();
440449
if (current !== undefined) {
441-
await this.treeView.reveal(current);
450+
await this.treeView.reveal(current, { select: true });
442451
await this.invokeCallbackOn(current);
443452
}
444453
}
@@ -484,12 +493,7 @@ export class QueryHistoryManager extends DisposableObject {
484493
if (response !== undefined) {
485494
// Interpret empty string response as 'go back to using default'
486495
singleItem.initialInfo.userSpecifiedLabel = response === '' ? undefined : response;
487-
if (this.treeDataProvider.sortOrder === SortOrder.NameAsc ||
488-
this.treeDataProvider.sortOrder === SortOrder.NameDesc) {
489-
this.treeDataProvider.refresh();
490-
} else {
491-
this.treeDataProvider.refresh(singleItem);
492-
}
496+
this.treeDataProvider.refresh();
493497
}
494498
}
495499

@@ -685,7 +689,7 @@ export class QueryHistoryManager extends DisposableObject {
685689
// We must fire the onDidChangeTreeData event to ensure the current element can be selected
686690
// using `reveal` if the tree view was not visible when the current element was added.
687691
this.treeDataProvider.refresh();
688-
void this.treeView.reveal(current);
692+
void this.treeView.reveal(current, { select: true });
689693
}
690694
}
691695
}
@@ -826,13 +830,19 @@ the file in the file explorer and dragging it into the workspace.`
826830
singleItem: FullQueryInfo,
827831
multiSelect: FullQueryInfo[]
828832
): { finalSingleItem: FullQueryInfo; finalMultiSelect: FullQueryInfo[] } {
829-
if (singleItem === undefined && (multiSelect === undefined || multiSelect.length === 0 || multiSelect[0] === undefined)) {
833+
if (!singleItem && !multiSelect?.[0]) {
830834
const selection = this.treeView.selection;
831-
if (selection) {
835+
const current = this.treeDataProvider.getCurrent();
836+
if (selection?.length) {
832837
return {
833838
finalSingleItem: selection[0],
834839
finalMultiSelect: selection
835840
};
841+
} else if (current) {
842+
return {
843+
finalSingleItem: current,
844+
finalMultiSelect: [current]
845+
};
836846
}
837847
}
838848
return {
@@ -841,7 +851,7 @@ the file in the file explorer and dragging it into the workspace.`
841851
};
842852
}
843853

844-
async refreshTreeView(item: FullQueryInfo): Promise<void> {
845-
this.treeDataProvider.refresh(item);
854+
refreshTreeView(): void {
855+
this.treeDataProvider.refresh();
846856
}
847857
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { env } from 'vscode';
22

3-
import { QueryWithResults, tmpDir, QueryEvaluatonInfo } from './run-queries';
3+
import { QueryWithResults, tmpDir, QueryEvaluationInfo } from './run-queries';
44
import * as messages from './pure/messages';
55
import * as cli from './cli';
66
import * as sarif from 'sarif';
@@ -39,7 +39,7 @@ export enum QueryStatus {
3939
}
4040

4141
export class CompletedQueryInfo implements QueryWithResults {
42-
readonly query: QueryEvaluatonInfo;
42+
readonly query: QueryEvaluationInfo;
4343
readonly result: messages.EvaluationResult;
4444
readonly logFileLocation?: string;
4545
resultCount: number;

extensions/ql-vscode/src/run-queries.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export const tmpDirDisposal = {
5353
* temporary files associated with it, such as the compiled query
5454
* output and results.
5555
*/
56-
export class QueryEvaluatonInfo {
56+
export class QueryEvaluationInfo {
5757
readonly compiledQueryPath: string;
5858
readonly dilPath: string;
5959
readonly csvPath: string;
@@ -266,7 +266,7 @@ export class QueryEvaluatonInfo {
266266

267267

268268
export interface QueryWithResults {
269-
readonly query: QueryEvaluatonInfo;
269+
readonly query: QueryEvaluationInfo;
270270
readonly result: messages.EvaluationResult;
271271
readonly logFileLocation?: string;
272272
readonly dispose: () => void;
@@ -351,7 +351,7 @@ async function getSelectedPosition(editor: TextEditor, range?: Range): Promise<m
351351
async function checkDbschemeCompatibility(
352352
cliServer: cli.CodeQLCliServer,
353353
qs: qsClient.QueryServerClient,
354-
query: QueryEvaluatonInfo,
354+
query: QueryEvaluationInfo,
355355
progress: ProgressCallback,
356356
token: CancellationToken,
357357
): Promise<void> {
@@ -393,7 +393,7 @@ async function checkDbschemeCompatibility(
393393
}
394394
}
395395

396-
function reportNoUpgradePath(query: QueryEvaluatonInfo) {
396+
function reportNoUpgradePath(query: QueryEvaluationInfo) {
397397
throw new Error(`Query ${query.program.queryPath} expects database scheme ${query.queryDbscheme}, but the current database has a different scheme, and no database upgrades are available. The current database scheme may be newer than the CodeQL query libraries in your workspace.\n\nPlease try using a newer version of the query libraries.`);
398398
}
399399

@@ -403,7 +403,7 @@ function reportNoUpgradePath(query: QueryEvaluatonInfo) {
403403
async function compileNonDestructiveUpgrade(
404404
qs: qsClient.QueryServerClient,
405405
upgradeTemp: tmp.DirectoryResult,
406-
query: QueryEvaluatonInfo,
406+
query: QueryEvaluationInfo,
407407
progress: ProgressCallback,
408408
token: CancellationToken,
409409
): Promise<string> {
@@ -614,7 +614,7 @@ export async function compileAndRunQueryAgainstDatabase(
614614
}
615615
}
616616

617-
const query = new QueryEvaluatonInfo(initialInfo.id, qlProgram, db, packConfig.dbscheme, initialInfo.quickEvalPosition, metadata, templates);
617+
const query = new QueryEvaluationInfo(initialInfo.id, qlProgram, db, packConfig.dbscheme, initialInfo.quickEvalPosition, metadata, templates);
618618

619619
const upgradeDir = await tmp.dir({ dir: upgradesTmpDir.name, unsafeCleanup: true });
620620
try {
@@ -716,7 +716,7 @@ const compilationFailedErrorTail = ' compilation failed. Please make sure there
716716
' and choose CodeQL Query Server from the dropdown.';
717717

718718
function createSyntheticResult(
719-
query: QueryEvaluatonInfo,
719+
query: QueryEvaluationInfo,
720720
message: string,
721721
resultType: number
722722
): QueryWithResults {

extensions/ql-vscode/src/vscode-tests/no-workspace/query-history.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import * as sinon from 'sinon';
66
import * as chaiAsPromised from 'chai-as-promised';
77
import { logger } from '../../logging';
88
import { QueryHistoryManager, HistoryTreeDataProvider } from '../../query-history';
9-
import { QueryEvaluatonInfo, QueryWithResults } from '../../run-queries';
9+
import { QueryEvaluationInfo, QueryWithResults } from '../../run-queries';
1010
import { QueryHistoryConfigListener } from '../../config';
1111
import * as messages from '../../pure/messages';
1212
import { QueryServerClient } from '../../queryserver-client';
@@ -379,7 +379,7 @@ describe('query-history', () => {
379379
return {
380380
query: {
381381
hasInterpretedResults: () => Promise.resolve(hasInterpretedResults)
382-
} as QueryEvaluatonInfo,
382+
} as QueryEvaluationInfo,
383383
result: {
384384
resultType: didRunSuccessfully
385385
? messages.QueryResultType.SUCCESS

0 commit comments

Comments
 (0)