Remove remote queries manager
This commit is contained in:
@@ -101,8 +101,6 @@ import {
|
||||
withProgress,
|
||||
} from "./commandRunner";
|
||||
import { CodeQlStatusBarHandler } from "./status-bar";
|
||||
|
||||
import { RemoteQueriesManager } from "./remote-queries/remote-queries-manager";
|
||||
import { URLSearchParams } from "url";
|
||||
import {
|
||||
handleDownloadPacks,
|
||||
@@ -652,23 +650,12 @@ async function activateWithInstalledDistribution(
|
||||
),
|
||||
);
|
||||
|
||||
void extLogger.log("Initializing remote queries manager.");
|
||||
const rqm = new RemoteQueriesManager(
|
||||
ctx,
|
||||
app,
|
||||
cliServer,
|
||||
queryStorageDir,
|
||||
extLogger,
|
||||
);
|
||||
ctx.subscriptions.push(rqm);
|
||||
|
||||
void extLogger.log("Initializing query history.");
|
||||
const qhm = new QueryHistoryManager(
|
||||
app,
|
||||
qs,
|
||||
dbm,
|
||||
localQueryResultsView,
|
||||
rqm,
|
||||
variantAnalysisManager,
|
||||
evalLogViewer,
|
||||
queryStorageDir,
|
||||
|
||||
@@ -54,7 +54,6 @@ import { pathExists } from "fs-extra";
|
||||
import { CliVersionConstraint } from "../cli";
|
||||
import { HistoryItemLabelProvider } from "./history-item-label-provider";
|
||||
import { cancelRemoteQuery } from "../remote-queries/gh-api/gh-actions-api-client";
|
||||
import { RemoteQueriesManager } from "../remote-queries/remote-queries-manager";
|
||||
import { RemoteQueryHistoryItem } from "../remote-queries/remote-query-history-item";
|
||||
import { ResultsView } from "../interface";
|
||||
import { WebviewReveal } from "../interface-utils";
|
||||
@@ -142,7 +141,6 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
private readonly qs: QueryRunner,
|
||||
private readonly dbm: DatabaseManager,
|
||||
private readonly localQueriesResultsView: ResultsView,
|
||||
private readonly remoteQueriesManager: RemoteQueriesManager,
|
||||
private readonly variantAnalysisManager: VariantAnalysisManager,
|
||||
private readonly evalLogViewer: EvalLogViewer,
|
||||
private readonly queryStorageDir: string,
|
||||
@@ -372,7 +370,6 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
);
|
||||
|
||||
this.registerQueryHistoryScrubber(queryHistoryConfigListener, this, ctx);
|
||||
this.registerToRemoteQueriesEvents();
|
||||
this.registerToVariantAnalysisEvents();
|
||||
}
|
||||
|
||||
@@ -477,57 +474,6 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
this.push(variantAnalysisRemovedSubscription);
|
||||
}
|
||||
|
||||
private registerToRemoteQueriesEvents() {
|
||||
const queryAddedSubscription = this.remoteQueriesManager.onRemoteQueryAdded(
|
||||
async (event) => {
|
||||
this.addQuery({
|
||||
t: "remote",
|
||||
status: QueryStatus.InProgress,
|
||||
completed: false,
|
||||
queryId: event.queryId,
|
||||
remoteQuery: event.query,
|
||||
});
|
||||
|
||||
await this.refreshTreeView();
|
||||
},
|
||||
);
|
||||
|
||||
const queryRemovedSubscription =
|
||||
this.remoteQueriesManager.onRemoteQueryRemoved(async (event) => {
|
||||
const item = this.treeDataProvider.allHistory.find(
|
||||
(i) => i.t === "remote" && i.queryId === event.queryId,
|
||||
);
|
||||
if (item) {
|
||||
await this.removeRemoteQuery(item as RemoteQueryHistoryItem);
|
||||
}
|
||||
});
|
||||
|
||||
const queryStatusUpdateSubscription =
|
||||
this.remoteQueriesManager.onRemoteQueryStatusUpdate(async (event) => {
|
||||
const item = this.treeDataProvider.allHistory.find(
|
||||
(i) => i.t === "remote" && i.queryId === event.queryId,
|
||||
);
|
||||
if (item) {
|
||||
const remoteQueryHistoryItem = item as RemoteQueryHistoryItem;
|
||||
remoteQueryHistoryItem.status = event.status;
|
||||
remoteQueryHistoryItem.failureReason = event.failureReason;
|
||||
remoteQueryHistoryItem.resultCount = event.resultCount;
|
||||
if (event.status === QueryStatus.Completed) {
|
||||
remoteQueryHistoryItem.completed = true;
|
||||
}
|
||||
await this.refreshTreeView();
|
||||
} else {
|
||||
void extLogger.log(
|
||||
"Variant analysis status update event received for unknown variant analysis",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
this.push(queryAddedSubscription);
|
||||
this.push(queryRemovedSubscription);
|
||||
this.push(queryStatusUpdateSubscription);
|
||||
}
|
||||
|
||||
async readQueryHistory(): Promise<void> {
|
||||
void extLogger.log(
|
||||
`Reading cached query history from '${this.queryMetadataStorageLocation}'.`,
|
||||
@@ -538,9 +484,6 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
this.treeDataProvider.allHistory = history;
|
||||
await Promise.all(
|
||||
this.treeDataProvider.allHistory.map(async (item) => {
|
||||
if (item.t === "remote") {
|
||||
await this.remoteQueriesManager.rehydrateRemoteQuery(item.queryId);
|
||||
}
|
||||
if (item.t === "variant-analysis") {
|
||||
await this.variantAnalysisManager.rehydrateVariantAnalysis(
|
||||
item.variantAnalysis,
|
||||
@@ -653,7 +596,7 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
await item.completedQuery?.query.deleteQuery();
|
||||
}
|
||||
} else if (item.t === "remote") {
|
||||
await this.removeRemoteQuery(item);
|
||||
// Do nothing. TODO: Remove once remote queries are no longer supported.
|
||||
} else if (item.t === "variant-analysis") {
|
||||
await this.removeVariantAnalysis(item);
|
||||
} else {
|
||||
@@ -670,20 +613,6 @@ export class QueryHistoryManager extends DisposableObject {
|
||||
}
|
||||
}
|
||||
|
||||
private async removeRemoteQuery(item: RemoteQueryHistoryItem): Promise<void> {
|
||||
// Remote queries can be removed locally, but not remotely.
|
||||
// The user must cancel the query on GitHub Actions explicitly.
|
||||
this.treeDataProvider.remove(item);
|
||||
void extLogger.log(`Deleted ${this.labelProvider.getLabel(item)}.`);
|
||||
if (item.status === QueryStatus.InProgress) {
|
||||
void extLogger.log(
|
||||
"The variant analysis is still running on GitHub Actions. To cancel there, you must go to the workflow run in your browser.",
|
||||
);
|
||||
}
|
||||
|
||||
await this.remoteQueriesManager.removeRemoteQuery(item.queryId);
|
||||
}
|
||||
|
||||
private async removeVariantAnalysis(
|
||||
item: VariantAnalysisHistoryItem,
|
||||
): Promise<void> {
|
||||
@@ -1547,9 +1476,11 @@ the file in the file explorer and dragging it into the workspace.`,
|
||||
false,
|
||||
);
|
||||
} else if (item.t === "remote") {
|
||||
await this.remoteQueriesManager.openRemoteQueryResults(item.queryId);
|
||||
// Do nothing. TODO: Remove when remote queries is no longer supported.
|
||||
} else if (item.t === "variant-analysis") {
|
||||
await this.variantAnalysisManager.showView(item.variantAnalysis.id);
|
||||
} else {
|
||||
assertNever(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,259 +0,0 @@
|
||||
import { pathExists } from "fs-extra";
|
||||
import { EOL } from "os";
|
||||
import { extname } from "path";
|
||||
import { CancellationToken } from "vscode";
|
||||
|
||||
import { Logger } from "../common";
|
||||
import { downloadArtifactFromLink } from "./gh-api/gh-actions-api-client";
|
||||
import { AnalysisSummary } from "./shared/remote-query-result";
|
||||
import {
|
||||
AnalysisResults,
|
||||
AnalysisAlert,
|
||||
AnalysisRawResults,
|
||||
} from "./shared/analysis-result";
|
||||
import { UserCancellationException } from "../commandRunner";
|
||||
import { sarifParser } from "../sarif-parser";
|
||||
import { extractAnalysisAlerts } from "./sarif-processing";
|
||||
import { CodeQLCliServer } from "../cli";
|
||||
import { extractRawResults } from "./bqrs-processing";
|
||||
import { asyncFilter, getErrorMessage } from "../pure/helpers-pure";
|
||||
import { createDownloadPath } from "./download-link";
|
||||
import { App } from "../common/app";
|
||||
|
||||
export class AnalysesResultsManager {
|
||||
// Store for the results of various analyses for each remote query.
|
||||
// The key is the queryId and is also the name of the directory where results are stored.
|
||||
private readonly analysesResults: Map<string, AnalysisResults[]>;
|
||||
|
||||
constructor(
|
||||
private readonly app: App,
|
||||
private readonly cliServer: CodeQLCliServer,
|
||||
readonly storagePath: string,
|
||||
private readonly logger: Logger,
|
||||
) {
|
||||
this.analysesResults = new Map();
|
||||
}
|
||||
|
||||
public async downloadAnalysisResults(
|
||||
analysisSummary: AnalysisSummary,
|
||||
publishResults: (analysesResults: AnalysisResults[]) => Promise<void>,
|
||||
): Promise<void> {
|
||||
if (this.isAnalysisInMemory(analysisSummary)) {
|
||||
// We already have the results for this analysis in memory, don't download again.
|
||||
return;
|
||||
}
|
||||
|
||||
void this.logger.log(
|
||||
`Downloading and processing results for ${analysisSummary.nwo}`,
|
||||
);
|
||||
|
||||
await this.downloadSingleAnalysisResults(analysisSummary, publishResults);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the array analysis results. For each analysis results, if it is not downloaded yet,
|
||||
* it will be downloaded. If it is already downloaded, it will be loaded into memory.
|
||||
* If it is already in memory, this will be a no-op.
|
||||
*
|
||||
* @param allAnalysesToLoad List of analyses to ensure are downloaded and in memory
|
||||
* @param token Optional cancellation token
|
||||
* @param publishResults Optional function to publish the results after loading
|
||||
*/
|
||||
public async loadAnalysesResults(
|
||||
allAnalysesToLoad: AnalysisSummary[],
|
||||
token?: CancellationToken,
|
||||
publishResults: (
|
||||
analysesResults: AnalysisResults[],
|
||||
) => Promise<void> = () => Promise.resolve(),
|
||||
): Promise<void> {
|
||||
// Filter out analyses that we have already in memory.
|
||||
const analysesToDownload = allAnalysesToLoad.filter(
|
||||
(x) => !this.isAnalysisInMemory(x),
|
||||
);
|
||||
|
||||
void this.logger.log("Downloading and processing analyses results");
|
||||
|
||||
const batchSize = 3;
|
||||
const numOfBatches = Math.ceil(analysesToDownload.length / batchSize);
|
||||
const allFailures = [];
|
||||
|
||||
for (let i = 0; i < analysesToDownload.length; i += batchSize) {
|
||||
if (token?.isCancellationRequested) {
|
||||
throw new UserCancellationException(
|
||||
"Downloading of analyses results has been cancelled",
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
const batch = analysesToDownload.slice(i, i + batchSize);
|
||||
const batchTasks = batch.map((analysis) =>
|
||||
this.downloadSingleAnalysisResults(analysis, publishResults),
|
||||
);
|
||||
|
||||
const nwos = batch.map((a) => a.nwo).join(", ");
|
||||
void this.logger.log(
|
||||
`Downloading batch ${
|
||||
Math.floor(i / batchSize) + 1
|
||||
} of ${numOfBatches} (${nwos})`,
|
||||
);
|
||||
|
||||
const taskResults = await Promise.allSettled(batchTasks);
|
||||
const failedTasks = taskResults.filter(
|
||||
(x) => x.status === "rejected",
|
||||
) as PromiseRejectedResult[];
|
||||
if (failedTasks.length > 0) {
|
||||
const failures = failedTasks.map((t) => t.reason.message);
|
||||
failures.forEach((f) => void this.logger.log(f));
|
||||
allFailures.push(...failures);
|
||||
}
|
||||
}
|
||||
|
||||
if (allFailures.length > 0) {
|
||||
throw Error(allFailures.join(EOL));
|
||||
}
|
||||
}
|
||||
|
||||
public getAnalysesResults(queryId: string): AnalysisResults[] {
|
||||
return [...this.internalGetAnalysesResults(queryId)];
|
||||
}
|
||||
|
||||
private internalGetAnalysesResults(queryId: string): AnalysisResults[] {
|
||||
return this.analysesResults.get(queryId) || [];
|
||||
}
|
||||
|
||||
public removeAnalysesResults(queryId: string) {
|
||||
this.analysesResults.delete(queryId);
|
||||
}
|
||||
|
||||
private async downloadSingleAnalysisResults(
|
||||
analysis: AnalysisSummary,
|
||||
publishResults: (analysesResults: AnalysisResults[]) => Promise<void>,
|
||||
): Promise<void> {
|
||||
const analysisResults: AnalysisResults = {
|
||||
nwo: analysis.nwo,
|
||||
status: "InProgress",
|
||||
interpretedResults: [],
|
||||
resultCount: analysis.resultCount,
|
||||
starCount: analysis.starCount,
|
||||
lastUpdated: analysis.lastUpdated,
|
||||
};
|
||||
const queryId = analysis.downloadLink.queryId;
|
||||
const resultsForQuery = this.internalGetAnalysesResults(queryId);
|
||||
resultsForQuery.push(analysisResults);
|
||||
this.analysesResults.set(queryId, resultsForQuery);
|
||||
void publishResults([...resultsForQuery]);
|
||||
const pos = resultsForQuery.length - 1;
|
||||
|
||||
let artifactPath;
|
||||
try {
|
||||
artifactPath = await downloadArtifactFromLink(
|
||||
this.app.credentials,
|
||||
this.storagePath,
|
||||
analysis.downloadLink,
|
||||
);
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`Could not download the analysis results for ${
|
||||
analysis.nwo
|
||||
}: ${getErrorMessage(e)}`,
|
||||
);
|
||||
}
|
||||
|
||||
const fileLinkPrefix = this.createGitHubDotcomFileLinkPrefix(
|
||||
analysis.nwo,
|
||||
analysis.databaseSha,
|
||||
);
|
||||
|
||||
let newAnaysisResults: AnalysisResults;
|
||||
const fileExtension = extname(artifactPath);
|
||||
if (fileExtension === ".sarif") {
|
||||
const queryResults = await this.readSarifResults(
|
||||
artifactPath,
|
||||
fileLinkPrefix,
|
||||
);
|
||||
newAnaysisResults = {
|
||||
...analysisResults,
|
||||
interpretedResults: queryResults,
|
||||
status: "Completed",
|
||||
};
|
||||
} else if (fileExtension === ".bqrs") {
|
||||
const queryResults = await this.readBqrsResults(
|
||||
artifactPath,
|
||||
fileLinkPrefix,
|
||||
analysis.sourceLocationPrefix,
|
||||
);
|
||||
newAnaysisResults = {
|
||||
...analysisResults,
|
||||
rawResults: queryResults,
|
||||
status: "Completed",
|
||||
};
|
||||
} else {
|
||||
void this.logger.log(
|
||||
`Cannot download results. File type '${fileExtension}' not supported.`,
|
||||
);
|
||||
newAnaysisResults = {
|
||||
...analysisResults,
|
||||
status: "Failed",
|
||||
};
|
||||
}
|
||||
resultsForQuery[pos] = newAnaysisResults;
|
||||
void publishResults([...resultsForQuery]);
|
||||
}
|
||||
|
||||
public async loadDownloadedAnalyses(allAnalysesToCheck: AnalysisSummary[]) {
|
||||
// Find all analyses that are already downloaded.
|
||||
const allDownloadedAnalyses = await asyncFilter(allAnalysesToCheck, (x) =>
|
||||
this.isAnalysisDownloaded(x),
|
||||
);
|
||||
// Now, ensure that all of these analyses are in memory. Some may already be in memory. These are ignored.
|
||||
await this.loadAnalysesResults(allDownloadedAnalyses);
|
||||
}
|
||||
|
||||
private async isAnalysisDownloaded(
|
||||
analysis: AnalysisSummary,
|
||||
): Promise<boolean> {
|
||||
return await pathExists(
|
||||
createDownloadPath(this.storagePath, analysis.downloadLink),
|
||||
);
|
||||
}
|
||||
|
||||
private async readBqrsResults(
|
||||
filePath: string,
|
||||
fileLinkPrefix: string,
|
||||
sourceLocationPrefix: string,
|
||||
): Promise<AnalysisRawResults> {
|
||||
return await extractRawResults(
|
||||
this.cliServer,
|
||||
this.logger,
|
||||
filePath,
|
||||
fileLinkPrefix,
|
||||
sourceLocationPrefix,
|
||||
);
|
||||
}
|
||||
|
||||
private async readSarifResults(
|
||||
filePath: string,
|
||||
fileLinkPrefix: string,
|
||||
): Promise<AnalysisAlert[]> {
|
||||
const sarifLog = await sarifParser(filePath);
|
||||
|
||||
const processedSarif = extractAnalysisAlerts(sarifLog, fileLinkPrefix);
|
||||
if (processedSarif.errors.length) {
|
||||
void this.logger.log(
|
||||
`Error processing SARIF file: ${EOL}${processedSarif.errors.join(EOL)}`,
|
||||
);
|
||||
}
|
||||
|
||||
return processedSarif.alerts;
|
||||
}
|
||||
|
||||
private isAnalysisInMemory(analysis: AnalysisSummary): boolean {
|
||||
return this.internalGetAnalysesResults(analysis.downloadLink.queryId).some(
|
||||
(x) => x.nwo === analysis.nwo,
|
||||
);
|
||||
}
|
||||
|
||||
private createGitHubDotcomFileLinkPrefix(nwo: string, sha: string): string {
|
||||
return `https://github.com/${nwo}/blob/${sha}`;
|
||||
}
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
import { EventEmitter, ExtensionContext } from "vscode";
|
||||
import { join } from "path";
|
||||
import { pathExists, readFile, remove } from "fs-extra";
|
||||
|
||||
import { CodeQLCliServer } from "../cli";
|
||||
import { showAndLogExceptionWithTelemetry } from "../helpers";
|
||||
import { Logger } from "../common";
|
||||
import { RemoteQueriesView } from "./remote-queries-view";
|
||||
import { RemoteQuery } from "./remote-query";
|
||||
import { RemoteQueryResult } from "./remote-query-result";
|
||||
import { AnalysesResultsManager } from "./analyses-results-manager";
|
||||
import { asError, getErrorMessage } from "../pure/helpers-pure";
|
||||
import { QueryStatus } from "../query-status";
|
||||
import { DisposableObject } from "../pure/disposable-object";
|
||||
import { AnalysisResults } from "./shared/analysis-result";
|
||||
import { App } from "../common/app";
|
||||
import { redactableError } from "../pure/errors";
|
||||
|
||||
const noop = () => {
|
||||
/* do nothing */
|
||||
};
|
||||
|
||||
export interface NewQueryEvent {
|
||||
queryId: string;
|
||||
query: RemoteQuery;
|
||||
}
|
||||
|
||||
export interface RemovedQueryEvent {
|
||||
queryId: string;
|
||||
}
|
||||
|
||||
export interface UpdatedQueryStatusEvent {
|
||||
queryId: string;
|
||||
status: QueryStatus;
|
||||
failureReason?: string;
|
||||
repositoryCount?: number;
|
||||
resultCount?: number;
|
||||
}
|
||||
|
||||
export class RemoteQueriesManager extends DisposableObject {
|
||||
public readonly onRemoteQueryAdded;
|
||||
public readonly onRemoteQueryRemoved;
|
||||
public readonly onRemoteQueryStatusUpdate;
|
||||
|
||||
private readonly remoteQueryAddedEventEmitter;
|
||||
private readonly remoteQueryRemovedEventEmitter;
|
||||
private readonly remoteQueryStatusUpdateEventEmitter;
|
||||
|
||||
private readonly analysesResultsManager: AnalysesResultsManager;
|
||||
private readonly view: RemoteQueriesView;
|
||||
|
||||
constructor(
|
||||
ctx: ExtensionContext,
|
||||
app: App,
|
||||
cliServer: CodeQLCliServer,
|
||||
private readonly storagePath: string,
|
||||
logger: Logger,
|
||||
) {
|
||||
super();
|
||||
this.analysesResultsManager = new AnalysesResultsManager(
|
||||
app,
|
||||
cliServer,
|
||||
storagePath,
|
||||
logger,
|
||||
);
|
||||
this.view = new RemoteQueriesView(ctx, logger, this.analysesResultsManager);
|
||||
|
||||
this.remoteQueryAddedEventEmitter = this.push(
|
||||
new EventEmitter<NewQueryEvent>(),
|
||||
);
|
||||
this.remoteQueryRemovedEventEmitter = this.push(
|
||||
new EventEmitter<RemovedQueryEvent>(),
|
||||
);
|
||||
this.remoteQueryStatusUpdateEventEmitter = this.push(
|
||||
new EventEmitter<UpdatedQueryStatusEvent>(),
|
||||
);
|
||||
this.onRemoteQueryAdded = this.remoteQueryAddedEventEmitter.event;
|
||||
this.onRemoteQueryRemoved = this.remoteQueryRemovedEventEmitter.event;
|
||||
this.onRemoteQueryStatusUpdate =
|
||||
this.remoteQueryStatusUpdateEventEmitter.event;
|
||||
|
||||
this.push(this.view);
|
||||
}
|
||||
|
||||
public async rehydrateRemoteQuery(queryId: string) {
|
||||
if (!(await this.queryRecordExists(queryId))) {
|
||||
// In this case, the query was deleted from disk, most likely because it was purged
|
||||
// by another workspace.
|
||||
this.remoteQueryRemovedEventEmitter.fire({ queryId });
|
||||
}
|
||||
}
|
||||
|
||||
public async removeRemoteQuery(queryId: string) {
|
||||
this.analysesResultsManager.removeAnalysesResults(queryId);
|
||||
await this.removeStorageDirectory(queryId);
|
||||
}
|
||||
|
||||
public async openRemoteQueryResults(queryId: string) {
|
||||
try {
|
||||
const remoteQuery = (await this.retrieveJsonFile(
|
||||
queryId,
|
||||
"query.json",
|
||||
)) as RemoteQuery;
|
||||
const remoteQueryResult = (await this.retrieveJsonFile(
|
||||
queryId,
|
||||
"query-result.json",
|
||||
)) as RemoteQueryResult;
|
||||
|
||||
// Open results in the background
|
||||
void this.openResults(remoteQuery, remoteQueryResult).then(
|
||||
noop,
|
||||
(e: unknown) =>
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
redactableError(
|
||||
asError(e),
|
||||
)`Could not open query results. ${getErrorMessage(e)}`,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
redactableError(
|
||||
asError(e),
|
||||
)`Could not open query results. ${getErrorMessage(e)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public async openResults(query: RemoteQuery, queryResult: RemoteQueryResult) {
|
||||
await this.view.showResults(query, queryResult);
|
||||
}
|
||||
|
||||
private async retrieveJsonFile<T>(
|
||||
queryId: string,
|
||||
fileName: string,
|
||||
): Promise<T> {
|
||||
const filePath = join(this.storagePath, queryId, fileName);
|
||||
return JSON.parse(await readFile(filePath, "utf8"));
|
||||
}
|
||||
|
||||
private async removeStorageDirectory(queryId: string): Promise<void> {
|
||||
const filePath = join(this.storagePath, queryId);
|
||||
await remove(filePath);
|
||||
}
|
||||
|
||||
private async queryRecordExists(queryId: string): Promise<boolean> {
|
||||
const filePath = join(this.storagePath, queryId);
|
||||
return await pathExists(filePath);
|
||||
}
|
||||
|
||||
// Pulled from the analysis results manager, so that we can get access to
|
||||
// analyses results from the "export results" command.
|
||||
public getAnalysesResults(queryId: string): AnalysisResults[] {
|
||||
return [...this.analysesResultsManager.getAnalysesResults(queryId)];
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,15 @@
|
||||
import {
|
||||
ExtensionContext,
|
||||
window as Window,
|
||||
ViewColumn,
|
||||
Uri,
|
||||
ViewColumn,
|
||||
window as Window,
|
||||
workspace,
|
||||
} from "vscode";
|
||||
import { basename } from "path";
|
||||
|
||||
import {
|
||||
ToRemoteQueriesMessage,
|
||||
FromRemoteQueriesMessage,
|
||||
RemoteQueryDownloadAnalysisResultsMessage,
|
||||
RemoteQueryDownloadAllAnalysesResultsMessage,
|
||||
ToRemoteQueriesMessage,
|
||||
} from "../pure/interface-types";
|
||||
import { Logger } from "../common";
|
||||
import { assertNever } from "../pure/helpers-pure";
|
||||
@@ -28,8 +26,6 @@ import {
|
||||
import { showAndLogWarningMessage } from "../helpers";
|
||||
import { URLSearchParams } from "url";
|
||||
import { SHOW_QUERY_TEXT_MSG } from "../query-history/query-history-manager";
|
||||
import { AnalysesResultsManager } from "./analyses-results-manager";
|
||||
import { AnalysisResults } from "./shared/analysis-result";
|
||||
import { humanizeUnit } from "../pure/time";
|
||||
import { AbstractWebview, WebviewPanelConfig } from "../abstract-webview";
|
||||
import { telemetryListener } from "../telemetry";
|
||||
@@ -38,13 +34,7 @@ export class RemoteQueriesView extends AbstractWebview<
|
||||
ToRemoteQueriesMessage,
|
||||
FromRemoteQueriesMessage
|
||||
> {
|
||||
private currentQueryId: string | undefined;
|
||||
|
||||
constructor(
|
||||
ctx: ExtensionContext,
|
||||
private readonly logger: Logger,
|
||||
private readonly analysesResultsManager: AnalysesResultsManager,
|
||||
) {
|
||||
constructor(ctx: ExtensionContext, private readonly logger: Logger) {
|
||||
super(ctx);
|
||||
this.panelLoadedCallBacks.push(() => {
|
||||
void logger.log("Variant analysis results view loaded");
|
||||
@@ -57,22 +47,11 @@ export class RemoteQueriesView extends AbstractWebview<
|
||||
|
||||
await this.waitForPanelLoaded();
|
||||
const model = this.buildViewModel(query, queryResult);
|
||||
this.currentQueryId = queryResult.queryId;
|
||||
|
||||
await this.postMessage({
|
||||
t: "setRemoteQueryResult",
|
||||
queryResult: model,
|
||||
});
|
||||
|
||||
// Ensure all pre-downloaded artifacts are loaded into memory
|
||||
await this.analysesResultsManager.loadDownloadedAnalyses(
|
||||
model.analysisSummaries,
|
||||
);
|
||||
|
||||
await this.setAnalysisResults(
|
||||
this.analysesResultsManager.getAnalysesResults(queryResult.queryId),
|
||||
queryResult.queryId,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,13 +108,13 @@ export class RemoteQueriesView extends AbstractWebview<
|
||||
preserveFocus: true,
|
||||
view: "remote-queries",
|
||||
additionalOptions: {
|
||||
localResourceRoots: [Uri.file(this.analysesResultsManager.storagePath)],
|
||||
localResourceRoots: [],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected onPanelDispose(): void {
|
||||
this.currentQueryId = undefined;
|
||||
// No-op
|
||||
}
|
||||
|
||||
protected async onMessage(msg: FromRemoteQueriesMessage): Promise<void> {
|
||||
@@ -155,10 +134,8 @@ export class RemoteQueriesView extends AbstractWebview<
|
||||
case "copyRepoList":
|
||||
break;
|
||||
case "remoteQueryDownloadAnalysisResults":
|
||||
await this.downloadAnalysisResults(msg);
|
||||
break;
|
||||
case "remoteQueryDownloadAllAnalysesResults":
|
||||
await this.downloadAllAnalysesResults(msg);
|
||||
break;
|
||||
case "remoteQueryExportResults":
|
||||
break;
|
||||
@@ -195,39 +172,6 @@ export class RemoteQueriesView extends AbstractWebview<
|
||||
}
|
||||
}
|
||||
|
||||
private async downloadAnalysisResults(
|
||||
msg: RemoteQueryDownloadAnalysisResultsMessage,
|
||||
): Promise<void> {
|
||||
const queryId = this.currentQueryId;
|
||||
await this.analysesResultsManager.downloadAnalysisResults(
|
||||
msg.analysisSummary,
|
||||
(results) => this.setAnalysisResults(results, queryId),
|
||||
);
|
||||
}
|
||||
|
||||
private async downloadAllAnalysesResults(
|
||||
msg: RemoteQueryDownloadAllAnalysesResultsMessage,
|
||||
): Promise<void> {
|
||||
const queryId = this.currentQueryId;
|
||||
await this.analysesResultsManager.loadAnalysesResults(
|
||||
msg.analysisSummaries,
|
||||
undefined,
|
||||
(results) => this.setAnalysisResults(results, queryId),
|
||||
);
|
||||
}
|
||||
|
||||
public async setAnalysisResults(
|
||||
analysesResults: AnalysisResults[],
|
||||
queryId: string | undefined,
|
||||
): Promise<void> {
|
||||
if (this.panel?.active && this.currentQueryId === queryId) {
|
||||
await this.postMessage({
|
||||
t: "setAnalysesResults",
|
||||
analysesResults,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private getDuration(startTime: number, endTime: number): string {
|
||||
const diffInMs = startTime - endTime;
|
||||
return humanizeUnit(diffInMs);
|
||||
|
||||
@@ -10,7 +10,6 @@ import { LocalQueryInfo } from "../../../../src/query-results";
|
||||
import { DatabaseManager } from "../../../../src/databases";
|
||||
import { tmpDir } from "../../../../src/helpers";
|
||||
import { HistoryItemLabelProvider } from "../../../../src/query-history/history-item-label-provider";
|
||||
import { RemoteQueriesManager } from "../../../../src/remote-queries/remote-queries-manager";
|
||||
import { ResultsView } from "../../../../src/interface";
|
||||
import { EvalLogViewer } from "../../../../src/eval-log-viewer";
|
||||
import { QueryRunner } from "../../../../src/queryRunner";
|
||||
@@ -43,7 +42,6 @@ describe("HistoryTreeDataProvider", () => {
|
||||
let queryHistoryManager: QueryHistoryManager;
|
||||
|
||||
let localQueriesResultsViewStub: ResultsView;
|
||||
let remoteQueriesManagerStub: RemoteQueriesManager;
|
||||
let variantAnalysisManagerStub: VariantAnalysisManager;
|
||||
|
||||
let allHistory: QueryHistoryInfo[];
|
||||
@@ -61,13 +59,6 @@ describe("HistoryTreeDataProvider", () => {
|
||||
localQueriesResultsViewStub = {
|
||||
showResults: jest.fn(),
|
||||
} as any as ResultsView;
|
||||
remoteQueriesManagerStub = {
|
||||
onRemoteQueryAdded: jest.fn(),
|
||||
onRemoteQueryRemoved: jest.fn(),
|
||||
onRemoteQueryStatusUpdate: jest.fn(),
|
||||
removeRemoteQuery: jest.fn(),
|
||||
openRemoteQueryResults: jest.fn(),
|
||||
} as any as RemoteQueriesManager;
|
||||
|
||||
variantAnalysisManagerStub = {
|
||||
onVariantAnalysisAdded: jest.fn(),
|
||||
@@ -481,7 +472,6 @@ describe("HistoryTreeDataProvider", () => {
|
||||
{} as QueryRunner,
|
||||
{} as DatabaseManager,
|
||||
localQueriesResultsViewStub,
|
||||
remoteQueriesManagerStub,
|
||||
variantAnalysisManagerStub,
|
||||
{} as EvalLogViewer,
|
||||
"xxx",
|
||||
|
||||
@@ -11,7 +11,6 @@ import { LocalQueryInfo } from "../../../../src/query-results";
|
||||
import { DatabaseManager } from "../../../../src/databases";
|
||||
import { tmpDir } from "../../../../src/helpers";
|
||||
import { HistoryItemLabelProvider } from "../../../../src/query-history/history-item-label-provider";
|
||||
import { RemoteQueriesManager } from "../../../../src/remote-queries/remote-queries-manager";
|
||||
import { ResultsView } from "../../../../src/interface";
|
||||
import { EvalLogViewer } from "../../../../src/eval-log-viewer";
|
||||
import { QueryRunner } from "../../../../src/queryRunner";
|
||||
@@ -54,7 +53,6 @@ describe("QueryHistoryManager", () => {
|
||||
let queryHistoryManager: QueryHistoryManager;
|
||||
|
||||
let localQueriesResultsViewStub: ResultsView;
|
||||
let remoteQueriesManagerStub: RemoteQueriesManager;
|
||||
let variantAnalysisManagerStub: VariantAnalysisManager;
|
||||
|
||||
let tryOpenExternalFile: Function;
|
||||
@@ -86,13 +84,6 @@ describe("QueryHistoryManager", () => {
|
||||
localQueriesResultsViewStub = {
|
||||
showResults: jest.fn(),
|
||||
} as any as ResultsView;
|
||||
remoteQueriesManagerStub = {
|
||||
onRemoteQueryAdded: jest.fn(),
|
||||
onRemoteQueryRemoved: jest.fn(),
|
||||
onRemoteQueryStatusUpdate: jest.fn(),
|
||||
removeRemoteQuery: jest.fn(),
|
||||
openRemoteQueryResults: jest.fn(),
|
||||
} as any as RemoteQueriesManager;
|
||||
|
||||
variantAnalysisManagerStub = {
|
||||
onVariantAnalysisAdded: jest.fn(),
|
||||
@@ -256,42 +247,6 @@ describe("QueryHistoryManager", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("remote query", () => {
|
||||
describe("when complete", () => {
|
||||
it("should show results", async () => {
|
||||
queryHistoryManager = await createMockQueryHistory(allHistory);
|
||||
const itemClicked = remoteQueryHistory[0];
|
||||
await queryHistoryManager.handleItemClicked(itemClicked, [
|
||||
itemClicked,
|
||||
]);
|
||||
|
||||
expect(
|
||||
remoteQueriesManagerStub.openRemoteQueryResults,
|
||||
).toHaveBeenCalledTimes(1);
|
||||
expect(
|
||||
remoteQueriesManagerStub.openRemoteQueryResults,
|
||||
).toHaveBeenCalledWith(itemClicked.queryId);
|
||||
expect(queryHistoryManager.treeDataProvider.getCurrent()).toBe(
|
||||
itemClicked,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when incomplete", () => {
|
||||
it("should do nothing", async () => {
|
||||
queryHistoryManager = await createMockQueryHistory(allHistory);
|
||||
const itemClicked = remoteQueryHistory[2];
|
||||
await queryHistoryManager.handleItemClicked(itemClicked, [
|
||||
itemClicked,
|
||||
]);
|
||||
|
||||
expect(
|
||||
remoteQueriesManagerStub.openRemoteQueryResults,
|
||||
).not.toBeCalledWith(itemClicked.queryId);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("variant analysis", () => {
|
||||
describe("when complete", () => {
|
||||
it("should show results", async () => {
|
||||
@@ -347,9 +302,6 @@ describe("QueryHistoryManager", () => {
|
||||
]);
|
||||
|
||||
expect(localQueriesResultsViewStub.showResults).not.toHaveBeenCalled();
|
||||
expect(
|
||||
remoteQueriesManagerStub.openRemoteQueryResults,
|
||||
).not.toHaveBeenCalled();
|
||||
expect(variantAnalysisManagerStub.showView).not.toBeCalled();
|
||||
expect(
|
||||
queryHistoryManager.treeDataProvider.getCurrent(),
|
||||
@@ -364,9 +316,6 @@ describe("QueryHistoryManager", () => {
|
||||
await queryHistoryManager.handleItemClicked(undefined!, []);
|
||||
|
||||
expect(localQueriesResultsViewStub.showResults).not.toHaveBeenCalled();
|
||||
expect(
|
||||
remoteQueriesManagerStub.openRemoteQueryResults,
|
||||
).not.toHaveBeenCalled();
|
||||
expect(variantAnalysisManagerStub.showView).not.toHaveBeenCalled();
|
||||
expect(
|
||||
queryHistoryManager.treeDataProvider.getCurrent(),
|
||||
@@ -476,102 +425,6 @@ describe("QueryHistoryManager", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the item is a remote query", () => {
|
||||
describe("when the item being removed is not selected", () => {
|
||||
let toDelete: RemoteQueryHistoryItem;
|
||||
let selected: RemoteQueryHistoryItem;
|
||||
|
||||
beforeEach(async () => {
|
||||
// deleting the first item when a different item is selected
|
||||
// will not change the selection
|
||||
toDelete = remoteQueryHistory[1];
|
||||
selected = remoteQueryHistory[3];
|
||||
|
||||
queryHistoryManager = await createMockQueryHistory(allHistory);
|
||||
|
||||
// initialize the selection
|
||||
await queryHistoryManager.treeView.reveal(remoteQueryHistory[0], {
|
||||
select: true,
|
||||
});
|
||||
|
||||
// select the item we want
|
||||
await queryHistoryManager.treeView.reveal(selected, {
|
||||
select: true,
|
||||
});
|
||||
|
||||
// should be selected
|
||||
expect(queryHistoryManager.treeDataProvider.getCurrent()).toEqual(
|
||||
selected,
|
||||
);
|
||||
|
||||
// remove an item
|
||||
await queryHistoryManager.handleRemoveHistoryItem(toDelete, [
|
||||
toDelete,
|
||||
]);
|
||||
});
|
||||
|
||||
it("should remove the item", () => {
|
||||
expect(
|
||||
remoteQueriesManagerStub.removeRemoteQuery,
|
||||
).toHaveBeenCalledWith(toDelete.queryId);
|
||||
expect(queryHistoryManager.treeDataProvider.allHistory).not.toContain(
|
||||
toDelete,
|
||||
);
|
||||
});
|
||||
|
||||
it("should not change the selection", () => {
|
||||
expect(queryHistoryManager.treeDataProvider.getCurrent()).toEqual(
|
||||
selected,
|
||||
);
|
||||
|
||||
expect(
|
||||
remoteQueriesManagerStub.openRemoteQueryResults,
|
||||
).toHaveBeenCalledWith(selected.queryId);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the item being removed is selected", () => {
|
||||
let toDelete: RemoteQueryHistoryItem;
|
||||
let newSelected: RemoteQueryHistoryItem;
|
||||
|
||||
beforeEach(async () => {
|
||||
// deleting the selected item automatically selects next item
|
||||
toDelete = remoteQueryHistory[1];
|
||||
newSelected = remoteQueryHistory[2];
|
||||
|
||||
queryHistoryManager = await createMockQueryHistory(
|
||||
remoteQueryHistory,
|
||||
);
|
||||
|
||||
// select the item we want
|
||||
await queryHistoryManager.treeView.reveal(toDelete, {
|
||||
select: true,
|
||||
});
|
||||
await queryHistoryManager.handleRemoveHistoryItem(toDelete, [
|
||||
toDelete,
|
||||
]);
|
||||
});
|
||||
|
||||
it("should remove the item", () => {
|
||||
expect(
|
||||
remoteQueriesManagerStub.removeRemoteQuery,
|
||||
).toHaveBeenCalledWith(toDelete.queryId);
|
||||
expect(queryHistoryManager.treeDataProvider.allHistory).not.toContain(
|
||||
toDelete,
|
||||
);
|
||||
});
|
||||
|
||||
it.skip("should change the selection", () => {
|
||||
expect(queryHistoryManager.treeDataProvider.getCurrent()).toEqual(
|
||||
newSelected,
|
||||
);
|
||||
expect(
|
||||
remoteQueriesManagerStub.openRemoteQueryResults,
|
||||
).toHaveBeenCalledWith(newSelected.queryId);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the item is a variant analysis", () => {
|
||||
let showBinaryChoiceDialogSpy: jest.SpiedFunction<
|
||||
typeof helpers.showBinaryChoiceDialog
|
||||
@@ -1428,7 +1281,6 @@ describe("QueryHistoryManager", () => {
|
||||
{} as QueryRunner,
|
||||
{} as DatabaseManager,
|
||||
localQueriesResultsViewStub,
|
||||
remoteQueriesManagerStub,
|
||||
variantAnalysisManagerStub,
|
||||
{} as EvalLogViewer,
|
||||
"xxx",
|
||||
|
||||
@@ -15,7 +15,6 @@ import { tmpDir, walkDirectory } from "../../../../src/helpers";
|
||||
import { DisposableBucket } from "../../disposable-bucket";
|
||||
import { testDisposeHandler } from "../../test-dispose-handler";
|
||||
import { HistoryItemLabelProvider } from "../../../../src/query-history/history-item-label-provider";
|
||||
import { RemoteQueriesManager } from "../../../../src/remote-queries/remote-queries-manager";
|
||||
import { ResultsView } from "../../../../src/interface";
|
||||
import { EvalLogViewer } from "../../../../src/eval-log-viewer";
|
||||
import { QueryRunner } from "../../../../src/queryRunner";
|
||||
@@ -50,14 +49,6 @@ describe("Variant Analyses and QueryHistoryManager", () => {
|
||||
const localQueriesResultsViewStub = {
|
||||
showResults: jest.fn(),
|
||||
} as any as ResultsView;
|
||||
const remoteQueriesManagerStub = {
|
||||
onRemoteQueryAdded: jest.fn(),
|
||||
onRemoteQueryRemoved: jest.fn(),
|
||||
onRemoteQueryStatusUpdate: jest.fn(),
|
||||
rehydrateRemoteQuery: jest.fn(),
|
||||
removeRemoteQuery: jest.fn(),
|
||||
openRemoteQueryResults: jest.fn(),
|
||||
} as any as RemoteQueriesManager;
|
||||
const variantAnalysisManagerStub = {
|
||||
onVariantAnalysisAdded: jest.fn(),
|
||||
onVariantAnalysisRemoved: jest.fn(),
|
||||
@@ -87,7 +78,6 @@ describe("Variant Analyses and QueryHistoryManager", () => {
|
||||
{} as QueryRunner,
|
||||
{} as DatabaseManager,
|
||||
localQueriesResultsViewStub,
|
||||
remoteQueriesManagerStub,
|
||||
variantAnalysisManagerStub,
|
||||
{} as EvalLogViewer,
|
||||
STORAGE_DIR,
|
||||
|
||||
Reference in New Issue
Block a user