Merge pull request #2202 from github/robertbrignull/extract_progress

Move withProgress and associated code to a separate file
This commit is contained in:
Robert
2023-03-22 15:19:50 +00:00
committed by GitHub
34 changed files with 175 additions and 183 deletions

View File

@@ -1,5 +1,5 @@
import { Uri, window } from "vscode";
import { withProgress } from "./commandRunner";
import { withProgress } from "./progress";
import { AstViewer } from "./astViewer";
import {
TemplatePrintAstProvider,

View File

@@ -1,11 +1,4 @@
import {
CancellationToken,
ProgressOptions as VSCodeProgressOptions,
window as Window,
commands,
Disposable,
ProgressLocation,
} from "vscode";
import { CancellationToken, commands, Disposable } from "vscode";
import {
showAndLogExceptionWithTelemetry,
showAndLogWarningMessage,
@@ -14,51 +7,22 @@ import { extLogger } from "./common";
import { asError, getErrorMessage, getErrorStack } from "./pure/helpers-pure";
import { telemetryListener } from "./telemetry";
import { redactableError } from "./pure/errors";
export class UserCancellationException extends Error {
/**
* @param message The error message
* @param silent If silent is true, then this exception will avoid showing a warning message to the user.
*/
constructor(message?: string, public readonly silent = false) {
super(message);
}
}
export interface ProgressUpdate {
/**
* The current step
*/
step: number;
/**
* The maximum step. This *should* be constant for a single job.
*/
maxStep: number;
/**
* The current progress message
*/
message: string;
}
export type ProgressCallback = (p: ProgressUpdate) => void;
// Make certain properties within a type optional
type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
export type ProgressOptions = Optional<VSCodeProgressOptions, "location">;
import {
UserCancellationException,
withProgress,
ProgressOptions,
ProgressCallback,
} from "./progress";
/**
* A task that reports progress.
* A task that handles command invocations from `commandRunner`.
* Arguments passed to the command handler are passed along,
* untouched to this `NoProgressTask` instance.
*
* @param progress a progress handler function. Call this
* function with a `ProgressUpdate` instance in order to
* denote some progress being achieved on this task.
* @param token a cancellation token
* @param args arguments passed to this task passed on from
* `commands.registerCommand`.
*/
export type ProgressTask<R> = (
progress: ProgressCallback,
token: CancellationToken,
) => Thenable<R>;
export type NoProgressTask = (...args: any[]) => Promise<any>;
/**
* A task that handles command invocations from `commandRunner`
@@ -75,64 +39,12 @@ export type ProgressTask<R> = (
* @param args arguments passed to this task passed on from
* `commands.registerCommand`.
*/
export type ProgressTaskWithArgs<R> = (
type ProgressTaskWithArgs<R> = (
progress: ProgressCallback,
token: CancellationToken,
...args: any[]
) => Thenable<R>;
/**
* A task that handles command invocations from `commandRunner`.
* Arguments passed to the command handler are passed along,
* untouched to this `NoProgressTask` instance.
*
* @param args arguments passed to this task passed on from
* `commands.registerCommand`.
*/
export type NoProgressTask = (...args: any[]) => Promise<any>;
/**
* This mediates between the kind of progress callbacks we want to
* write (where we *set* current progress position and give
* `maxSteps`) and the kind vscode progress api expects us to write
* (which increment progress by a certain amount out of 100%).
*
* Where possible, the `commandRunner` function below should be used
* instead of this function. The commandRunner is meant for wrapping
* top-level commands and provides error handling and other support
* automatically.
*
* Only use this function if you need a progress monitor and the
* control flow does not always come from a command (eg- during
* extension activation, or from an internal language server
* request).
*/
export function withProgress<R>(
task: ProgressTask<R>,
{
location = ProgressLocation.Notification,
title,
cancellable,
}: ProgressOptions = {},
): Thenable<R> {
let progressAchieved = 0;
return Window.withProgress(
{
location,
title,
cancellable,
},
(progress, token) => {
return task((p) => {
const { message, step, maxStep } = p;
const increment = (100 * (step - progressAchieved)) / maxStep;
progressAchieved = step;
progress.report({ message, increment });
}, token);
},
);
}
/**
* A generic wrapper for command registration. This wrapper adds uniform error handling for commands.
*
@@ -216,48 +128,3 @@ export function commandRunnerWithProgress<R>(
outputLogger,
);
}
/**
* Displays a progress monitor that indicates how much progess has been made
* reading from a stream.
*
* @param readable The stream to read progress from
* @param messagePrefix A prefix for displaying the message
* @param totalNumBytes Total number of bytes in this stream
* @param progress The progress callback used to set messages
*/
export function reportStreamProgress(
readable: NodeJS.ReadableStream,
messagePrefix: string,
totalNumBytes?: number,
progress?: ProgressCallback,
) {
if (progress && totalNumBytes) {
let numBytesDownloaded = 0;
const bytesToDisplayMB = (numBytes: number): string =>
`${(numBytes / (1024 * 1024)).toFixed(1)} MB`;
const updateProgress = () => {
progress({
step: numBytesDownloaded,
maxStep: totalNumBytes,
message: `${messagePrefix} [${bytesToDisplayMB(
numBytesDownloaded,
)} of ${bytesToDisplayMB(totalNumBytes)}]`,
});
};
// Display the progress straight away rather than waiting for the first chunk.
updateProgress();
readable.on("data", (data) => {
numBytesDownloaded += data.length;
updateProgress();
});
} else if (progress) {
progress({
step: 1,
maxStep: 2,
message: `${messagePrefix} (Size unknown)`,
});
}
}

View File

@@ -11,7 +11,7 @@ import {
import { CodeQLCliServer } from "../cli";
import { DatabaseManager, DatabaseItem } from "../local-databases";
import fileRangeFromURI from "./fileRangeFromURI";
import { ProgressCallback } from "../commandRunner";
import { ProgressCallback } from "../progress";
import { KeyType } from "./keyType";
import {
qlpackOfDatabase,

View File

@@ -16,7 +16,7 @@ import { DatabaseItem } from "../local-databases";
import { extLogger } from "../common";
import { createInitialQueryInfo } from "../run-queries-shared";
import { CancellationToken, Uri } from "vscode";
import { ProgressCallback } from "../commandRunner";
import { ProgressCallback } from "../progress";
import { QueryRunner } from "../queryRunner";
import { redactableError } from "../pure/errors";
import { QLPACK_FILENAMES } from "../pure/ql";

View File

@@ -18,7 +18,7 @@ import {
import { CodeQLCliServer } from "../cli";
import { DatabaseManager } from "../local-databases";
import { CachedOperation } from "../helpers";
import { ProgressCallback, withProgress } from "../commandRunner";
import { ProgressCallback, withProgress } from "../progress";
import AstBuilder from "./astBuilder";
import { KeyType } from "./keyType";
import {

View File

@@ -18,7 +18,7 @@ import { retry } from "@octokit/plugin-retry";
import { DatabaseManager, DatabaseItem } from "./local-databases";
import { showAndLogInformationMessage, tmpDir } from "./helpers";
import { reportStreamProgress, ProgressCallback } from "./commandRunner";
import { reportStreamProgress, ProgressCallback } from "./progress";
import { extLogger } from "./common";
import { getErrorMessage } from "./pure/helpers-pure";
import {

View File

@@ -7,7 +7,7 @@ import {
window,
workspace,
} from "vscode";
import { UserCancellationException } from "../../commandRunner";
import { UserCancellationException } from "../../progress";
import {
getNwoFromGitHubUrl,
isValidGitHubNwo,

View File

@@ -14,7 +14,7 @@ import {
} from "./helpers";
import { extLogger } from "./common";
import { getCodeQlCliVersion } from "./cli-version";
import { ProgressCallback, reportStreamProgress } from "./commandRunner";
import { ProgressCallback, reportStreamProgress } from "./progress";
import {
codeQlLauncherName,
deprecatedCodeQlLauncherName,

View File

@@ -89,7 +89,8 @@ import { QLTestAdapterFactory } from "./test-adapter";
import { TestUIService } from "./test-ui";
import { CompareView } from "./compare/compare-view";
import { initializeTelemetry } from "./telemetry";
import { commandRunner, ProgressCallback, withProgress } from "./commandRunner";
import { commandRunner } from "./commandRunner";
import { ProgressCallback, withProgress } from "./progress";
import { CodeQlStatusBarHandler } from "./status-bar";
import { getPackagingCommands } from "./packaging";
import { HistoryItemLabelProvider } from "./query-history/history-item-label-provider";

View File

@@ -19,7 +19,7 @@ import {
commands,
} from "vscode";
import { CodeQLCliServer, QlpacksInfo } from "./cli";
import { UserCancellationException } from "./commandRunner";
import { UserCancellationException } from "./progress";
import { extLogger, OutputChannelLogger } from "./common";
import { QueryMetadata } from "./pure/interface-types";
import { telemetryListener } from "./telemetry";

View File

@@ -1,5 +1,5 @@
import { CancellationToken } from "vscode";
import { ProgressCallback } from "../commandRunner";
import { ProgressCallback } from "../progress";
import { DatabaseItem } from "../local-databases";
import {
Dataset,

View File

@@ -13,7 +13,7 @@ import {
ProgressMessage,
WithProgressId,
} from "../pure/legacy-messages";
import { ProgressCallback, ProgressTask } from "../commandRunner";
import { ProgressCallback, ProgressTask } from "../progress";
import { ServerProcess } from "../json-rpc-server";
type WithProgressReporting = (

View File

@@ -13,7 +13,7 @@ import {
tryGetQueryMetadata,
upgradesTmpDir,
} from "../helpers";
import { ProgressCallback } from "../commandRunner";
import { ProgressCallback } from "../progress";
import { QueryMetadata } from "../pure/interface-types";
import { extLogger, Logger, TeeLogger } from "../common";
import * as messages from "../pure/legacy-messages";

View File

@@ -4,7 +4,7 @@ import {
showAndLogExceptionWithTelemetry,
tmpDir,
} from "../helpers";
import { ProgressCallback, UserCancellationException } from "../commandRunner";
import { ProgressCallback, UserCancellationException } from "../progress";
import { extLogger } from "../common";
import * as messages from "../pure/legacy-messages";
import * as qsClient from "./queryserver-client";

View File

@@ -21,7 +21,7 @@ import {
DatabaseItem,
DatabaseManager,
} from "./local-databases";
import { ProgressCallback, withProgress } from "./commandRunner";
import { ProgressCallback, withProgress } from "./progress";
import {
isLikelyDatabaseRoot,
isLikelyDbLanguageFolder,

View File

@@ -12,7 +12,7 @@ import {
isFolderAlreadyInWorkspace,
showBinaryChoiceDialog,
} from "./helpers";
import { ProgressCallback, withProgress } from "./commandRunner";
import { ProgressCallback, withProgress } from "./progress";
import {
zipArchiveScheme,
encodeArchiveBasePath,

View File

@@ -1,8 +1,4 @@
import {
ProgressCallback,
ProgressUpdate,
withProgress,
} from "./commandRunner";
import { ProgressCallback, ProgressUpdate, withProgress } from "./progress";
import {
CancellationToken,
CancellationTokenSource,

View File

@@ -9,7 +9,7 @@ import {
ProgressCallback,
UserCancellationException,
withProgress,
} from "./commandRunner";
} from "./progress";
import { extLogger } from "./common";
import { asError, getErrorStack } from "./pure/helpers-pure";
import { redactableError } from "./pure/errors";

View File

@@ -0,0 +1,128 @@
import {
CancellationToken,
ProgressLocation,
ProgressOptions as VSCodeProgressOptions,
window as Window,
} from "vscode";
export class UserCancellationException extends Error {
/**
* @param message The error message
* @param silent If silent is true, then this exception will avoid showing a warning message to the user.
*/
constructor(message?: string, public readonly silent = false) {
super(message);
}
}
export interface ProgressUpdate {
/**
* The current step
*/
step: number;
/**
* The maximum step. This *should* be constant for a single job.
*/
maxStep: number;
/**
* The current progress message
*/
message: string;
}
export type ProgressCallback = (p: ProgressUpdate) => void;
// Make certain properties within a type optional
type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
export type ProgressOptions = Optional<VSCodeProgressOptions, "location">;
/**
* A task that reports progress.
*
* @param progress a progress handler function. Call this
* function with a `ProgressUpdate` instance in order to
* denote some progress being achieved on this task.
* @param token a cancellation token
*/
export type ProgressTask<R> = (
progress: ProgressCallback,
token: CancellationToken,
) => Thenable<R>;
/**
* This mediates between the kind of progress callbacks we want to
* write (where we *set* current progress position and give
* `maxSteps`) and the kind vscode progress api expects us to write
* (which increment progress by a certain amount out of 100%).
*/
export function withProgress<R>(
task: ProgressTask<R>,
{
location = ProgressLocation.Notification,
title,
cancellable,
}: ProgressOptions = {},
): Thenable<R> {
let progressAchieved = 0;
return Window.withProgress(
{
location,
title,
cancellable,
},
(progress, token) => {
return task((p) => {
const { message, step, maxStep } = p;
const increment = (100 * (step - progressAchieved)) / maxStep;
progressAchieved = step;
progress.report({ message, increment });
}, token);
},
);
}
/**
* Displays a progress monitor that indicates how much progess has been made
* reading from a stream.
*
* @param readable The stream to read progress from
* @param messagePrefix A prefix for displaying the message
* @param totalNumBytes Total number of bytes in this stream
* @param progress The progress callback used to set messages
*/
export function reportStreamProgress(
readable: NodeJS.ReadableStream,
messagePrefix: string,
totalNumBytes?: number,
progress?: ProgressCallback,
) {
if (progress && totalNumBytes) {
let numBytesDownloaded = 0;
const bytesToDisplayMB = (numBytes: number): string =>
`${(numBytes / (1024 * 1024)).toFixed(1)} MB`;
const updateProgress = () => {
progress({
step: numBytesDownloaded,
maxStep: totalNumBytes,
message: `${messagePrefix} [${bytesToDisplayMB(
numBytesDownloaded,
)} of ${bytesToDisplayMB(totalNumBytes)}]`,
});
};
// Display the progress straight away rather than waiting for the first chunk.
updateProgress();
readable.on("data", (data) => {
numBytesDownloaded += data.length;
updateProgress();
});
} else if (progress) {
progress({
step: 1,
maxStep: 2,
message: `${messagePrefix} (Size unknown)`,
});
}
}

View File

@@ -1,5 +1,5 @@
import { CancellationToken } from "vscode";
import { ProgressCallback, UserCancellationException } from "../commandRunner";
import { ProgressCallback, UserCancellationException } from "../progress";
import { DatabaseItem } from "../local-databases";
import {
clearCache,

View File

@@ -11,7 +11,7 @@ import {
ProgressMessage,
WithProgressId,
} from "../pure/new-messages";
import { ProgressCallback, ProgressTask } from "../commandRunner";
import { ProgressCallback, ProgressTask } from "../progress";
import { ServerProcess } from "../json-rpc-server";
type ServerOpts = {

View File

@@ -1,7 +1,7 @@
import { join } from "path";
import { CancellationToken } from "vscode";
import * as cli from "../cli";
import { ProgressCallback } from "../commandRunner";
import { ProgressCallback } from "../progress";
import { DatabaseItem } from "../local-databases";
import {
getOnDiskWorkspaceFolders,

View File

@@ -1,6 +1,6 @@
import { CancellationToken } from "vscode";
import { CodeQLCliServer } from "./cli";
import { ProgressCallback } from "./commandRunner";
import { ProgressCallback } from "./progress";
import { DatabaseItem } from "./local-databases";
import { InitialQueryInfo, LocalQueryInfo } from "./query-results";
import { QueryWithResults } from "./run-queries-shared";

View File

@@ -11,7 +11,7 @@ import {
getQlPackForDbscheme,
showBinaryChoiceDialog,
} from "./helpers";
import { ProgressCallback, UserCancellationException } from "./commandRunner";
import { ProgressCallback, UserCancellationException } from "./progress";
import { getErrorMessage } from "./pure/helpers-pure";
import { FALLBACK_QLPACK_FILENAME, getQlPackPath } from "./pure/ql";
import { App } from "./common/app";

View File

@@ -12,7 +12,7 @@ import {
window,
} from "vscode";
import { isCanary, AUTOSAVE_SETTING } from "./config";
import { UserCancellationException } from "./commandRunner";
import { UserCancellationException } from "./progress";
import {
pathExists,
readFile,

View File

@@ -16,7 +16,7 @@ import {
} from "./config";
import * as appInsights from "applicationinsights";
import { extLogger } from "./common";
import { UserCancellationException } from "./commandRunner";
import { UserCancellationException } from "./progress";
import { showBinaryChoiceWithUrlDialog } from "./helpers";
import { RedactableError } from "./pure/errors";

View File

@@ -13,7 +13,7 @@ import {
ProgressCallback,
UserCancellationException,
withProgress,
} from "../commandRunner";
} from "../progress";
import { showInformationMessageWithAction } from "../helpers";
import { extLogger } from "../common";
import { createGist } from "./gh-api/gh-api-client";

View File

@@ -1,4 +1,4 @@
import { UserCancellationException } from "../commandRunner";
import { UserCancellationException } from "../progress";
import { DbManager } from "../databases/db-manager";
import { DbItemKind } from "../databases/db-item";

View File

@@ -18,7 +18,7 @@ import {
getRemoteControllerRepo,
setRemoteControllerRepo,
} from "../config";
import { ProgressCallback, UserCancellationException } from "../commandRunner";
import { ProgressCallback, UserCancellationException } from "../progress";
import { RequestError } from "@octokit/types/dist-types";
import { QueryMetadata } from "../pure/interface-types";
import { getErrorMessage, REPO_REGEX } from "../pure/helpers-pure";

View File

@@ -55,7 +55,7 @@ import {
ProgressCallback,
UserCancellationException,
withProgress,
} from "../commandRunner";
} from "../progress";
import { CodeQLCliServer } from "../cli";
import {
defaultFilterSortState,

View File

@@ -25,7 +25,7 @@ import {
} from "../../../../src/variant-analysis/shared/variant-analysis";
import { VariantAnalysis as VariantAnalysisApiResponse } from "../../../../src/variant-analysis/gh-api/variant-analysis";
import { createMockApiResponse } from "../../../factories/variant-analysis/gh-api/variant-analysis-api-response";
import { UserCancellationException } from "../../../../src/commandRunner";
import { UserCancellationException } from "../../../../src/progress";
import { Repository } from "../../../../src/variant-analysis/gh-api/repository";
import { DbManager } from "../../../../src/databases/db-manager";
import { ExtensionApp } from "../../../../src/common/vscode/vscode-app";

View File

@@ -13,7 +13,7 @@ import {
FullDatabaseOptions,
} from "../../../src/local-databases";
import { Logger } from "../../../src/common";
import { ProgressCallback } from "../../../src/commandRunner";
import { ProgressCallback } from "../../../src/progress";
import { CodeQLCliServer, DbInfo } from "../../../src/cli";
import {
encodeArchiveBasePath,

View File

@@ -38,7 +38,7 @@ import {
showInformationMessageWithAction,
walkDirectory,
} from "../../../src/helpers";
import { reportStreamProgress } from "../../../src/commandRunner";
import { reportStreamProgress } from "../../../src/progress";
import { QueryLanguage } from "../../../src/common/query-language";
import { Setting } from "../../../src/config";

View File

@@ -9,7 +9,7 @@ import {
TelemetryListener,
telemetryListener as globalTelemetryListener,
} from "../../../src/telemetry";
import { UserCancellationException } from "../../../src/commandRunner";
import { UserCancellationException } from "../../../src/progress";
import { ENABLE_TELEMETRY } from "../../../src/config";
import { createMockExtensionContext } from "./index";
import { vscodeGetConfigurationMock } from "../test-config";