Skip to content

Commit 5e0cade

Browse files
authored
Merge pull request #3016 from github/koesie10/improve-skeleton-db-download
Improve database download prompt when creating query
2 parents 3399212 + fa12671 commit 5e0cade

File tree

4 files changed

+271
-60
lines changed

4 files changed

+271
-60
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ export class LocalQueries extends DisposableObject {
338338
this.cliServer,
339339
progress,
340340
credentials,
341-
this.app.logger,
341+
this.app,
342342
this.databaseManager,
343343
contextStoragePath,
344344
this.selectedQueryTreeViewItems,

extensions/ql-vscode/src/local-queries/skeleton-query-wizard.ts

Lines changed: 105 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
import { basename, dirname, join } from "path";
2-
import { Uri, window as Window, workspace } from "vscode";
2+
import { Uri, window, window as Window, workspace } from "vscode";
33
import { CodeQLCliServer } from "../codeql-cli/cli";
4-
import { BaseLogger } from "../common/logging";
4+
import { showAndLogExceptionWithTelemetry } from "../common/logging";
55
import { Credentials } from "../common/authentication";
6-
import { QueryLanguage } from "../common/query-language";
6+
import {
7+
getLanguageDisplayName,
8+
QueryLanguage,
9+
} from "../common/query-language";
710
import { getFirstWorkspaceFolder } from "../common/vscode/workspace-folders";
8-
import { getErrorMessage } from "../common/helpers-pure";
11+
import { asError, getErrorMessage } from "../common/helpers-pure";
912
import { QlPackGenerator } from "./qlpack-generator";
1013
import { DatabaseItem, DatabaseManager } from "../databases/local-databases";
1114
import {
1215
ProgressCallback,
1316
UserCancellationException,
17+
withProgress,
1418
} from "../common/vscode/progress";
1519
import {
1620
askForGitHubRepo,
@@ -23,6 +27,9 @@ import {
2327
} from "../config";
2428
import { lstat, pathExists } from "fs-extra";
2529
import { askForLanguage } from "../codeql-cli/query-language";
30+
import { showInformationMessageWithAction } from "../common/vscode/dialog";
31+
import { redactableError } from "../common/errors";
32+
import { App } from "../common/app";
2633
import { QueryTreeViewItem } from "../queries-panel/query-tree-view-item";
2734

2835
type QueryLanguagesToDatabaseMap = Record<string, string>;
@@ -41,12 +48,13 @@ export const QUERY_LANGUAGE_TO_DATABASE_REPO: QueryLanguagesToDatabaseMap = {
4148
export class SkeletonQueryWizard {
4249
private fileName = "example.ql";
4350
private qlPackStoragePath: string | undefined;
51+
private downloadPromise: Promise<void> | undefined;
4452

4553
constructor(
4654
private readonly cliServer: CodeQLCliServer,
4755
private readonly progress: ProgressCallback,
4856
private readonly credentials: Credentials | undefined,
49-
private readonly logger: BaseLogger,
57+
private readonly app: App,
5058
private readonly databaseManager: DatabaseManager,
5159
private readonly databaseStoragePath: string | undefined,
5260
private readonly selectedItems: readonly QueryTreeViewItem[],
@@ -57,6 +65,16 @@ export class SkeletonQueryWizard {
5765
return `codeql-custom-queries-${this.language}`;
5866
}
5967

68+
/**
69+
* Wait for the download process to complete by waiting for the user to select
70+
* either "Download database" or closing the dialog. This is used for testing.
71+
*/
72+
public async waitForDownload() {
73+
if (this.downloadPromise) {
74+
await this.downloadPromise;
75+
}
76+
}
77+
6078
public async execute() {
6179
if (!this.language) {
6280
// show quick pick to choose language
@@ -85,7 +103,7 @@ export class SkeletonQueryWizard {
85103
try {
86104
await this.openExampleFile();
87105
} catch (e: unknown) {
88-
void this.logger.log(
106+
void this.app.logger.log(
89107
`Could not open example query file: ${getErrorMessage(e)}`,
90108
);
91109
}
@@ -104,7 +122,9 @@ export class SkeletonQueryWizard {
104122
);
105123

106124
void workspace.openTextDocument(queryFileUri).then((doc) => {
107-
void Window.showTextDocument(doc);
125+
void Window.showTextDocument(doc, {
126+
preview: false,
127+
});
108128
});
109129
}
110130

@@ -208,7 +228,7 @@ export class SkeletonQueryWizard {
208228

209229
await qlPackGenerator.generate();
210230
} catch (e: unknown) {
211-
void this.logger.log(
231+
void this.app.logger.log(
212232
`Could not create skeleton QL pack: ${getErrorMessage(e)}`,
213233
);
214234
}
@@ -240,7 +260,7 @@ export class SkeletonQueryWizard {
240260
this.fileName = await this.determineNextFileName(this.folderName);
241261
await qlPackGenerator.createExampleQlFile(this.fileName);
242262
} catch (e: unknown) {
243-
void this.logger.log(
263+
void this.app.logger.log(
244264
`Could not create query example file: ${getErrorMessage(e)}`,
245265
);
246266
}
@@ -260,7 +280,47 @@ export class SkeletonQueryWizard {
260280
return `example${qlFiles.length + 1}.ql`;
261281
}
262282

263-
private async downloadDatabase() {
283+
private async promptDownloadDatabase() {
284+
if (this.qlPackStoragePath === undefined) {
285+
throw new Error("QL Pack storage path is undefined");
286+
}
287+
288+
if (this.language === undefined) {
289+
throw new Error("Language is undefined");
290+
}
291+
292+
const openFileLink = this.openFileMarkdownLink;
293+
294+
const displayLanguage = getLanguageDisplayName(this.language);
295+
const action = await showInformationMessageWithAction(
296+
`New CodeQL query for ${displayLanguage} ${openFileLink} created, but no CodeQL databases for ${displayLanguage} were detected in your workspace. Would you like to download a CodeQL database for ${displayLanguage} to analyze with ${openFileLink}?`,
297+
"Download database",
298+
);
299+
300+
if (action) {
301+
void withProgress(async (progress) => {
302+
try {
303+
await this.downloadDatabase(progress);
304+
} catch (e: unknown) {
305+
if (e instanceof UserCancellationException) {
306+
return;
307+
}
308+
309+
void showAndLogExceptionWithTelemetry(
310+
this.app.logger,
311+
this.app.telemetry,
312+
redactableError(
313+
asError(e),
314+
)`An error occurred while downloading the GitHub repository: ${getErrorMessage(
315+
e,
316+
)}`,
317+
);
318+
}
319+
});
320+
}
321+
}
322+
323+
private async downloadDatabase(progress: ProgressCallback) {
264324
if (this.qlPackStoragePath === undefined) {
265325
throw new Error("QL Pack storage path is undefined");
266326
}
@@ -273,10 +333,10 @@ export class SkeletonQueryWizard {
273333
throw new Error("Language is undefined");
274334
}
275335

276-
this.progress({
336+
progress({
277337
message: "Downloading database",
278-
step: 3,
279-
maxStep: 3,
338+
step: 1,
339+
maxStep: 2,
280340
});
281341

282342
const githubRepoNwo = QUERY_LANGUAGE_TO_DATABASE_REPO[this.language];
@@ -291,7 +351,7 @@ export class SkeletonQueryWizard {
291351
this.databaseManager,
292352
this.databaseStoragePath,
293353
this.credentials,
294-
this.progress,
354+
progress,
295355
this.cliServer,
296356
this.language,
297357
);
@@ -313,14 +373,42 @@ export class SkeletonQueryWizard {
313373
);
314374

315375
if (existingDatabaseItem) {
316-
// select the found database
317-
await this.databaseManager.setCurrentDatabaseItem(existingDatabaseItem);
376+
const openFileLink = this.openFileMarkdownLink;
377+
378+
if (this.databaseManager.currentDatabaseItem !== existingDatabaseItem) {
379+
// select the found database
380+
await this.databaseManager.setCurrentDatabaseItem(existingDatabaseItem);
381+
382+
const displayLanguage = getLanguageDisplayName(this.language);
383+
void window.showInformationMessage(
384+
`New CodeQL query for ${displayLanguage} ${openFileLink} created. We have automatically selected your existing CodeQL ${displayLanguage} database ${existingDatabaseItem.name} for you to analyze with ${openFileLink}.`,
385+
);
386+
}
318387
} else {
319388
// download new database and select it
320-
await this.downloadDatabase();
389+
this.downloadPromise = this.promptDownloadDatabase().finally(() => {
390+
this.downloadPromise = undefined;
391+
});
321392
}
322393
}
323394

395+
private get openFileMarkdownLink() {
396+
if (this.qlPackStoragePath === undefined) {
397+
throw new Error("QL Pack storage path is undefined");
398+
}
399+
400+
const queryPath = join(
401+
this.qlPackStoragePath,
402+
this.folderName,
403+
this.fileName,
404+
);
405+
const queryPathUri = Uri.file(queryPath);
406+
407+
const openFileArgs = [queryPathUri.toString(true)];
408+
const queryString = encodeURI(JSON.stringify(openFileArgs));
409+
return `[${this.fileName}](command:vscode.open?${queryString})`;
410+
}
411+
324412
public static async findDatabaseItemByNwo(
325413
language: string,
326414
databaseNwo: string,

extensions/ql-vscode/test/__mocks__/appMock.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export function createMockApp({
3434
environment?: EnvironmentContext;
3535
logger?: NotificationLogger;
3636
telemetry?: AppTelemetry;
37-
}): App {
37+
} = {}): App {
3838
return {
3939
mode: AppMode.Test,
4040
logger,

0 commit comments

Comments
 (0)