From 6a650947208afc7ec7b19f2708e1fb8ba9dc2839 Mon Sep 17 00:00:00 2001 From: Nora Date: Wed, 3 Jan 2024 15:15:17 +0000 Subject: [PATCH 1/5] Make preparation progress bar cancellable --- .../src/model-editor/extension-pack-picker.ts | 15 ++++++++++-- .../src/model-editor/model-editor-module.ts | 23 +++++++++++++++++-- .../extension-pack-picker.test.ts | 18 ++++++++++++++- 3 files changed, 51 insertions(+), 5 deletions(-) diff --git a/extensions/ql-vscode/src/model-editor/extension-pack-picker.ts b/extensions/ql-vscode/src/model-editor/extension-pack-picker.ts index 7f3becb0eb4..a4c18e8d52f 100644 --- a/extensions/ql-vscode/src/model-editor/extension-pack-picker.ts +++ b/extensions/ql-vscode/src/model-editor/extension-pack-picker.ts @@ -1,11 +1,14 @@ import { join } from "path"; import { outputFile, pathExists, readFile } from "fs-extra"; import { dump as dumpYaml, load as loadYaml } from "js-yaml"; -import { Uri } from "vscode"; +import { CancellationToken, Uri } from "vscode"; import Ajv from "ajv"; import { CodeQLCliServer } from "../codeql-cli/cli"; import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders"; -import { ProgressCallback } from "../common/vscode/progress"; +import { + ProgressCallback, + UserCancellationException, +} from "../common/vscode/progress"; import { DatabaseItem } from "../databases/local-databases"; import { getQlPackPath, QLPACK_FILENAMES } from "../common/ql"; import { getErrorMessage } from "../common/helpers-pure"; @@ -31,6 +34,7 @@ export async function pickExtensionPack( modelConfig: ModelConfig, logger: NotificationLogger, progress: ProgressCallback, + token: CancellationToken, maxStep: number, ): Promise { progress({ @@ -50,6 +54,13 @@ export async function pickExtensionPack( true, ); + if (token.isCancellationRequested) { + throw new UserCancellationException( + "Open Model editor action cancelled.", + true, + ); + } + progress({ message: "Creating extension pack...", step: 2, diff --git a/extensions/ql-vscode/src/model-editor/model-editor-module.ts b/extensions/ql-vscode/src/model-editor/model-editor-module.ts index 13aa59dada1..2b1c2e4bac2 100644 --- a/extensions/ql-vscode/src/model-editor/model-editor-module.ts +++ b/extensions/ql-vscode/src/model-editor/model-editor-module.ts @@ -6,7 +6,10 @@ import { DatabaseItem, DatabaseManager } from "../databases/local-databases"; import { ensureDir } from "fs-extra"; import { join } from "path"; import { App } from "../common/app"; -import { withProgress } from "../common/vscode/progress"; +import { + UserCancellationException, + withProgress, +} from "../common/vscode/progress"; import { pickExtensionPack } from "./extension-pack-picker"; import { showAndLogErrorMessage } from "../common/logging"; import { dir } from "tmp-promise"; @@ -159,7 +162,7 @@ export class ModelEditorModule extends DisposableObject { } return withProgress( - async (progress) => { + async (progress, token) => { const maxStep = 4; if (!(await this.cliServer.cliConstraints.supportsQlpacksKind())) { @@ -176,6 +179,7 @@ export class ModelEditorModule extends DisposableObject { this.modelConfig, this.app.logger, progress, + token, maxStep, ); if (!modelFile) { @@ -188,6 +192,13 @@ export class ModelEditorModule extends DisposableObject { maxStep, }); + if (token.isCancellationRequested) { + throw new UserCancellationException( + "Open Model editor action cancelled.", + true, + ); + } + // Create new temporary directory for query files and pack dependencies const { path: queryDir, cleanup: cleanupQueryDir } = await dir({ unsafeCleanup: true, @@ -211,6 +222,13 @@ export class ModelEditorModule extends DisposableObject { maxStep, }); + if (token.isCancellationRequested) { + throw new UserCancellationException( + "Open Model editor action cancelled.", + true, + ); + } + // Check again just before opening the editor to ensure no model editor has been opened between // our first check and now. if (this.modelingStore.isDbOpen(db.databaseUri.toString())) { @@ -253,6 +271,7 @@ export class ModelEditorModule extends DisposableObject { }, { title: "Opening CodeQL Model Editor", + cancellable: true, }, ); } diff --git a/extensions/ql-vscode/test/vscode-tests/no-workspace/model-editor/extension-pack-picker.test.ts b/extensions/ql-vscode/test/vscode-tests/no-workspace/model-editor/extension-pack-picker.test.ts index 6898a32cc79..aec2b826e32 100644 --- a/extensions/ql-vscode/test/vscode-tests/no-workspace/model-editor/extension-pack-picker.test.ts +++ b/extensions/ql-vscode/test/vscode-tests/no-workspace/model-editor/extension-pack-picker.test.ts @@ -1,4 +1,9 @@ -import { Uri, workspace, WorkspaceFolder } from "vscode"; +import { + CancellationTokenSource, + Uri, + workspace, + WorkspaceFolder, +} from "vscode"; import { dump as dumpYaml, load as loadYaml } from "js-yaml"; import { outputFile, readFile } from "fs-extra"; import { join } from "path"; @@ -24,6 +29,7 @@ describe("pickExtensionPack", () => { }; const progress = jest.fn(); + const token = new CancellationTokenSource().token; let workspaceFoldersSpy: jest.SpyInstance; let additionalPacks: string[]; let workspaceFolder: WorkspaceFolder; @@ -79,6 +85,7 @@ describe("pickExtensionPack", () => { modelConfig, logger, progress, + token, maxStep, ), ).toEqual(autoExtensionPack); @@ -136,6 +143,7 @@ describe("pickExtensionPack", () => { modelConfig, logger, progress, + token, maxStep, ), ).toEqual({ @@ -190,6 +198,7 @@ describe("pickExtensionPack", () => { modelConfig, logger, progress, + token, maxStep, ), ).toEqual({ @@ -234,6 +243,7 @@ describe("pickExtensionPack", () => { modelConfig, logger, progress, + token, maxStep, ), ).toEqual(undefined); @@ -260,6 +270,7 @@ describe("pickExtensionPack", () => { modelConfig, logger, progress, + token, maxStep, ), ).toEqual(undefined); @@ -288,6 +299,7 @@ describe("pickExtensionPack", () => { modelConfig, logger, progress, + token, maxStep, ), ).toEqual(undefined); @@ -326,6 +338,7 @@ describe("pickExtensionPack", () => { modelConfig, logger, progress, + token, maxStep, ), ).toEqual(undefined); @@ -364,6 +377,7 @@ describe("pickExtensionPack", () => { modelConfig, logger, progress, + token, maxStep, ), ).toEqual(undefined); @@ -405,6 +419,7 @@ describe("pickExtensionPack", () => { modelConfig, logger, progress, + token, maxStep, ), ).toEqual(undefined); @@ -463,6 +478,7 @@ describe("pickExtensionPack", () => { modelConfig, logger, progress, + token, maxStep, ), ).toEqual(extensionPack); From 0b27912314e065175b5573c75645f0b86f106628 Mon Sep 17 00:00:00 2001 From: Nora Date: Wed, 3 Jan 2024 15:15:56 +0000 Subject: [PATCH 2/5] Make model editor open progress bar cancellable --- .../src/model-editor/model-editor-queries.ts | 33 ++++++++++++++++++- .../src/model-editor/model-editor-view.ts | 30 +++++++++++++---- 2 files changed, 56 insertions(+), 7 deletions(-) diff --git a/extensions/ql-vscode/src/model-editor/model-editor-queries.ts b/extensions/ql-vscode/src/model-editor/model-editor-queries.ts index 02a21916ffe..48a68a78280 100644 --- a/extensions/ql-vscode/src/model-editor/model-editor-queries.ts +++ b/extensions/ql-vscode/src/model-editor/model-editor-queries.ts @@ -7,7 +7,10 @@ import { import { CancellationToken } from "vscode"; import { CodeQLCliServer } from "../codeql-cli/cli"; import { DatabaseItem } from "../databases/local-databases"; -import { ProgressCallback } from "../common/vscode/progress"; +import { + ProgressCallback, + UserCancellationException, +} from "../common/vscode/progress"; import { redactableError } from "../common/errors"; import { telemetryListener } from "../common/vscode/telemetry"; import { join } from "path"; @@ -89,6 +92,13 @@ export async function runModelEditorQueries( // For a reference of what this should do in the future, see the previous implementation in // https://github.com/github/vscode-codeql/blob/089d3566ef0bc67d9b7cc66e8fd6740b31c1c0b0/extensions/ql-vscode/src/data-extensions-editor/external-api-usage-query.ts#L33-L72 + if (token.isCancellationRequested) { + throw new UserCancellationException( + "Run model editor queries cancelled.", + true, + ); + } + progress({ message: "Resolving QL packs", step: 1, @@ -99,6 +109,13 @@ export async function runModelEditorQueries( await cliServer.resolveQlpacks(additionalPacks, true), ); + if (token.isCancellationRequested) { + throw new UserCancellationException( + "Run model editor queries cancelled.", + true, + ); + } + progress({ message: "Resolving query", step: 2, @@ -143,6 +160,13 @@ export async function runModelEditorQueries( return; } + if (token.isCancellationRequested) { + throw new UserCancellationException( + "Run model editor queries cancelled.", + true, + ); + } + // Read the results and covert to internal representation progress({ message: "Decoding results", @@ -159,6 +183,13 @@ export async function runModelEditorQueries( return; } + if (token.isCancellationRequested) { + throw new UserCancellationException( + "Run model editor queries cancelled.", + true, + ); + } + progress({ message: "Finalizing results", step: 1950, diff --git a/extensions/ql-vscode/src/model-editor/model-editor-view.ts b/extensions/ql-vscode/src/model-editor/model-editor-view.ts index e450cc96264..5753cec6e8e 100644 --- a/extensions/ql-vscode/src/model-editor/model-editor-view.ts +++ b/extensions/ql-vscode/src/model-editor/model-editor-view.ts @@ -1,4 +1,5 @@ import { + CancellationToken, CancellationTokenSource, Tab, TabInputWebview, @@ -14,7 +15,11 @@ import { FromModelEditorMessage, ToModelEditorMessage, } from "../common/interface-types"; -import { ProgressCallback, withProgress } from "../common/vscode/progress"; +import { + ProgressCallback, + UserCancellationException, + withProgress, +} from "../common/vscode/progress"; import { QueryRunner } from "../query-server"; import { showAndLogErrorMessage, @@ -338,8 +343,8 @@ export class ModelEditorView extends AbstractWebview< await Promise.all([ this.setViewState(), - withProgress((progress) => this.loadMethods(progress), { - cancellable: false, + withProgress((progress, token) => this.loadMethods(progress, token), { + cancellable: true, }), this.loadExistingModeledMethods(), ]); @@ -423,11 +428,16 @@ export class ModelEditorView extends AbstractWebview< } } - protected async loadMethods(progress: ProgressCallback): Promise { + protected async loadMethods( + progress: ProgressCallback, + token?: CancellationToken, + ): Promise { const mode = this.modelingStore.getMode(this.databaseItem); try { - const cancellationTokenSource = new CancellationTokenSource(); + if (!token) { + token = new CancellationTokenSource().token; + } const queryResult = await runModelEditorQueries(mode, { cliServer: this.cliServer, queryRunner: this.queryRunner, @@ -441,12 +451,19 @@ export class ModelEditorView extends AbstractWebview< ...update, message: `Loading models: ${update.message}`, }), - token: cancellationTokenSource.token, + token, }); if (!queryResult) { return; } + if (token.isCancellationRequested) { + throw new UserCancellationException( + "Model editor: Load methods cancelled.", + true, + ); + } + this.modelingStore.setMethods(this.databaseItem, queryResult); } catch (err) { void showAndLogExceptionWithTelemetry( @@ -578,6 +595,7 @@ export class ModelEditorView extends AbstractWebview< this.modelConfig, this.app.logger, progress, + token, 3, ); if (!modelFile) { From ad8dc1e9060f69518a7ee32c92e153b81f5dc7e1 Mon Sep 17 00:00:00 2001 From: Nora Date: Thu, 4 Jan 2024 10:52:42 +0000 Subject: [PATCH 3/5] Update Changelog --- extensions/ql-vscode/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/ql-vscode/CHANGELOG.md b/extensions/ql-vscode/CHANGELOG.md index 937c36033a5..5f3260f5cc3 100644 --- a/extensions/ql-vscode/CHANGELOG.md +++ b/extensions/ql-vscode/CHANGELOG.md @@ -6,6 +6,7 @@ - Avoid showing a popup when hovering over source elements in database source files. [#3125](https://github.com/github/vscode-codeql/pull/3125) - Add comparison of alerts when comparing query results. This allows viewing path explanations for differences in alerts. [#3113](https://github.com/github/vscode-codeql/pull/3113) - Fix a bug where the CodeQL CLI and variant analysis results were corrupted after extraction in VS Code Insiders. [#3151](https://github.com/github/vscode-codeql/pull/3151) & [#3152](https://github.com/github/vscode-codeql/pull/3152) +- Add option to cancel opening the model editor. [#3189](https://github.com/github/vscode-codeql/pull/3189) ## 1.11.0 - 13 December 2023 From eb59ead817c8d675ce93629221ae38a658264ed3 Mon Sep 17 00:00:00 2001 From: Nora Date: Thu, 4 Jan 2024 15:19:10 +0000 Subject: [PATCH 4/5] Catch request cancelled error --- extensions/ql-vscode/src/model-editor/model-editor-view.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/extensions/ql-vscode/src/model-editor/model-editor-view.ts b/extensions/ql-vscode/src/model-editor/model-editor-view.ts index 5753cec6e8e..ce69a722d77 100644 --- a/extensions/ql-vscode/src/model-editor/model-editor-view.ts +++ b/extensions/ql-vscode/src/model-editor/model-editor-view.ts @@ -466,6 +466,13 @@ export class ModelEditorView extends AbstractWebview< this.modelingStore.setMethods(this.databaseItem, queryResult); } catch (err) { + if ( + getErrorMessage(err).match(/The request \(.*\) has been cancelled/i) + ) { + this.panel?.dispose(); + return; + } + void showAndLogExceptionWithTelemetry( this.app.logger, this.app.telemetry, From 745544cd9c27bdb667efd7c0bfba53183b6b39e9 Mon Sep 17 00:00:00 2001 From: Nora Date: Fri, 5 Jan 2024 12:37:44 +0000 Subject: [PATCH 5/5] Use error type and error code --- extensions/ql-vscode/src/model-editor/model-editor-view.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/extensions/ql-vscode/src/model-editor/model-editor-view.ts b/extensions/ql-vscode/src/model-editor/model-editor-view.ts index ce69a722d77..51558ba403e 100644 --- a/extensions/ql-vscode/src/model-editor/model-editor-view.ts +++ b/extensions/ql-vscode/src/model-editor/model-editor-view.ts @@ -52,6 +52,8 @@ import { ModelingStore } from "./modeling-store"; import { ModelingEvents } from "./modeling-events"; import { getModelsAsDataLanguage, ModelsAsDataLanguage } from "./languages"; import { runGenerateQueries } from "./generate"; +import { ResponseError } from "vscode-jsonrpc"; +import { LSPErrorCodes } from "vscode-languageclient"; export class ModelEditorView extends AbstractWebview< ToModelEditorMessage, @@ -467,7 +469,8 @@ export class ModelEditorView extends AbstractWebview< this.modelingStore.setMethods(this.databaseItem, queryResult); } catch (err) { if ( - getErrorMessage(err).match(/The request \(.*\) has been cancelled/i) + err instanceof ResponseError && + err.code === LSPErrorCodes.RequestCancelled ) { this.panel?.dispose(); return;