Merge pull request #2070 from github/koesie10/remove-remote-query-manager

Remove remote queries manager
This commit is contained in:
Koen Vlaswinkel
2023-02-14 12:16:02 +01:00
committed by GitHub
9 changed files with 4 additions and 970 deletions

View File

@@ -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,

View File

@@ -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);
}
}
}

View File

@@ -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}`;
}
}

View File

@@ -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)];
}
}

View File

@@ -1,293 +0,0 @@
import {
ExtensionContext,
window as Window,
ViewColumn,
Uri,
workspace,
} from "vscode";
import { basename } from "path";
import {
ToRemoteQueriesMessage,
FromRemoteQueriesMessage,
RemoteQueryDownloadAnalysisResultsMessage,
RemoteQueryDownloadAllAnalysesResultsMessage,
} from "../pure/interface-types";
import { Logger } from "../common";
import { assertNever } from "../pure/helpers-pure";
import {
AnalysisSummary,
RemoteQueryResult,
sumAnalysisSummariesResults,
} from "./remote-query-result";
import { RemoteQuery } from "./remote-query";
import {
AnalysisSummary as AnalysisResultViewModel,
RemoteQueryResult as RemoteQueryResultViewModel,
} from "./shared/remote-query-result";
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";
export class RemoteQueriesView extends AbstractWebview<
ToRemoteQueriesMessage,
FromRemoteQueriesMessage
> {
private currentQueryId: string | undefined;
constructor(
ctx: ExtensionContext,
private readonly logger: Logger,
private readonly analysesResultsManager: AnalysesResultsManager,
) {
super(ctx);
this.panelLoadedCallBacks.push(() => {
void logger.log("Variant analysis results view loaded");
});
}
async showResults(query: RemoteQuery, queryResult: RemoteQueryResult) {
const panel = await this.getPanel();
panel.reveal(undefined, true);
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,
);
}
/**
* Builds up a model tailored to the view based on the query and result domain entities.
* The data is cleaned up, sorted where necessary, and transformed to a format that
* the view model can use.
* @param query Information about the query that was run.
* @param queryResult The result of the query.
* @returns A fully created view model.
*/
private buildViewModel(
query: RemoteQuery,
queryResult: RemoteQueryResult,
): RemoteQueryResultViewModel {
const queryFileName = basename(query.queryFilePath);
const totalResultCount = sumAnalysisSummariesResults(
queryResult.analysisSummaries,
);
const executionDuration = this.getDuration(
queryResult.executionEndTime,
query.executionStartTime,
);
const analysisSummaries = this.buildAnalysisSummaries(
queryResult.analysisSummaries,
);
const totalRepositoryCount = queryResult.analysisSummaries.length;
const affectedRepositories = queryResult.analysisSummaries.filter(
(r) => r.resultCount > 0,
);
return {
queryId: queryResult.queryId,
queryTitle: query.queryName,
queryFileName,
queryFilePath: query.queryFilePath,
queryText: query.queryText,
language: query.language,
workflowRunUrl: `https://github.com/${query.controllerRepository.owner}/${query.controllerRepository.name}/actions/runs/${query.actionsWorkflowRunId}`,
totalRepositoryCount,
affectedRepositoryCount: affectedRepositories.length,
totalResultCount,
executionTimestamp: this.formatDate(query.executionStartTime),
executionDuration,
analysisSummaries,
analysisFailures: queryResult.analysisFailures,
};
}
protected getPanelConfig(): WebviewPanelConfig {
return {
viewId: "remoteQueriesView",
title: "CodeQL Query Results",
viewColumn: ViewColumn.Active,
preserveFocus: true,
view: "remote-queries",
additionalOptions: {
localResourceRoots: [Uri.file(this.analysesResultsManager.storagePath)],
},
};
}
protected onPanelDispose(): void {
this.currentQueryId = undefined;
}
protected async onMessage(msg: FromRemoteQueriesMessage): Promise<void> {
switch (msg.t) {
case "viewLoaded":
this.onWebViewLoaded();
break;
case "remoteQueryError":
void this.logger.log(`Variant analysis error: ${msg.error}`);
break;
case "openFile":
await this.openFile(msg.filePath);
break;
case "openVirtualFile":
await this.openVirtualFile(msg.queryText);
break;
case "copyRepoList":
break;
case "remoteQueryDownloadAnalysisResults":
await this.downloadAnalysisResults(msg);
break;
case "remoteQueryDownloadAllAnalysesResults":
await this.downloadAllAnalysesResults(msg);
break;
case "remoteQueryExportResults":
break;
case "telemetry":
telemetryListener?.sendUIInteraction(msg.action);
break;
default:
assertNever(msg);
}
}
private async openFile(filePath: string) {
try {
const textDocument = await workspace.openTextDocument(filePath);
await Window.showTextDocument(textDocument, ViewColumn.One);
} catch (error) {
void showAndLogWarningMessage(`Could not open file: ${filePath}`);
}
}
private async openVirtualFile(text: string) {
try {
const params = new URLSearchParams({
queryText: encodeURIComponent(SHOW_QUERY_TEXT_MSG + text),
});
const uri = Uri.parse(
`remote-query:query-text.ql?${params.toString()}`,
true,
);
const doc = await workspace.openTextDocument(uri);
await Window.showTextDocument(doc, { preview: false });
} catch (error) {
void showAndLogWarningMessage("Could not open query text");
}
}
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);
}
private formatDate = (millis: number): string => {
const d = new Date(millis);
const datePart = d.toLocaleDateString(undefined, {
day: "numeric",
month: "short",
});
const timePart = d.toLocaleTimeString(undefined, {
hour: "numeric",
minute: "numeric",
hour12: true,
});
return `${datePart} at ${timePart}`;
};
private formatFileSize(bytes: number): string {
const kb = bytes / 1024;
const mb = kb / 1024;
const gb = mb / 1024;
if (bytes < 1024) {
return `${bytes} bytes`;
} else if (kb < 1024) {
return `${kb.toFixed(2)} KB`;
} else if (mb < 1024) {
return `${mb.toFixed(2)} MB`;
} else {
return `${gb.toFixed(2)} GB`;
}
}
/**
* Builds up a list of analysis summaries, in a data structure tailored to the view.
* @param analysisSummaries The summaries of a specific analyses.
* @returns A fully created view model.
*/
private buildAnalysisSummaries(
analysisSummaries: AnalysisSummary[],
): AnalysisResultViewModel[] {
const filteredAnalysisSummaries = analysisSummaries.filter(
(r) => r.resultCount > 0,
);
const sortedAnalysisSummaries = filteredAnalysisSummaries.sort(
(a, b) => b.resultCount - a.resultCount,
);
return sortedAnalysisSummaries.map((analysisResult) => ({
nwo: analysisResult.nwo,
databaseSha: analysisResult.databaseSha || "HEAD",
resultCount: analysisResult.resultCount,
downloadLink: analysisResult.downloadLink,
sourceLocationPrefix: analysisResult.sourceLocationPrefix,
fileSize: this.formatFileSize(analysisResult.fileSizeInBytes),
starCount: analysisResult.starCount,
lastUpdated: analysisResult.lastUpdated,
}));
}
}

View File

@@ -18,12 +18,3 @@ export interface AnalysisSummary {
starCount?: number;
lastUpdated?: number;
}
/**
* Sums up the number of results for all repos queried via a remote query.
*/
export const sumAnalysisSummariesResults = (
analysisSummaries: AnalysisSummary[],
): number => {
return analysisSummaries.reduce((acc, cur) => acc + cur.resultCount, 0);
};

View File

@@ -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",

View File

@@ -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",

View File

@@ -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,