diff --git a/extensions/ql-vscode/src/common/interface-types.ts b/extensions/ql-vscode/src/common/interface-types.ts index 1149cef07..9f38c19ad 100644 --- a/extensions/ql-vscode/src/common/interface-types.ts +++ b/extensions/ql-vscode/src/common/interface-types.ts @@ -500,13 +500,6 @@ interface SetExternalApiUsagesMessage { externalApiUsages: ExternalApiUsage[]; } -export interface ShowProgressMessage { - t: "showProgress"; - step: number; - maxStep: number; - message: string; -} - interface LoadModeledMethodsMessage { t: "loadModeledMethods"; modeledMethods: Record; @@ -558,7 +551,6 @@ interface ModelDependencyMessage { export type ToDataExtensionsEditorMessage = | SetExtensionPackStateMessage | SetExternalApiUsagesMessage - | ShowProgressMessage | LoadModeledMethodsMessage | AddModeledMethodsMessage; diff --git a/extensions/ql-vscode/src/data-extensions-editor/data-extensions-editor-view.ts b/extensions/ql-vscode/src/data-extensions-editor/data-extensions-editor-view.ts index 891e9516c..454713a1a 100644 --- a/extensions/ql-vscode/src/data-extensions-editor/data-extensions-editor-view.ts +++ b/extensions/ql-vscode/src/data-extensions-editor/data-extensions-editor-view.ts @@ -14,11 +14,7 @@ import { FromDataExtensionsEditorMessage, ToDataExtensionsEditorMessage, } from "../common/interface-types"; -import { - ProgressCallback, - ProgressUpdate, - withProgress, -} from "../common/vscode/progress"; +import { ProgressCallback, withProgress } from "../common/vscode/progress"; import { QueryRunner } from "../query-server"; import { showAndLogExceptionWithTelemetry, @@ -225,203 +221,205 @@ export class DataExtensionsEditorView extends AbstractWebview< } protected async loadExternalApiUsages(): Promise { - const cancellationTokenSource = new CancellationTokenSource(); + await withProgress( + async (progress) => { + try { + const cancellationTokenSource = new CancellationTokenSource(); + const queryResult = await runQuery( + this.mode === Mode.Framework + ? "frameworkModeQuery" + : "applicationModeQuery", + { + cliServer: this.cliServer, + queryRunner: this.queryRunner, + databaseItem: this.databaseItem, + queryStorageDir: this.queryStorageDir, + progress: (update) => progress({ ...update, maxStep: 1500 }), + token: cancellationTokenSource.token, + }, + ); + if (!queryResult) { + return; + } - try { - const queryResult = await runQuery( - this.mode === Mode.Framework - ? "frameworkModeQuery" - : "applicationModeQuery", - { - cliServer: this.cliServer, - queryRunner: this.queryRunner, - databaseItem: this.databaseItem, - queryStorageDir: this.queryStorageDir, - progress: (progressUpdate: ProgressUpdate) => { - void this.showProgress(progressUpdate, 1500); - }, - token: cancellationTokenSource.token, - }, - ); - if (!queryResult) { - await this.clearProgress(); - return; - } + progress({ + message: "Decoding results", + step: 1100, + maxStep: 1500, + }); - await this.showProgress({ - message: "Decoding results", - step: 1100, - maxStep: 1500, - }); + const bqrsChunk = await readQueryResults({ + cliServer: this.cliServer, + bqrsPath: queryResult.outputDir.bqrsPath, + }); + if (!bqrsChunk) { + return; + } - const bqrsChunk = await readQueryResults({ - cliServer: this.cliServer, - bqrsPath: queryResult.outputDir.bqrsPath, - }); - if (!bqrsChunk) { - await this.clearProgress(); - return; - } + progress({ + message: "Finalizing results", + step: 1450, + maxStep: 1500, + }); - await this.showProgress({ - message: "Finalizing results", - step: 1450, - maxStep: 1500, - }); + const externalApiUsages = decodeBqrsToExternalApiUsages(bqrsChunk); - const externalApiUsages = decodeBqrsToExternalApiUsages(bqrsChunk); - - await this.postMessage({ - t: "setExternalApiUsages", - externalApiUsages, - }); - - await this.clearProgress(); - } catch (err) { - void showAndLogExceptionWithTelemetry( - this.app.logger, - this.app.telemetry, - redactableError( - asError(err), - )`Failed to load external API usages: ${getErrorMessage(err)}`, - ); - } + await this.postMessage({ + t: "setExternalApiUsages", + externalApiUsages, + }); + } catch (err) { + void showAndLogExceptionWithTelemetry( + this.app.logger, + this.app.telemetry, + redactableError( + asError(err), + )`Failed to load external API usages: ${getErrorMessage(err)}`, + ); + } + }, + { cancellable: false }, + ); } protected async generateModeledMethods(): Promise { - const tokenSource = new CancellationTokenSource(); + await withProgress( + async (progress) => { + const tokenSource = new CancellationTokenSource(); - let addedDatabase: DatabaseItem | undefined; + let addedDatabase: DatabaseItem | undefined; - // In application mode, we need the database of a specific library to generate - // the modeled methods. In framework mode, we'll use the current database. - if (this.mode === Mode.Application) { - addedDatabase = await this.promptImportDatabase((update) => - this.showProgress(update), - ); - if (!addedDatabase) { - return; - } - } - - await this.showProgress({ - step: 0, - maxStep: 4000, - message: "Generating modeled methods for library", - }); - - try { - await generateFlowModel({ - cliServer: this.cliServer, - queryRunner: this.queryRunner, - queryStorageDir: this.queryStorageDir, - databaseItem: addedDatabase ?? this.databaseItem, - onResults: async (modeledMethods) => { - const modeledMethodsByName: Record = {}; - - for (const modeledMethod of modeledMethods) { - modeledMethodsByName[modeledMethod.signature] = modeledMethod; + // In application mode, we need the database of a specific library to generate + // the modeled methods. In framework mode, we'll use the current database. + if (this.mode === Mode.Application) { + addedDatabase = await this.promptImportDatabase(progress); + if (!addedDatabase) { + return; } + } - await this.postMessage({ - t: "addModeledMethods", - modeledMethods: modeledMethodsByName, + progress({ + step: 0, + maxStep: 4000, + message: "Generating modeled methods for library", + }); + + try { + await generateFlowModel({ + cliServer: this.cliServer, + queryRunner: this.queryRunner, + queryStorageDir: this.queryStorageDir, + databaseItem: addedDatabase ?? this.databaseItem, + onResults: async (modeledMethods) => { + const modeledMethodsByName: Record = {}; + + for (const modeledMethod of modeledMethods) { + modeledMethodsByName[modeledMethod.signature] = modeledMethod; + } + + await this.postMessage({ + t: "addModeledMethods", + modeledMethods: modeledMethodsByName, + }); + }, + progress, + token: tokenSource.token, }); - }, - progress: (update) => this.showProgress(update), - token: tokenSource.token, - }); - } catch (e: unknown) { - void showAndLogExceptionWithTelemetry( - this.app.logger, - this.app.telemetry, - redactableError( - asError(e), - )`Failed to generate flow model: ${getErrorMessage(e)}`, - ); - } + } catch (e: unknown) { + void showAndLogExceptionWithTelemetry( + this.app.logger, + this.app.telemetry, + redactableError( + asError(e), + )`Failed to generate flow model: ${getErrorMessage(e)}`, + ); + } - if (addedDatabase) { - // After the flow model has been generated, we can remove the temporary database - // which we used for generating the flow model. - await this.showProgress({ - step: 3900, - maxStep: 4000, - message: "Removing temporary database", - }); - await this.databaseManager.removeDatabaseItem(addedDatabase); - } - - await this.clearProgress(); + if (addedDatabase) { + // After the flow model has been generated, we can remove the temporary database + // which we used for generating the flow model. + progress({ + step: 3900, + maxStep: 4000, + message: "Removing temporary database", + }); + await this.databaseManager.removeDatabaseItem(addedDatabase); + } + }, + { cancellable: false }, + ); } private async generateModeledMethodsFromLlm( externalApiUsages: ExternalApiUsage[], modeledMethods: Record, ): Promise { - const maxStep = 3000; + await withProgress( + async (progress) => { + const maxStep = 3000; - await this.showProgress({ - step: 0, - maxStep, - message: "Retrieving usages", - }); + progress({ + step: 0, + maxStep, + message: "Retrieving usages", + }); - const usages = await getAutoModelUsages({ - cliServer: this.cliServer, - queryRunner: this.queryRunner, - queryStorageDir: this.queryStorageDir, - databaseItem: this.databaseItem, - progress: (update) => this.showProgress(update, maxStep), - }); + const usages = await getAutoModelUsages({ + cliServer: this.cliServer, + queryRunner: this.queryRunner, + queryStorageDir: this.queryStorageDir, + databaseItem: this.databaseItem, + progress: (update) => progress({ ...update, maxStep }), + }); - await this.showProgress({ - step: 1800, - maxStep, - message: "Creating request", - }); + progress({ + step: 1800, + maxStep, + message: "Creating request", + }); - const request = createAutoModelRequest( - this.databaseItem.language, - externalApiUsages, - modeledMethods, - usages, - this.mode, + const request = createAutoModelRequest( + this.databaseItem.language, + externalApiUsages, + modeledMethods, + usages, + this.mode, + ); + + progress({ + step: 2000, + maxStep, + message: "Sending request", + }); + + const response = await this.callAutoModelApi(request); + if (!response) { + return; + } + + progress({ + step: 2500, + maxStep, + message: "Parsing response", + }); + + const predictedModeledMethods = parsePredictedClassifications( + response.predicted || [], + ); + + progress({ + step: 2800, + maxStep, + message: "Applying results", + }); + + await this.postMessage({ + t: "addModeledMethods", + modeledMethods: predictedModeledMethods, + }); + }, + { cancellable: false }, ); - - await this.showProgress({ - step: 2000, - maxStep, - message: "Sending request", - }); - - const response = await this.callAutoModelApi(request); - if (!response) { - return; - } - - await this.showProgress({ - step: 2500, - maxStep, - message: "Parsing response", - }); - - const predictedModeledMethods = parsePredictedClassifications( - response.predicted || [], - ); - - await this.showProgress({ - step: 2800, - maxStep, - message: "Applying results", - }); - - await this.postMessage({ - t: "addModeledMethods", - modeledMethods: predictedModeledMethods, - }); - - await this.clearProgress(); } private async modelDependency(): Promise { @@ -482,46 +480,12 @@ export class DataExtensionsEditorView extends AbstractWebview< return addedDatabase; } - /* - * Progress in this class is a bit weird. Most of the progress is based on running the query. - * Query progress is always between 0 and 1000. However, we still have some steps that need - * to be done after the query has finished. Therefore, the maximum step is 1500. This captures - * that there's 1000 steps of the query progress since that takes the most time, and then - * an additional 500 steps for the rest of the work. The progress doesn't need to be 100% - * accurate, so this is just a rough estimate. - * - * For generating the modeled methods for an external library, the max step is 4000. This is - * based on the following steps: - * - 1000 for the summary model - * - 1000 for the sink model - * - 1000 for the source model - * - 1000 for the neutral model - */ - private async showProgress(update: ProgressUpdate, maxStep?: number) { - await this.postMessage({ - t: "showProgress", - step: update.step, - maxStep: maxStep ?? update.maxStep, - message: update.message, - }); - } - - private async clearProgress() { - await this.showProgress({ - step: 0, - maxStep: 0, - message: "", - }); - } - private async callAutoModelApi( request: ModelRequest, ): Promise { try { return await autoModel(this.app.credentials, request); } catch (e) { - await this.clearProgress(); - if (e instanceof RequestError && e.status === 429) { void showAndLogExceptionWithTelemetry( this.app.logger, diff --git a/extensions/ql-vscode/src/view/data-extensions-editor/DataExtensionsEditor.tsx b/extensions/ql-vscode/src/view/data-extensions-editor/DataExtensionsEditor.tsx index d1bdb221a..454cf8760 100644 --- a/extensions/ql-vscode/src/view/data-extensions-editor/DataExtensionsEditor.tsx +++ b/extensions/ql-vscode/src/view/data-extensions-editor/DataExtensionsEditor.tsx @@ -1,9 +1,6 @@ import * as React from "react"; import { useCallback, useEffect, useMemo, useState } from "react"; -import { - ShowProgressMessage, - ToDataExtensionsEditorMessage, -} from "../../common/interface-types"; +import { ToDataExtensionsEditorMessage } from "../../common/interface-types"; import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"; import styled from "styled-components"; import { ExternalApiUsage } from "../../data-extensions-editor/external-api-usage"; @@ -46,17 +43,6 @@ const ButtonsContainer = styled.div` margin-bottom: 1rem; `; -type ProgressBarProps = { - completion: number; -}; - -const ProgressBar = styled.div` - height: 10px; - width: ${(props) => props.completion * 100}%; - - background-color: var(--vscode-progressBar-background); -`; - type Props = { initialViewState?: DataExtensionEditorViewState; initialExternalApiUsages?: ExternalApiUsage[]; @@ -82,11 +68,6 @@ export function DataExtensionsEditor({ const [modeledMethods, setModeledMethods] = useState< Record >(initialModeledMethods); - const [progress, setProgress] = useState>({ - step: 0, - maxStep: 0, - message: "", - }); useEffect(() => { const listener = (evt: MessageEvent) => { @@ -99,9 +80,6 @@ export function DataExtensionsEditor({ case "setExternalApiUsages": setExternalApiUsages(msg.externalApiUsages); break; - case "showProgress": - setProgress(msg); - break; case "loadModeledMethods": setModeledMethods((oldModeledMethods) => { return { @@ -244,89 +222,71 @@ export function DataExtensionsEditor({ }); }, [viewState?.mode]); - if (viewState === undefined) { + if (viewState === undefined || externalApiUsages.length === 0) { return Loading...; } return ( - {progress.maxStep > 0 && ( -

- {" "} - {progress.message} -

- )} - - {externalApiUsages.length > 0 && ( - <> - - {getLanguageDisplayName(viewState.extensionPack.language)} - - - - - {viewState.extensionPack.name} - + + {getLanguageDisplayName(viewState.extensionPack.language)} + + + + + {viewState.extensionPack.name} + +
{percentFormatter.format(modeledPercentage / 100)} modeled
+
+ {percentFormatter.format(unModeledPercentage / 100)} unmodeled +
+ {viewState.enableFrameworkMode && ( + <>
- {percentFormatter.format(modeledPercentage / 100)} modeled + Mode:{" "} + {viewState.mode === Mode.Framework ? "Framework" : "Application"}
- {percentFormatter.format(unModeledPercentage / 100)} unmodeled + + + Switch mode +
- {viewState.enableFrameworkMode && ( - <> -
- Mode:{" "} - {viewState.mode === Mode.Framework - ? "Framework" - : "Application"} -
-
- - - Switch mode - -
- - )} -
+ + )} +
- - - - Save all - - {viewState.enableFrameworkMode && ( - - Refresh - - )} - {viewState.mode === Mode.Framework && ( - - Generate - - )} - - - - - )} + + + + Save all + + {viewState.enableFrameworkMode && ( + + Refresh + + )} + {viewState.mode === Mode.Framework && ( + + Generate + + )} + + +
); }