Remove codeQL.monitorRemoteQuery command
This commit is contained in:
@@ -4,28 +4,28 @@ import {
|
|||||||
CancellationTokenSource,
|
CancellationTokenSource,
|
||||||
commands,
|
commands,
|
||||||
Disposable,
|
Disposable,
|
||||||
|
env,
|
||||||
ExtensionContext,
|
ExtensionContext,
|
||||||
extensions,
|
extensions,
|
||||||
languages,
|
languages,
|
||||||
ProgressLocation,
|
ProgressLocation,
|
||||||
ProgressOptions,
|
ProgressOptions,
|
||||||
Uri,
|
ProviderResult,
|
||||||
window as Window,
|
|
||||||
env,
|
|
||||||
window,
|
|
||||||
QuickPickItem,
|
QuickPickItem,
|
||||||
Range,
|
Range,
|
||||||
workspace,
|
Uri,
|
||||||
ProviderResult,
|
|
||||||
version as vscodeVersion,
|
version as vscodeVersion,
|
||||||
|
window as Window,
|
||||||
|
window,
|
||||||
|
workspace,
|
||||||
} from "vscode";
|
} from "vscode";
|
||||||
import { LanguageClient } from "vscode-languageclient/node";
|
import { LanguageClient } from "vscode-languageclient/node";
|
||||||
import { platform, arch } from "os";
|
import { arch, platform } from "os";
|
||||||
import { ensureDir } from "fs-extra";
|
import { ensureDir } from "fs-extra";
|
||||||
import { join, basename } from "path";
|
import { basename, join } from "path";
|
||||||
import { dirSync } from "tmp-promise";
|
import { dirSync } from "tmp-promise";
|
||||||
import { testExplorerExtensionId, TestHub } from "vscode-test-adapter-api";
|
import { testExplorerExtensionId, TestHub } from "vscode-test-adapter-api";
|
||||||
import { parse, lt } from "semver";
|
import { lt, parse } from "semver";
|
||||||
|
|
||||||
import { AstViewer } from "./astViewer";
|
import { AstViewer } from "./astViewer";
|
||||||
import {
|
import {
|
||||||
@@ -47,10 +47,10 @@ import { install } from "./languageSupport";
|
|||||||
import { DatabaseItem, DatabaseManager } from "./databases";
|
import { DatabaseItem, DatabaseManager } from "./databases";
|
||||||
import { DatabaseUI } from "./databases-ui";
|
import { DatabaseUI } from "./databases-ui";
|
||||||
import {
|
import {
|
||||||
TemplateQueryDefinitionProvider,
|
|
||||||
TemplateQueryReferenceProvider,
|
|
||||||
TemplatePrintAstProvider,
|
TemplatePrintAstProvider,
|
||||||
TemplatePrintCfgProvider,
|
TemplatePrintCfgProvider,
|
||||||
|
TemplateQueryDefinitionProvider,
|
||||||
|
TemplateQueryReferenceProvider,
|
||||||
} from "./contextual/templateProvider";
|
} from "./contextual/templateProvider";
|
||||||
import {
|
import {
|
||||||
DEFAULT_DISTRIBUTION_VERSION_RANGE,
|
DEFAULT_DISTRIBUTION_VERSION_RANGE,
|
||||||
@@ -64,22 +64,22 @@ import {
|
|||||||
} from "./distribution";
|
} from "./distribution";
|
||||||
import {
|
import {
|
||||||
findLanguage,
|
findLanguage,
|
||||||
tmpDirDisposal,
|
|
||||||
showBinaryChoiceDialog,
|
|
||||||
showAndLogErrorMessage,
|
showAndLogErrorMessage,
|
||||||
showAndLogWarningMessage,
|
showAndLogExceptionWithTelemetry,
|
||||||
showAndLogInformationMessage,
|
showAndLogInformationMessage,
|
||||||
|
showAndLogWarningMessage,
|
||||||
|
showBinaryChoiceDialog,
|
||||||
showInformationMessageWithAction,
|
showInformationMessageWithAction,
|
||||||
tmpDir,
|
tmpDir,
|
||||||
showAndLogExceptionWithTelemetry,
|
tmpDirDisposal,
|
||||||
} from "./helpers";
|
} from "./helpers";
|
||||||
import { asError, assertNever, getErrorMessage } from "./pure/helpers-pure";
|
import { asError, assertNever, getErrorMessage } from "./pure/helpers-pure";
|
||||||
import { spawnIdeServer } from "./ide-server";
|
import { spawnIdeServer } from "./ide-server";
|
||||||
import { ResultsView } from "./interface";
|
import { ResultsView } from "./interface";
|
||||||
import { WebviewReveal } from "./interface-utils";
|
import { WebviewReveal } from "./interface-utils";
|
||||||
import {
|
import {
|
||||||
ideServerLogger,
|
|
||||||
extLogger,
|
extLogger,
|
||||||
|
ideServerLogger,
|
||||||
ProgressReporter,
|
ProgressReporter,
|
||||||
queryServerLogger,
|
queryServerLogger,
|
||||||
} from "./common";
|
} from "./common";
|
||||||
@@ -97,8 +97,8 @@ import {
|
|||||||
commandRunner,
|
commandRunner,
|
||||||
commandRunnerWithProgress,
|
commandRunnerWithProgress,
|
||||||
ProgressCallback,
|
ProgressCallback,
|
||||||
withProgress,
|
|
||||||
ProgressUpdate,
|
ProgressUpdate,
|
||||||
|
withProgress,
|
||||||
} from "./commandRunner";
|
} from "./commandRunner";
|
||||||
import { CodeQlStatusBarHandler } from "./status-bar";
|
import { CodeQlStatusBarHandler } from "./status-bar";
|
||||||
|
|
||||||
@@ -115,7 +115,6 @@ import {
|
|||||||
exportSelectedRemoteQueryResults,
|
exportSelectedRemoteQueryResults,
|
||||||
exportVariantAnalysisResults,
|
exportVariantAnalysisResults,
|
||||||
} from "./remote-queries/export-results";
|
} from "./remote-queries/export-results";
|
||||||
import { RemoteQuery } from "./remote-queries/remote-query";
|
|
||||||
import { EvalLogViewer } from "./eval-log-viewer";
|
import { EvalLogViewer } from "./eval-log-viewer";
|
||||||
import { SummaryLanguageSupport } from "./log-insights/summary-language-support";
|
import { SummaryLanguageSupport } from "./log-insights/summary-language-support";
|
||||||
import { JoinOrderScannerProvider } from "./log-insights/join-order";
|
import { JoinOrderScannerProvider } from "./log-insights/join-order";
|
||||||
@@ -1129,15 +1128,6 @@ async function activateWithInstalledDistribution(
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
ctx.subscriptions.push(
|
|
||||||
commandRunner(
|
|
||||||
"codeQL.monitorRemoteQuery",
|
|
||||||
async (queryId: string, query: RemoteQuery, token: CancellationToken) => {
|
|
||||||
await rqm.monitorRemoteQuery(queryId, query, token);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
ctx.subscriptions.push(
|
ctx.subscriptions.push(
|
||||||
commandRunner("codeQL.copyRepoList", async (queryId: string) => {
|
commandRunner("codeQL.copyRepoList", async (queryId: string) => {
|
||||||
await rqm.copyRemoteQueryRepoListToClipboard(queryId);
|
await rqm.copyRemoteQueryRepoListToClipboard(queryId);
|
||||||
|
|||||||
@@ -539,11 +539,7 @@ export class QueryHistoryManager extends DisposableObject {
|
|||||||
await Promise.all(
|
await Promise.all(
|
||||||
this.treeDataProvider.allHistory.map(async (item) => {
|
this.treeDataProvider.allHistory.map(async (item) => {
|
||||||
if (item.t === "remote") {
|
if (item.t === "remote") {
|
||||||
await this.remoteQueriesManager.rehydrateRemoteQuery(
|
await this.remoteQueriesManager.rehydrateRemoteQuery(item.queryId);
|
||||||
item.queryId,
|
|
||||||
item.remoteQuery,
|
|
||||||
item.status,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (item.t === "variant-analysis") {
|
if (item.t === "variant-analysis") {
|
||||||
await this.variantAnalysisManager.rehydrateVariantAnalysis(
|
await this.variantAnalysisManager.rehydrateVariantAnalysis(
|
||||||
|
|||||||
@@ -1,113 +1,11 @@
|
|||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import { pathExists, readFile, writeFile } from "fs-extra";
|
import { pathExists, writeFile } from "fs-extra";
|
||||||
import {
|
|
||||||
showAndLogExceptionWithTelemetry,
|
|
||||||
showAndLogWarningMessage,
|
|
||||||
tmpDir,
|
|
||||||
} from "../../helpers";
|
|
||||||
import { Credentials } from "../../common/authentication";
|
import { Credentials } from "../../common/authentication";
|
||||||
import { extLogger } from "../../common";
|
import { extLogger } from "../../common";
|
||||||
import { RemoteQueryWorkflowResult } from "../remote-query-workflow-result";
|
import { createDownloadPath, DownloadLink } from "../download-link";
|
||||||
import { DownloadLink, createDownloadPath } from "../download-link";
|
|
||||||
import { RemoteQuery } from "../remote-query";
|
import { RemoteQuery } from "../remote-query";
|
||||||
import {
|
|
||||||
RemoteQueryFailureIndexItem,
|
|
||||||
RemoteQueryResultIndex,
|
|
||||||
RemoteQuerySuccessIndexItem,
|
|
||||||
} from "../remote-query-result-index";
|
|
||||||
import { asError, getErrorMessage } from "../../pure/helpers-pure";
|
|
||||||
import { unzipFile } from "../../pure/zip";
|
import { unzipFile } from "../../pure/zip";
|
||||||
import { VariantAnalysis } from "../shared/variant-analysis";
|
import { VariantAnalysis } from "../shared/variant-analysis";
|
||||||
import { redactableError } from "../../pure/errors";
|
|
||||||
|
|
||||||
export const RESULT_INDEX_ARTIFACT_NAME = "result-index";
|
|
||||||
|
|
||||||
interface ApiSuccessIndexItem {
|
|
||||||
nwo: string;
|
|
||||||
id: string;
|
|
||||||
sha?: string;
|
|
||||||
results_count: number;
|
|
||||||
bqrs_file_size: number;
|
|
||||||
sarif_file_size?: number;
|
|
||||||
source_location_prefix: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ApiFailureIndexItem {
|
|
||||||
nwo: string;
|
|
||||||
id: string;
|
|
||||||
error: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ApiResultIndex {
|
|
||||||
successes: ApiSuccessIndexItem[];
|
|
||||||
failures: ApiFailureIndexItem[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getRemoteQueryIndex(
|
|
||||||
credentials: Credentials,
|
|
||||||
remoteQuery: RemoteQuery,
|
|
||||||
): Promise<RemoteQueryResultIndex | undefined> {
|
|
||||||
const controllerRepo = remoteQuery.controllerRepository;
|
|
||||||
const owner = controllerRepo.owner;
|
|
||||||
const repoName = controllerRepo.name;
|
|
||||||
const workflowRunId = remoteQuery.actionsWorkflowRunId;
|
|
||||||
|
|
||||||
const workflowUri = `https://github.com/${owner}/${repoName}/actions/runs/${workflowRunId}`;
|
|
||||||
const artifactsUrlPath = `/repos/${owner}/${repoName}/actions/artifacts`;
|
|
||||||
|
|
||||||
const artifactList = await listWorkflowRunArtifacts(
|
|
||||||
credentials,
|
|
||||||
owner,
|
|
||||||
repoName,
|
|
||||||
workflowRunId,
|
|
||||||
);
|
|
||||||
const resultIndexArtifactId = tryGetArtifactIDfromName(
|
|
||||||
RESULT_INDEX_ARTIFACT_NAME,
|
|
||||||
artifactList,
|
|
||||||
);
|
|
||||||
if (!resultIndexArtifactId) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
const resultIndex = await getResultIndex(
|
|
||||||
credentials,
|
|
||||||
owner,
|
|
||||||
repoName,
|
|
||||||
resultIndexArtifactId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const successes = resultIndex?.successes.map((item) => {
|
|
||||||
const artifactId = getArtifactIDfromName(
|
|
||||||
item.id,
|
|
||||||
workflowUri,
|
|
||||||
artifactList,
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: item.id.toString(),
|
|
||||||
artifactId,
|
|
||||||
nwo: item.nwo,
|
|
||||||
sha: item.sha,
|
|
||||||
resultCount: item.results_count,
|
|
||||||
bqrsFileSize: item.bqrs_file_size,
|
|
||||||
sarifFileSize: item.sarif_file_size,
|
|
||||||
sourceLocationPrefix: item.source_location_prefix,
|
|
||||||
} as RemoteQuerySuccessIndexItem;
|
|
||||||
});
|
|
||||||
|
|
||||||
const failures = resultIndex?.failures.map((item) => {
|
|
||||||
return {
|
|
||||||
id: item.id.toString(),
|
|
||||||
nwo: item.nwo,
|
|
||||||
error: item.error,
|
|
||||||
} as RemoteQueryFailureIndexItem;
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
artifactsUrlPath,
|
|
||||||
successes: successes || [],
|
|
||||||
failures: failures || [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function cancelRemoteQuery(
|
export async function cancelRemoteQuery(
|
||||||
credentials: Credentials,
|
credentials: Credentials,
|
||||||
@@ -175,208 +73,6 @@ export async function downloadArtifactFromLink(
|
|||||||
return join(extractedPath, downloadLink.innerFilePath || "");
|
return join(extractedPath, downloadLink.innerFilePath || "");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether a specific artifact is present in the list of artifacts of a workflow run.
|
|
||||||
* @param credentials Credentials for authenticating to the GitHub API.
|
|
||||||
* @param owner
|
|
||||||
* @param repo
|
|
||||||
* @param workflowRunId The ID of the workflow run to get the artifact for.
|
|
||||||
* @param artifactName The artifact name, as a string.
|
|
||||||
* @returns A boolean indicating if the artifact is available.
|
|
||||||
*/
|
|
||||||
export async function isArtifactAvailable(
|
|
||||||
credentials: Credentials,
|
|
||||||
owner: string,
|
|
||||||
repo: string,
|
|
||||||
workflowRunId: number,
|
|
||||||
artifactName: string,
|
|
||||||
): Promise<boolean> {
|
|
||||||
const artifactList = await listWorkflowRunArtifacts(
|
|
||||||
credentials,
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
workflowRunId,
|
|
||||||
);
|
|
||||||
|
|
||||||
return tryGetArtifactIDfromName(artifactName, artifactList) !== undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Downloads the result index artifact and extracts the result index items.
|
|
||||||
* @param credentials Credentials for authenticating to the GitHub API.
|
|
||||||
* @param owner
|
|
||||||
* @param repo
|
|
||||||
* @param workflowRunId The ID of the workflow run to get the result index for.
|
|
||||||
* @returns An object containing the result index.
|
|
||||||
*/
|
|
||||||
async function getResultIndex(
|
|
||||||
credentials: Credentials,
|
|
||||||
owner: string,
|
|
||||||
repo: string,
|
|
||||||
artifactId: number,
|
|
||||||
): Promise<ApiResultIndex | undefined> {
|
|
||||||
const artifactPath = await downloadArtifact(
|
|
||||||
credentials,
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
artifactId,
|
|
||||||
);
|
|
||||||
const indexFilePath = join(artifactPath, "index.json");
|
|
||||||
if (!(await pathExists(indexFilePath))) {
|
|
||||||
void showAndLogWarningMessage(
|
|
||||||
"Could not find an `index.json` file in the result artifact.",
|
|
||||||
);
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
const resultIndex = await readFile(join(artifactPath, "index.json"), "utf8");
|
|
||||||
|
|
||||||
try {
|
|
||||||
return JSON.parse(resultIndex);
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error(`Invalid result index file: ${error}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the status of a workflow run.
|
|
||||||
* @param credentials Credentials for authenticating to the GitHub API.
|
|
||||||
* @param owner
|
|
||||||
* @param repo
|
|
||||||
* @param workflowRunId The ID of the workflow run to get the result index for.
|
|
||||||
* @returns The workflow run status.
|
|
||||||
*/
|
|
||||||
export async function getWorkflowStatus(
|
|
||||||
credentials: Credentials,
|
|
||||||
owner: string,
|
|
||||||
repo: string,
|
|
||||||
workflowRunId: number,
|
|
||||||
): Promise<RemoteQueryWorkflowResult> {
|
|
||||||
const octokit = await credentials.getOctokit();
|
|
||||||
|
|
||||||
const workflowRun = await octokit.rest.actions.getWorkflowRun({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
run_id: workflowRunId,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (workflowRun.data.status === "completed") {
|
|
||||||
if (workflowRun.data.conclusion === "success") {
|
|
||||||
return { status: "CompletedSuccessfully" };
|
|
||||||
} else {
|
|
||||||
const error = getWorkflowError(workflowRun.data.conclusion);
|
|
||||||
return { status: "CompletedUnsuccessfully", error };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { status: "InProgress" };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lists the workflow run artifacts for the given workflow run ID.
|
|
||||||
* @param credentials Credentials for authenticating to the GitHub API.
|
|
||||||
* @param owner
|
|
||||||
* @param repo
|
|
||||||
* @param workflowRunId The ID of the workflow run to list artifacts for.
|
|
||||||
* @returns An array of artifact details (including artifact name and ID).
|
|
||||||
*/
|
|
||||||
async function listWorkflowRunArtifacts(
|
|
||||||
credentials: Credentials,
|
|
||||||
owner: string,
|
|
||||||
repo: string,
|
|
||||||
workflowRunId: number,
|
|
||||||
) {
|
|
||||||
const octokit = await credentials.getOctokit();
|
|
||||||
|
|
||||||
// There are limits on the number of artifacts that are returned by the API
|
|
||||||
// so we use paging to make sure we retrieve all of them.
|
|
||||||
let morePages = true;
|
|
||||||
let pageNum = 1;
|
|
||||||
const allArtifacts = [];
|
|
||||||
|
|
||||||
while (morePages) {
|
|
||||||
const response = await octokit.rest.actions.listWorkflowRunArtifacts({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
run_id: workflowRunId,
|
|
||||||
per_page: 100,
|
|
||||||
page: pageNum,
|
|
||||||
});
|
|
||||||
|
|
||||||
allArtifacts.push(...response.data.artifacts);
|
|
||||||
pageNum++;
|
|
||||||
if (response.data.artifacts.length < 100) {
|
|
||||||
morePages = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return allArtifacts;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param artifactName The artifact name, as a string.
|
|
||||||
* @param artifacts An array of artifact details (from the "list workflow run artifacts" API response).
|
|
||||||
* @returns The artifact ID corresponding to the given artifact name.
|
|
||||||
*/
|
|
||||||
function getArtifactIDfromName(
|
|
||||||
artifactName: string,
|
|
||||||
workflowUri: string,
|
|
||||||
artifacts: Array<{ id: number; name: string }>,
|
|
||||||
): number {
|
|
||||||
const artifactId = tryGetArtifactIDfromName(artifactName, artifacts);
|
|
||||||
|
|
||||||
if (!artifactId) {
|
|
||||||
const errorMessage = `Could not find artifact with name ${artifactName} in workflow ${workflowUri}.
|
|
||||||
Please check whether the workflow run has successfully completed.`;
|
|
||||||
throw Error(errorMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
return artifactId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param artifactName The artifact name, as a string.
|
|
||||||
* @param artifacts An array of artifact details (from the "list workflow run artifacts" API response).
|
|
||||||
* @returns The artifact ID corresponding to the given artifact name, if it exists.
|
|
||||||
*/
|
|
||||||
function tryGetArtifactIDfromName(
|
|
||||||
artifactName: string,
|
|
||||||
artifacts: Array<{ id: number; name: string }>,
|
|
||||||
): number | undefined {
|
|
||||||
const artifact = artifacts.find((a) => a.name === artifactName);
|
|
||||||
|
|
||||||
return artifact?.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Downloads an artifact from a workflow run.
|
|
||||||
* @param credentials Credentials for authenticating to the GitHub API.
|
|
||||||
* @param owner
|
|
||||||
* @param repo
|
|
||||||
* @param artifactId The ID of the artifact to download.
|
|
||||||
* @returns The path to the enclosing directory of the unzipped artifact.
|
|
||||||
*/
|
|
||||||
async function downloadArtifact(
|
|
||||||
credentials: Credentials,
|
|
||||||
owner: string,
|
|
||||||
repo: string,
|
|
||||||
artifactId: number,
|
|
||||||
): Promise<string> {
|
|
||||||
const octokit = await credentials.getOctokit();
|
|
||||||
const response = await octokit.rest.actions.downloadArtifact({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
artifact_id: artifactId,
|
|
||||||
archive_format: "zip",
|
|
||||||
});
|
|
||||||
const artifactPath = join(tmpDir.name, `${artifactId}`);
|
|
||||||
await unzipBuffer(
|
|
||||||
response.data as ArrayBuffer,
|
|
||||||
`${artifactPath}.zip`,
|
|
||||||
artifactPath,
|
|
||||||
);
|
|
||||||
return artifactPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function unzipBuffer(
|
async function unzipBuffer(
|
||||||
data: ArrayBuffer,
|
data: ArrayBuffer,
|
||||||
filePath: string,
|
filePath: string,
|
||||||
@@ -388,119 +84,3 @@ async function unzipBuffer(
|
|||||||
void extLogger.log(`Unzipping file to ${destinationPath}`);
|
void extLogger.log(`Unzipping file to ${destinationPath}`);
|
||||||
await unzipFile(filePath, destinationPath);
|
await unzipFile(filePath, destinationPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getWorkflowError(conclusion: string | null): string {
|
|
||||||
if (!conclusion) {
|
|
||||||
return "Workflow finished without a conclusion";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (conclusion === "cancelled") {
|
|
||||||
return "Variant analysis execution was cancelled.";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (conclusion === "timed_out") {
|
|
||||||
return "Variant analysis execution timed out.";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (conclusion === "failure") {
|
|
||||||
// TODO: Get the actual error from the workflow or potentially
|
|
||||||
// from an artifact from the action itself.
|
|
||||||
return "Variant analysis execution has failed.";
|
|
||||||
}
|
|
||||||
|
|
||||||
return `Unexpected variant analysis execution conclusion: ${conclusion}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const repositoriesMetadataQuery = `query Stars($repos: String!, $pageSize: Int!, $cursor: String) {
|
|
||||||
search(
|
|
||||||
query: $repos
|
|
||||||
type: REPOSITORY
|
|
||||||
first: $pageSize
|
|
||||||
after: $cursor
|
|
||||||
) {
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
... on Repository {
|
|
||||||
name
|
|
||||||
owner {
|
|
||||||
login
|
|
||||||
}
|
|
||||||
stargazerCount
|
|
||||||
updatedAt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cursor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}`;
|
|
||||||
|
|
||||||
type RepositoriesMetadataQueryResponse = {
|
|
||||||
search: {
|
|
||||||
edges: Array<{
|
|
||||||
cursor: string;
|
|
||||||
node: {
|
|
||||||
name: string;
|
|
||||||
owner: {
|
|
||||||
login: string;
|
|
||||||
};
|
|
||||||
stargazerCount: number;
|
|
||||||
updatedAt: string; // Actually a ISO Date string
|
|
||||||
};
|
|
||||||
}>;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export type RepositoriesMetadata = Record<
|
|
||||||
string,
|
|
||||||
{ starCount: number; lastUpdated: number }
|
|
||||||
>;
|
|
||||||
|
|
||||||
export async function getRepositoriesMetadata(
|
|
||||||
credentials: Credentials,
|
|
||||||
nwos: string[],
|
|
||||||
pageSize = 100,
|
|
||||||
): Promise<RepositoriesMetadata> {
|
|
||||||
const octokit = await credentials.getOctokit();
|
|
||||||
const repos = `repo:${nwos.join(" repo:")} fork:true`;
|
|
||||||
let cursor = null;
|
|
||||||
const metadata: RepositoriesMetadata = {};
|
|
||||||
try {
|
|
||||||
do {
|
|
||||||
const response: RepositoriesMetadataQueryResponse = await octokit.graphql(
|
|
||||||
{
|
|
||||||
query: repositoriesMetadataQuery,
|
|
||||||
repos,
|
|
||||||
pageSize,
|
|
||||||
cursor,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
cursor =
|
|
||||||
response.search.edges.length === pageSize
|
|
||||||
? response.search.edges[pageSize - 1].cursor
|
|
||||||
: null;
|
|
||||||
|
|
||||||
for (const edge of response.search.edges) {
|
|
||||||
const node = edge.node;
|
|
||||||
const owner = node.owner.login;
|
|
||||||
const name = node.name;
|
|
||||||
const starCount = node.stargazerCount;
|
|
||||||
// lastUpdated is always negative since it happened in the past.
|
|
||||||
const lastUpdated = new Date(node.updatedAt).getTime() - Date.now();
|
|
||||||
metadata[`${owner}/${name}`] = {
|
|
||||||
starCount,
|
|
||||||
lastUpdated,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} while (cursor);
|
|
||||||
} catch (e) {
|
|
||||||
void showAndLogExceptionWithTelemetry(
|
|
||||||
redactableError(
|
|
||||||
asError(e),
|
|
||||||
)`Error retrieving repository metadata for variant analysis: ${getErrorMessage(
|
|
||||||
e,
|
|
||||||
)}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return metadata;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,38 +1,16 @@
|
|||||||
import {
|
import { CancellationToken, env, EventEmitter, ExtensionContext } from "vscode";
|
||||||
CancellationToken,
|
|
||||||
commands,
|
|
||||||
env,
|
|
||||||
EventEmitter,
|
|
||||||
ExtensionContext,
|
|
||||||
} from "vscode";
|
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import { pathExists, readFile, remove, writeFile } from "fs-extra";
|
import { pathExists, readFile, remove } from "fs-extra";
|
||||||
import { EOL } from "os";
|
import { EOL } from "os";
|
||||||
|
|
||||||
import { CodeQLCliServer } from "../cli";
|
import { CodeQLCliServer } from "../cli";
|
||||||
import {
|
import { showAndLogExceptionWithTelemetry } from "../helpers";
|
||||||
showAndLogErrorMessage,
|
|
||||||
showAndLogExceptionWithTelemetry,
|
|
||||||
showAndLogInformationMessage,
|
|
||||||
showInformationMessageWithAction,
|
|
||||||
} from "../helpers";
|
|
||||||
import { Logger } from "../common";
|
import { Logger } from "../common";
|
||||||
import { RemoteQueriesView } from "./remote-queries-view";
|
import { RemoteQueriesView } from "./remote-queries-view";
|
||||||
import { RemoteQuery } from "./remote-query";
|
import { RemoteQuery } from "./remote-query";
|
||||||
import { RemoteQueriesMonitor } from "./remote-queries-monitor";
|
import { RemoteQueryResult } from "./remote-query-result";
|
||||||
import {
|
|
||||||
getRemoteQueryIndex,
|
|
||||||
getRepositoriesMetadata,
|
|
||||||
RepositoriesMetadata,
|
|
||||||
} from "./gh-api/gh-actions-api-client";
|
|
||||||
import { RemoteQueryResultIndex } from "./remote-query-result-index";
|
|
||||||
import {
|
|
||||||
RemoteQueryResult,
|
|
||||||
sumAnalysisSummariesResults,
|
|
||||||
} from "./remote-query-result";
|
|
||||||
import { DownloadLink } from "./download-link";
|
|
||||||
import { AnalysesResultsManager } from "./analyses-results-manager";
|
import { AnalysesResultsManager } from "./analyses-results-manager";
|
||||||
import { asError, assertNever, getErrorMessage } from "../pure/helpers-pure";
|
import { asError, getErrorMessage } from "../pure/helpers-pure";
|
||||||
import { QueryStatus } from "../query-status";
|
import { QueryStatus } from "../query-status";
|
||||||
import { DisposableObject } from "../pure/disposable-object";
|
import { DisposableObject } from "../pure/disposable-object";
|
||||||
import { AnalysisResults } from "./shared/analysis-result";
|
import { AnalysisResults } from "./shared/analysis-result";
|
||||||
@@ -72,13 +50,12 @@ export class RemoteQueriesManager extends DisposableObject {
|
|||||||
private readonly remoteQueryRemovedEventEmitter;
|
private readonly remoteQueryRemovedEventEmitter;
|
||||||
private readonly remoteQueryStatusUpdateEventEmitter;
|
private readonly remoteQueryStatusUpdateEventEmitter;
|
||||||
|
|
||||||
private readonly remoteQueriesMonitor: RemoteQueriesMonitor;
|
|
||||||
private readonly analysesResultsManager: AnalysesResultsManager;
|
private readonly analysesResultsManager: AnalysesResultsManager;
|
||||||
private readonly view: RemoteQueriesView;
|
private readonly view: RemoteQueriesView;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
ctx: ExtensionContext,
|
ctx: ExtensionContext,
|
||||||
private readonly app: App,
|
app: App,
|
||||||
cliServer: CodeQLCliServer,
|
cliServer: CodeQLCliServer,
|
||||||
private readonly storagePath: string,
|
private readonly storagePath: string,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
@@ -91,7 +68,6 @@ export class RemoteQueriesManager extends DisposableObject {
|
|||||||
logger,
|
logger,
|
||||||
);
|
);
|
||||||
this.view = new RemoteQueriesView(ctx, logger, this.analysesResultsManager);
|
this.view = new RemoteQueriesView(ctx, logger, this.analysesResultsManager);
|
||||||
this.remoteQueriesMonitor = new RemoteQueriesMonitor(logger);
|
|
||||||
|
|
||||||
this.remoteQueryAddedEventEmitter = this.push(
|
this.remoteQueryAddedEventEmitter = this.push(
|
||||||
new EventEmitter<NewQueryEvent>(),
|
new EventEmitter<NewQueryEvent>(),
|
||||||
@@ -110,19 +86,11 @@ export class RemoteQueriesManager extends DisposableObject {
|
|||||||
this.push(this.view);
|
this.push(this.view);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async rehydrateRemoteQuery(
|
public async rehydrateRemoteQuery(queryId: string) {
|
||||||
queryId: string,
|
|
||||||
query: RemoteQuery,
|
|
||||||
status: QueryStatus,
|
|
||||||
) {
|
|
||||||
if (!(await this.queryRecordExists(queryId))) {
|
if (!(await this.queryRecordExists(queryId))) {
|
||||||
// In this case, the query was deleted from disk, most likely because it was purged
|
// In this case, the query was deleted from disk, most likely because it was purged
|
||||||
// by another workspace.
|
// by another workspace.
|
||||||
this.remoteQueryRemovedEventEmitter.fire({ queryId });
|
this.remoteQueryRemovedEventEmitter.fire({ queryId });
|
||||||
} else if (status === QueryStatus.InProgress) {
|
|
||||||
// In this case, last time we checked, the query was still in progress.
|
|
||||||
// We need to setup the monitor to check for completion.
|
|
||||||
void commands.executeCommand("codeQL.monitorRemoteQuery", queryId, query);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,72 +129,6 @@ export class RemoteQueriesManager extends DisposableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async monitorRemoteQuery(
|
|
||||||
queryId: string,
|
|
||||||
remoteQuery: RemoteQuery,
|
|
||||||
cancellationToken: CancellationToken,
|
|
||||||
): Promise<void> {
|
|
||||||
const queryWorkflowResult = await this.remoteQueriesMonitor.monitorQuery(
|
|
||||||
remoteQuery,
|
|
||||||
this.app.credentials,
|
|
||||||
cancellationToken,
|
|
||||||
);
|
|
||||||
|
|
||||||
const executionEndTime = Date.now();
|
|
||||||
|
|
||||||
if (queryWorkflowResult.status === "CompletedSuccessfully") {
|
|
||||||
await this.downloadAvailableResults(
|
|
||||||
queryId,
|
|
||||||
remoteQuery,
|
|
||||||
executionEndTime,
|
|
||||||
);
|
|
||||||
} else if (queryWorkflowResult.status === "CompletedUnsuccessfully") {
|
|
||||||
if (queryWorkflowResult.error?.includes("cancelled")) {
|
|
||||||
// Workflow was cancelled on the server
|
|
||||||
this.remoteQueryStatusUpdateEventEmitter.fire({
|
|
||||||
queryId,
|
|
||||||
status: QueryStatus.Failed,
|
|
||||||
failureReason: "Cancelled",
|
|
||||||
});
|
|
||||||
await this.downloadAvailableResults(
|
|
||||||
queryId,
|
|
||||||
remoteQuery,
|
|
||||||
executionEndTime,
|
|
||||||
);
|
|
||||||
void showAndLogInformationMessage("Variant analysis was cancelled");
|
|
||||||
} else {
|
|
||||||
this.remoteQueryStatusUpdateEventEmitter.fire({
|
|
||||||
queryId,
|
|
||||||
status: QueryStatus.Failed,
|
|
||||||
failureReason: queryWorkflowResult.error,
|
|
||||||
});
|
|
||||||
void showAndLogErrorMessage(
|
|
||||||
`Variant analysis execution failed. Error: ${queryWorkflowResult.error}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if (queryWorkflowResult.status === "Cancelled") {
|
|
||||||
this.remoteQueryStatusUpdateEventEmitter.fire({
|
|
||||||
queryId,
|
|
||||||
status: QueryStatus.Failed,
|
|
||||||
failureReason: "Cancelled",
|
|
||||||
});
|
|
||||||
await this.downloadAvailableResults(
|
|
||||||
queryId,
|
|
||||||
remoteQuery,
|
|
||||||
executionEndTime,
|
|
||||||
);
|
|
||||||
void showAndLogInformationMessage("Variant analysis was cancelled");
|
|
||||||
} else if (queryWorkflowResult.status === "InProgress") {
|
|
||||||
// Should not get here. Only including this to ensure `assertNever` uses proper type checking.
|
|
||||||
void showAndLogExceptionWithTelemetry(
|
|
||||||
redactableError`Unexpected status: ${queryWorkflowResult.status}`,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// Ensure all cases are covered
|
|
||||||
assertNever(queryWorkflowResult.status);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async autoDownloadRemoteQueryResults(
|
public async autoDownloadRemoteQueryResults(
|
||||||
queryResult: RemoteQueryResult,
|
queryResult: RemoteQueryResult,
|
||||||
token: CancellationToken,
|
token: CancellationToken,
|
||||||
@@ -268,65 +170,10 @@ export class RemoteQueriesManager extends DisposableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private mapQueryResult(
|
|
||||||
executionEndTime: number,
|
|
||||||
resultIndex: RemoteQueryResultIndex,
|
|
||||||
queryId: string,
|
|
||||||
metadata: RepositoriesMetadata,
|
|
||||||
): RemoteQueryResult {
|
|
||||||
const analysisSummaries = resultIndex.successes.map((item) => ({
|
|
||||||
nwo: item.nwo,
|
|
||||||
databaseSha: item.sha || "HEAD",
|
|
||||||
resultCount: item.resultCount,
|
|
||||||
sourceLocationPrefix: item.sourceLocationPrefix,
|
|
||||||
fileSizeInBytes: item.sarifFileSize
|
|
||||||
? item.sarifFileSize
|
|
||||||
: item.bqrsFileSize,
|
|
||||||
starCount: metadata[item.nwo]?.starCount,
|
|
||||||
lastUpdated: metadata[item.nwo]?.lastUpdated,
|
|
||||||
downloadLink: {
|
|
||||||
id: item.artifactId.toString(),
|
|
||||||
urlPath: `${resultIndex.artifactsUrlPath}/${item.artifactId}`,
|
|
||||||
innerFilePath: item.sarifFileSize ? "results.sarif" : "results.bqrs",
|
|
||||||
queryId,
|
|
||||||
} as DownloadLink,
|
|
||||||
}));
|
|
||||||
const analysisFailures = resultIndex.failures.map((item) => ({
|
|
||||||
nwo: item.nwo,
|
|
||||||
error: item.error,
|
|
||||||
}));
|
|
||||||
|
|
||||||
return {
|
|
||||||
executionEndTime,
|
|
||||||
analysisSummaries,
|
|
||||||
analysisFailures,
|
|
||||||
queryId,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public async openResults(query: RemoteQuery, queryResult: RemoteQueryResult) {
|
public async openResults(query: RemoteQuery, queryResult: RemoteQueryResult) {
|
||||||
await this.view.showResults(query, queryResult);
|
await this.view.showResults(query, queryResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async askToOpenResults(
|
|
||||||
query: RemoteQuery,
|
|
||||||
queryResult: RemoteQueryResult,
|
|
||||||
): Promise<void> {
|
|
||||||
const totalResultCount = sumAnalysisSummariesResults(
|
|
||||||
queryResult.analysisSummaries,
|
|
||||||
);
|
|
||||||
const totalRepoCount = queryResult.analysisSummaries.length;
|
|
||||||
const message = `Query "${query.queryName}" run on ${totalRepoCount} repositories and returned ${totalResultCount} results`;
|
|
||||||
|
|
||||||
const shouldOpenView = await showInformationMessageWithAction(
|
|
||||||
message,
|
|
||||||
"View",
|
|
||||||
);
|
|
||||||
if (shouldOpenView) {
|
|
||||||
await this.openResults(query, queryResult);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getRemoteQueryResult(
|
private async getRemoteQueryResult(
|
||||||
queryId: string,
|
queryId: string,
|
||||||
): Promise<RemoteQueryResult> {
|
): Promise<RemoteQueryResult> {
|
||||||
@@ -336,15 +183,6 @@ export class RemoteQueriesManager extends DisposableObject {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async storeJsonFile<T>(
|
|
||||||
queryId: string,
|
|
||||||
fileName: string,
|
|
||||||
obj: T,
|
|
||||||
): Promise<void> {
|
|
||||||
const filePath = join(this.storagePath, queryId, fileName);
|
|
||||||
await writeFile(filePath, JSON.stringify(obj, null, 2), "utf8");
|
|
||||||
}
|
|
||||||
|
|
||||||
private async retrieveJsonFile<T>(
|
private async retrieveJsonFile<T>(
|
||||||
queryId: string,
|
queryId: string,
|
||||||
fileName: string,
|
fileName: string,
|
||||||
@@ -363,73 +201,6 @@ export class RemoteQueriesManager extends DisposableObject {
|
|||||||
return await pathExists(filePath);
|
return await pathExists(filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether there's a result index artifact available for the given query.
|
|
||||||
* If so, set the query status to `Completed` and auto-download the results.
|
|
||||||
*/
|
|
||||||
private async downloadAvailableResults(
|
|
||||||
queryId: string,
|
|
||||||
remoteQuery: RemoteQuery,
|
|
||||||
executionEndTime: number,
|
|
||||||
): Promise<void> {
|
|
||||||
const resultIndex = await getRemoteQueryIndex(
|
|
||||||
this.app.credentials,
|
|
||||||
remoteQuery,
|
|
||||||
);
|
|
||||||
if (resultIndex) {
|
|
||||||
const metadata = await this.getRepositoriesMetadata(resultIndex);
|
|
||||||
const queryResult = this.mapQueryResult(
|
|
||||||
executionEndTime,
|
|
||||||
resultIndex,
|
|
||||||
queryId,
|
|
||||||
metadata,
|
|
||||||
);
|
|
||||||
const resultCount = sumAnalysisSummariesResults(
|
|
||||||
queryResult.analysisSummaries,
|
|
||||||
);
|
|
||||||
this.remoteQueryStatusUpdateEventEmitter.fire({
|
|
||||||
queryId,
|
|
||||||
status: QueryStatus.Completed,
|
|
||||||
repositoryCount: queryResult.analysisSummaries.length,
|
|
||||||
resultCount,
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.storeJsonFile(queryId, "query-result.json", queryResult);
|
|
||||||
|
|
||||||
// Kick off auto-download of results in the background.
|
|
||||||
void commands.executeCommand(
|
|
||||||
"codeQL.autoDownloadRemoteQueryResults",
|
|
||||||
queryResult,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Ask if the user wants to open the results in the background.
|
|
||||||
void this.askToOpenResults(remoteQuery, queryResult).then(
|
|
||||||
noop,
|
|
||||||
(e: unknown) =>
|
|
||||||
void showAndLogExceptionWithTelemetry(
|
|
||||||
redactableError(
|
|
||||||
asError(e),
|
|
||||||
)`Could not open query results. ${getErrorMessage(e)}`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
const controllerRepo = `${remoteQuery.controllerRepository.owner}/${remoteQuery.controllerRepository.name}`;
|
|
||||||
const workflowRunUrl = `https://github.com/${controllerRepo}/actions/runs/${remoteQuery.actionsWorkflowRunId}`;
|
|
||||||
void showAndLogExceptionWithTelemetry(
|
|
||||||
redactableError`There was an issue retrieving the result for the query [${remoteQuery.queryName}](${workflowRunUrl}).`,
|
|
||||||
);
|
|
||||||
this.remoteQueryStatusUpdateEventEmitter.fire({
|
|
||||||
queryId,
|
|
||||||
status: QueryStatus.Failed,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getRepositoriesMetadata(resultIndex: RemoteQueryResultIndex) {
|
|
||||||
const nwos = resultIndex.successes.map((s) => s.nwo);
|
|
||||||
return await getRepositoriesMetadata(this.app.credentials, nwos);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pulled from the analysis results manager, so that we can get access to
|
// Pulled from the analysis results manager, so that we can get access to
|
||||||
// analyses results from the "export results" command.
|
// analyses results from the "export results" command.
|
||||||
public getAnalysesResults(queryId: string): AnalysisResults[] {
|
public getAnalysesResults(queryId: string): AnalysisResults[] {
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
import * as vscode from "vscode";
|
|
||||||
import { Logger } from "../common";
|
|
||||||
import { Credentials } from "../common/authentication";
|
|
||||||
import { sleep } from "../pure/time";
|
|
||||||
import {
|
|
||||||
getWorkflowStatus,
|
|
||||||
isArtifactAvailable,
|
|
||||||
RESULT_INDEX_ARTIFACT_NAME,
|
|
||||||
} from "./gh-api/gh-actions-api-client";
|
|
||||||
import { RemoteQuery } from "./remote-query";
|
|
||||||
import { RemoteQueryWorkflowResult } from "./remote-query-workflow-result";
|
|
||||||
|
|
||||||
export class RemoteQueriesMonitor {
|
|
||||||
// With a sleep of 5 seconds, the maximum number of attempts takes
|
|
||||||
// us to just over 2 days worth of monitoring.
|
|
||||||
private static readonly maxAttemptCount = 17280;
|
|
||||||
private static readonly sleepTime = 5000;
|
|
||||||
|
|
||||||
constructor(private readonly logger: Logger) {}
|
|
||||||
|
|
||||||
public async monitorQuery(
|
|
||||||
remoteQuery: RemoteQuery,
|
|
||||||
credentials: Credentials,
|
|
||||||
cancellationToken: vscode.CancellationToken,
|
|
||||||
): Promise<RemoteQueryWorkflowResult> {
|
|
||||||
let attemptCount = 0;
|
|
||||||
|
|
||||||
while (attemptCount <= RemoteQueriesMonitor.maxAttemptCount) {
|
|
||||||
await sleep(RemoteQueriesMonitor.sleepTime);
|
|
||||||
|
|
||||||
if (cancellationToken && cancellationToken.isCancellationRequested) {
|
|
||||||
return { status: "Cancelled" };
|
|
||||||
}
|
|
||||||
|
|
||||||
const workflowStatus = await getWorkflowStatus(
|
|
||||||
credentials,
|
|
||||||
remoteQuery.controllerRepository.owner,
|
|
||||||
remoteQuery.controllerRepository.name,
|
|
||||||
remoteQuery.actionsWorkflowRunId,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Even if the workflow indicates it has completed, artifacts
|
|
||||||
// might still take a while to become available. So we need to
|
|
||||||
// check for the artifact before we can declare the workflow
|
|
||||||
// as having completed.
|
|
||||||
if (workflowStatus.status === "CompletedSuccessfully") {
|
|
||||||
const resultIndexAvailable = await isArtifactAvailable(
|
|
||||||
credentials,
|
|
||||||
remoteQuery.controllerRepository.owner,
|
|
||||||
remoteQuery.controllerRepository.name,
|
|
||||||
remoteQuery.actionsWorkflowRunId,
|
|
||||||
RESULT_INDEX_ARTIFACT_NAME,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (resultIndexAvailable) {
|
|
||||||
return workflowStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We don't have a result-index yet, so we'll keep monitoring.
|
|
||||||
} else if (workflowStatus.status !== "InProgress") {
|
|
||||||
return workflowStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
attemptCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
void this.logger.log("Variant analysis monitoring timed out after 2 days");
|
|
||||||
return { status: "Cancelled" };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user