11import { basename , dirname , join } from "path" ;
2- import { Uri , window as Window , workspace } from "vscode" ;
2+ import { Uri , window , window as Window , workspace } from "vscode" ;
33import { CodeQLCliServer } from "../codeql-cli/cli" ;
4- import { BaseLogger } from "../common/logging" ;
4+ import { showAndLogExceptionWithTelemetry } from "../common/logging" ;
55import { Credentials } from "../common/authentication" ;
6- import { QueryLanguage } from "../common/query-language" ;
6+ import {
7+ getLanguageDisplayName ,
8+ QueryLanguage ,
9+ } from "../common/query-language" ;
710import { getFirstWorkspaceFolder } from "../common/vscode/workspace-folders" ;
8- import { getErrorMessage } from "../common/helpers-pure" ;
11+ import { asError , getErrorMessage } from "../common/helpers-pure" ;
912import { QlPackGenerator } from "./qlpack-generator" ;
1013import { DatabaseItem , DatabaseManager } from "../databases/local-databases" ;
1114import {
1215 ProgressCallback ,
1316 UserCancellationException ,
17+ withProgress ,
1418} from "../common/vscode/progress" ;
1519import {
1620 askForGitHubRepo ,
@@ -23,6 +27,9 @@ import {
2327} from "../config" ;
2428import { lstat , pathExists } from "fs-extra" ;
2529import { 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" ;
2633import { QueryTreeViewItem } from "../queries-panel/query-tree-view-item" ;
2734
2835type QueryLanguagesToDatabaseMap = Record < string , string > ;
@@ -41,12 +48,13 @@ export const QUERY_LANGUAGE_TO_DATABASE_REPO: QueryLanguagesToDatabaseMap = {
4148export 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 ,
0 commit comments