55 ViewColumn ,
66 window ,
77 workspace ,
8+ WorkspaceFolder ,
89} from "vscode" ;
910import { AbstractWebview , WebviewPanelConfig } from "../abstract-webview" ;
1011import {
@@ -23,9 +24,12 @@ import {
2324 showAndLogExceptionWithTelemetry ,
2425 showAndLogWarningMessage ,
2526} from "../helpers" ;
26- import { DatabaseItem } from "../local-databases" ;
27+ import { DatabaseItem , DatabaseManager } from "../local-databases" ;
2728import { CodeQLCliServer } from "../cli" ;
2829import { asError , assertNever , getErrorMessage } from "../pure/helpers-pure" ;
30+ import { generateFlowModel } from "./generate-flow-model" ;
31+ import { promptImportGithubDatabase } from "../databaseFetcher" ;
32+ import { App } from "../common/app" ;
2933import { ResolvableLocationValue } from "../pure/bqrs-cli-types" ;
3034import { showResolvableLocation } from "../interface-utils" ;
3135import { decodeBqrsToExternalApiUsages } from "./bqrs" ;
@@ -34,12 +38,27 @@ import { createDataExtensionYaml, loadDataExtensionYaml } from "./yaml";
3438import { ExternalApiUsage } from "./external-api-usage" ;
3539import { ModeledMethod } from "./modeled-method" ;
3640
41+ function getQlSubmoduleFolder ( ) : WorkspaceFolder | undefined {
42+ const workspaceFolder = workspace . workspaceFolders ?. find (
43+ ( folder ) => folder . name === "ql" ,
44+ ) ;
45+ if ( ! workspaceFolder ) {
46+ void extLogger . log ( "No workspace folder 'ql' found" ) ;
47+
48+ return ;
49+ }
50+
51+ return workspaceFolder ;
52+ }
53+
3754export class DataExtensionsEditorView extends AbstractWebview <
3855 ToDataExtensionsEditorMessage ,
3956 FromDataExtensionsEditorMessage
4057> {
4158 public constructor (
4259 ctx : ExtensionContext ,
60+ private readonly app : App ,
61+ private readonly databaseManager : DatabaseManager ,
4362 private readonly cliServer : CodeQLCliServer ,
4463 private readonly queryRunner : QueryRunner ,
4564 private readonly queryStorageDir : string ,
@@ -88,6 +107,10 @@ export class DataExtensionsEditorView extends AbstractWebview<
88107 ) ;
89108 await this . loadExternalApiUsages ( ) ;
90109
110+ break ;
111+ case "generateExternalApi" :
112+ await this . generateModeledMethods ( ) ;
113+
91114 break ;
92115 default :
93116 assertNever ( msg ) ;
@@ -160,8 +183,8 @@ export class DataExtensionsEditorView extends AbstractWebview<
160183 }
161184
162185 await this . postMessage ( {
163- t : "setExistingModeledMethods " ,
164- existingModeledMethods,
186+ t : "addModeledMethods " ,
187+ modeledMethods : existingModeledMethods ,
165188 } ) ;
166189 } catch ( e : unknown ) {
167190 void extLogger . log ( `Unable to read data extension YAML: ${ e } ` ) ;
@@ -213,6 +236,92 @@ export class DataExtensionsEditorView extends AbstractWebview<
213236 }
214237 }
215238
239+ protected async generateModeledMethods ( ) : Promise < void > {
240+ const tokenSource = new CancellationTokenSource ( ) ;
241+
242+ const selectedDatabase = this . databaseManager . currentDatabaseItem ;
243+
244+ // The external API methods are in the library source code, so we need to ask
245+ // the user to import the library database. We need to have the database
246+ // imported to the query server, so we need to register it to our workspace.
247+ const database = await promptImportGithubDatabase (
248+ this . app . commands ,
249+ this . databaseManager ,
250+ this . app . workspaceStoragePath ?? this . app . globalStoragePath ,
251+ this . app . credentials ,
252+ ( update ) => this . showProgress ( update ) ,
253+ tokenSource . token ,
254+ this . cliServer ,
255+ ) ;
256+ if ( ! database ) {
257+ await this . clearProgress ( ) ;
258+ void extLogger . log ( "No database chosen" ) ;
259+
260+ return ;
261+ }
262+
263+ // The library database was set as the current database by importing it,
264+ // but we need to set it back to the originally selected database.
265+ await this . databaseManager . setCurrentDatabaseItem ( selectedDatabase ) ;
266+
267+ const workspaceFolder = getQlSubmoduleFolder ( ) ;
268+ if ( ! workspaceFolder ) {
269+ return ;
270+ }
271+
272+ await this . showProgress ( {
273+ step : 0 ,
274+ maxStep : 4000 ,
275+ message : "Generating modeled methods for library" ,
276+ } ) ;
277+
278+ try {
279+ await generateFlowModel ( {
280+ cliServer : this . cliServer ,
281+ queryRunner : this . queryRunner ,
282+ queryStorageDir : this . queryStorageDir ,
283+ qlDir : workspaceFolder . uri . fsPath ,
284+ databaseItem : database ,
285+ onResults : async ( results ) => {
286+ const modeledMethodsByName : Record < string , ModeledMethod > = { } ;
287+
288+ for ( const result of results ) {
289+ modeledMethodsByName [ result . signature ] = result . modeledMethod ;
290+ }
291+
292+ await this . postMessage ( {
293+ t : "addModeledMethods" ,
294+ modeledMethods : modeledMethodsByName ,
295+ overrideNone : true ,
296+ } ) ;
297+ } ,
298+ progress : ( update ) => this . showProgress ( update ) ,
299+ token : tokenSource . token ,
300+ } ) ;
301+ } catch ( e : unknown ) {
302+ void showAndLogExceptionWithTelemetry (
303+ redactableError (
304+ asError ( e ) ,
305+ ) `Failed to generate flow model: ${ getErrorMessage ( e ) } ` ,
306+ ) ;
307+ }
308+
309+ // After the flow model has been generated, we can remove the temporary database
310+ // which we used for generating the flow model.
311+ await this . databaseManager . removeDatabaseItem (
312+ ( ) =>
313+ this . showProgress ( {
314+ step : 3900 ,
315+ maxStep : 4000 ,
316+ message : "Removing temporary database" ,
317+ } ) ,
318+ tokenSource . token ,
319+ database ,
320+ ) ;
321+
322+ await this . clearProgress ( ) ;
323+ }
324+
216325 private async runQuery ( ) : Promise < CoreCompletedQuery | undefined > {
217326 const qlpacks = await qlpackOfDatabase ( this . cliServer , this . databaseItem ) ;
218327
@@ -297,6 +406,13 @@ export class DataExtensionsEditorView extends AbstractWebview<
297406 * that there's 1000 steps of the query progress since that takes the most time, and then
298407 * an additional 500 steps for the rest of the work. The progress doesn't need to be 100%
299408 * accurate, so this is just a rough estimate.
409+ *
410+ * For generating the modeled methods for an external library, the max step is 4000. This is
411+ * based on the following steps:
412+ * - 1000 for the summary model
413+ * - 1000 for the sink model
414+ * - 1000 for the source model
415+ * - 1000 for the neutral model
300416 */
301417 private async showProgress ( update : ProgressUpdate , maxStep ?: number ) {
302418 await this . postMessage ( {
@@ -316,12 +432,8 @@ export class DataExtensionsEditorView extends AbstractWebview<
316432 }
317433
318434 private calculateModelFilename ( ) : string | undefined {
319- const workspaceFolder = workspace . workspaceFolders ?. find (
320- ( folder ) => folder . name === "ql" ,
321- ) ;
435+ const workspaceFolder = getQlSubmoduleFolder ( ) ;
322436 if ( ! workspaceFolder ) {
323- void extLogger . log ( "No workspace folder 'ql' found" ) ;
324-
325437 return ;
326438 }
327439
0 commit comments