Merge pull request #1162 from github/aeisenberg/remote-query-restart

Remember remote queries across restarts
This commit is contained in:
Andrew Eisenberg
2022-02-23 11:13:17 -08:00
committed by GitHub
12 changed files with 209 additions and 127 deletions

View File

@@ -90,13 +90,13 @@ import { CodeQlStatusBarHandler } from './status-bar';
import { Credentials } from './authentication';
import { RemoteQueriesManager } from './remote-queries/remote-queries-manager';
import { RemoteQuery } from './remote-queries/remote-query';
import { RemoteQueryResult } from './remote-queries/remote-query-result';
import { URLSearchParams } from 'url';
import { RemoteQueriesInterfaceManager } from './remote-queries/remote-queries-interface';
import * as sampleData from './remote-queries/sample-data';
import { handleDownloadPacks, handleInstallPackDependencies } from './packaging';
import { AnalysesResultsManager } from './remote-queries/analyses-results-manager';
import { RemoteQueryHistoryItem } from './remote-queries/remote-query-history-item';
/**
* extension.ts
@@ -455,11 +455,15 @@ async function activateWithInstalledDistribution(
queryStorageDir,
ctx,
queryHistoryConfigurationListener,
showResults,
async (from: CompletedLocalQueryInfo, to: CompletedLocalQueryInfo) =>
showResultsForComparison(from, to),
);
await qhm.readQueryHistory();
qhm.onWillOpenQueryItem(async item => {
if (item.t === 'local' && item.completed) {
await showResultsForCompletedQuery(item as CompletedLocalQueryInfo, WebviewReveal.Forced);
}
});
ctx.subscriptions.push(qhm);
void logger.log('Initializing results panel interface.');
@@ -534,7 +538,6 @@ async function activateWithInstalledDistribution(
source.token,
);
item.completeThisQuery(completedQueryInfo);
await qhm.writeQueryHistory();
await showResultsForCompletedQuery(item as CompletedLocalQueryInfo, WebviewReveal.NotForced);
// Note we must update the query history view after showing results as the
// display and sorting might depend on the number of results
@@ -543,7 +546,7 @@ async function activateWithInstalledDistribution(
item.failureReason = e.message;
throw e;
} finally {
qhm.refreshTreeView();
await qhm.refreshTreeView();
source.dispose();
}
}
@@ -834,6 +837,12 @@ async function activateWithInstalledDistribution(
void logger.log('Initializing remote queries interface.');
const rqm = new RemoteQueriesManager(ctx, cliServer, qhm, queryStorageDir, logger);
ctx.subscriptions.push(rqm);
// wait until after the remote queries manager is initialized to read the query history
// since the rqm is notified of queries being added.
await qhm.readQueryHistory();
registerRemoteQueryTextProvider();
@@ -866,9 +875,9 @@ async function activateWithInstalledDistribution(
ctx.subscriptions.push(
commandRunner('codeQL.monitorRemoteQuery', async (
query: RemoteQuery,
queryItem: RemoteQueryHistoryItem,
token: CancellationToken) => {
await rqm.monitorRemoteQuery(query, token);
await rqm.monitorRemoteQuery(queryItem, token);
}));
ctx.subscriptions.push(

View File

@@ -288,13 +288,24 @@ export class QueryHistoryManager extends DisposableObject {
queryHistoryScrubber: Disposable | undefined;
private queryMetadataStorageLocation;
private readonly _onDidAddQueryItem = super.push(new EventEmitter<QueryHistoryInfo>());
readonly onDidAddQueryItem: Event<QueryHistoryInfo> = this
._onDidAddQueryItem.event;
private readonly _onDidRemoveQueryItem = super.push(new EventEmitter<QueryHistoryInfo>());
readonly onDidRemoveQueryItem: Event<QueryHistoryInfo> = this
._onDidRemoveQueryItem.event;
private readonly _onWillOpenQueryItem = super.push(new EventEmitter<QueryHistoryInfo>());
readonly onWillOpenQueryItem: Event<QueryHistoryInfo> = this
._onWillOpenQueryItem.event;
constructor(
private qs: QueryServerClient,
private dbm: DatabaseManager,
private queryStorageDir: string,
ctx: ExtensionContext,
private queryHistoryConfigListener: QueryHistoryConfig,
private selectedCallback: (item: CompletedLocalQueryInfo) => Promise<void>,
private doCompareCallback: (
from: CompletedLocalQueryInfo,
to: CompletedLocalQueryInfo
@@ -484,53 +495,54 @@ export class QueryHistoryManager extends DisposableObject {
void logger.log(`Reading cached query history from '${this.queryMetadataStorageLocation}'.`);
const history = await slurpQueryHistory(this.queryMetadataStorageLocation, this.queryHistoryConfigListener);
this.treeDataProvider.allHistory = history;
this.treeDataProvider.allHistory.forEach((item) => {
this._onDidAddQueryItem.fire(item);
});
}
async writeQueryHistory(): Promise<void> {
const toSave = this.treeDataProvider.allHistory.filter(q => q.isCompleted());
await splatQueryHistory(toSave, this.queryMetadataStorageLocation);
}
async invokeCallbackOn(queryHistoryItem: QueryHistoryInfo) {
if (this.selectedCallback && queryHistoryItem.isCompleted()) {
const sc = this.selectedCallback;
await sc(queryHistoryItem as CompletedLocalQueryInfo);
}
await splatQueryHistory(this.treeDataProvider.allHistory, this.queryMetadataStorageLocation);
}
async handleOpenQuery(
singleItem: QueryHistoryInfo,
multiSelect: QueryHistoryInfo[]
): Promise<void> {
// TODO will support remote queries
const { finalSingleItem, finalMultiSelect } = this.determineSelection(singleItem, multiSelect);
if (!this.assertSingleQuery(finalMultiSelect) || finalSingleItem?.t !== 'local') {
if (!this.assertSingleQuery(finalMultiSelect) || !finalSingleItem) {
return;
}
const queryPath = finalSingleItem.t === 'local'
? finalSingleItem.initialInfo.queryPath
: finalSingleItem.remoteQuery.queryFilePath;
const textDocument = await workspace.openTextDocument(
Uri.file(finalSingleItem.initialInfo.queryPath)
Uri.file(queryPath)
);
const editor = await window.showTextDocument(
textDocument,
ViewColumn.One
);
const queryText = finalSingleItem.initialInfo.queryText;
if (queryText !== undefined && finalSingleItem.initialInfo.isQuickQuery) {
await editor.edit((edit) =>
edit.replace(
textDocument.validateRange(
new Range(0, 0, textDocument.lineCount, 0)
),
queryText
)
);
if (finalSingleItem.t === 'local') {
const queryText = finalSingleItem.initialInfo.queryText;
if (queryText !== undefined && finalSingleItem.initialInfo.isQuickQuery) {
await editor.edit((edit) =>
edit.replace(
textDocument.validateRange(
new Range(0, 0, textDocument.lineCount, 0)
),
queryText
)
);
}
}
}
async handleRemoveHistoryItem(
singleItem: QueryHistoryInfo,
multiSelect: QueryHistoryInfo[]
multiSelect: QueryHistoryInfo[] = []
) {
const { finalSingleItem, finalMultiSelect } = this.determineSelection(singleItem, multiSelect);
const toDelete = (finalMultiSelect || [finalSingleItem]);
@@ -548,15 +560,13 @@ export class QueryHistoryManager extends DisposableObject {
} else {
// Remote queries can be removed locally, but not remotely.
// The user must cancel the query on GitHub Actions explicitly.
this.treeDataProvider.remove(item);
await item.deleteQuery();
void logger.log(`Deleted ${item.label}.`);
if (item.status === QueryStatus.InProgress) {
void showAndLogInformationMessage(
'The remote query is still running on GitHub Actions. To cancel there, you must go to the query run in your browser.'
);
void logger.log('The remote query is still running on GitHub Actions. To cancel there, you must go to the query run in your browser.');
}
this._onDidRemoveQueryItem.fire(item);
}
}));
@@ -564,7 +574,7 @@ export class QueryHistoryManager extends DisposableObject {
const current = this.treeDataProvider.getCurrent();
if (current !== undefined) {
await this.treeView.reveal(current, { select: true });
await this.invokeCallbackOn(current);
await this._onWillOpenQueryItem.fire(current);
}
}
@@ -635,7 +645,7 @@ export class QueryHistoryManager extends DisposableObject {
const from = this.compareWithItem || singleItem;
const to = await this.findOtherQueryToCompare(from, finalMultiSelect);
if (from.isCompleted() && to?.isCompleted()) {
if (from.completed && to?.completed) {
await this.doCompareCallback(from as CompletedLocalQueryInfo, to as CompletedLocalQueryInfo);
}
} catch (e) {
@@ -647,9 +657,8 @@ export class QueryHistoryManager extends DisposableObject {
singleItem: QueryHistoryInfo,
multiSelect: QueryHistoryInfo[]
) {
// TODO will support remote queries
const { finalSingleItem, finalMultiSelect } = this.determineSelection(singleItem, multiSelect);
if (!this.assertSingleQuery(finalMultiSelect) || finalSingleItem?.t !== 'local') {
if (!this.assertSingleQuery(finalMultiSelect) || !finalSingleItem) {
return;
}
@@ -668,7 +677,7 @@ export class QueryHistoryManager extends DisposableObject {
await this.handleOpenQuery(finalSingleItem, [finalSingleItem]);
} else {
// show results on single click
await this.invokeCallbackOn(finalSingleItem);
await this._onWillOpenQueryItem.fire(finalSingleItem);
}
}
@@ -713,17 +722,20 @@ export class QueryHistoryManager extends DisposableObject {
) {
const { finalSingleItem, finalMultiSelect } = this.determineSelection(singleItem, multiSelect);
// TODO will support remote queries
if (!this.assertSingleQuery(finalMultiSelect) || finalSingleItem?.t !== 'local') {
if (!this.assertSingleQuery(finalMultiSelect) || !finalSingleItem) {
return;
}
const params = new URLSearchParams({
isQuickEval: String(!!finalSingleItem.initialInfo.quickEvalPosition),
isQuickEval: String(!!(finalSingleItem.t === 'local' && finalSingleItem.initialInfo.quickEvalPosition)),
queryText: encodeURIComponent(await this.getQueryText(finalSingleItem)),
});
const queryId = finalSingleItem.t === 'local'
? finalSingleItem.initialInfo.id
: finalSingleItem.queryId;
const uri = Uri.parse(
`codeql:${finalSingleItem.initialInfo.id}?${params.toString()}`, true
`codeql:${queryId}?${params.toString()}`, true
);
const doc = await workspace.openTextDocument(uri);
await window.showTextDocument(doc, { preview: false });
@@ -809,13 +821,15 @@ export class QueryHistoryManager extends DisposableObject {
}
async getQueryText(item: QueryHistoryInfo): Promise<string> {
// TODO the query text for remote queries is not yet available
return item.t === 'local' ? item.initialInfo.queryText : '';
return item.t === 'local'
? item.initialInfo.queryText
: item.remoteQuery.queryText;
}
addQuery(item: QueryHistoryInfo) {
this.treeDataProvider.pushQuery(item);
this.updateTreeViewSelectionIfVisible();
this._onDidAddQueryItem.fire(item);
}
/**
@@ -1011,7 +1025,8 @@ the file in the file explorer and dragging it into the workspace.`
};
}
refreshTreeView(): void {
async refreshTreeView(): Promise<void> {
this.treeDataProvider.refresh();
await this.writeQueryHistory();
}
}

View File

@@ -291,7 +291,7 @@ export class LocalQueryInfo {
}
}
isCompleted(): boolean {
get completed(): boolean {
return !!this.completedQuery;
}

View File

@@ -38,7 +38,7 @@ export async function slurpQueryHistory(fsPath: string, config: QueryHistoryConf
q.completedQuery.dispose = () => { /**/ };
}
} else if (q.t === 'remote') {
// TODO Remote queries are not implemented yet.
// noop
}
return q;
});
@@ -47,8 +47,11 @@ export async function slurpQueryHistory(fsPath: string, config: QueryHistoryConf
// most likely another workspace has deleted them because the
// queries aged out.
return asyncFilter(parsedQueries, async (q) => {
if (q.t !== 'local') {
return false;
if (q.t === 'remote') {
// the slurper doesn't know where the remote queries are stored
// so we need to assume here that they exist. Later, we check to
// see if they exist on disk.
return true;
}
const resultsPath = q.completedQuery?.query.resultsPaths.resultsPath;
return !!resultsPath && await fs.pathExists(resultsPath);
@@ -57,6 +60,8 @@ export async function slurpQueryHistory(fsPath: string, config: QueryHistoryConf
void showAndLogErrorMessage('Error loading query history.', {
fullMessage: ['Error loading query history.', e.stack].join('\n'),
});
// since the query history is invalid, it should be deleted so this error does not happen on next startup.
await fs.remove(fsPath);
return [];
}
}
@@ -75,8 +80,8 @@ export async function splatQueryHistory(queries: QueryHistoryInfo[], fsPath: str
if (!(await fs.pathExists(fsPath))) {
await fs.mkdir(path.dirname(fsPath), { recursive: true });
}
// remove incomplete queries since they cannot be recreated on restart
const filteredQueries = queries.filter(q => q.t === 'local' && q.completedQuery !== undefined);
// remove incomplete local queries since they cannot be recreated on restart
const filteredQueries = queries.filter(q => q.t === 'local' ? q.completedQuery !== undefined : true);
const data = JSON.stringify(filteredQueries, null, 2);
await fs.writeFile(fsPath, data);
} catch (e) {

View File

@@ -261,8 +261,8 @@ export class RemoteQueriesInterfaceManager {
return this.getPanel().webview.postMessage(msg);
}
private getDuration(startTime: Date, endTime: Date): string {
const diffInMs = startTime.getTime() - endTime.getTime();
private getDuration(startTime: number, endTime: number): string {
const diffInMs = startTime - endTime;
return this.formatDuration(diffInMs);
}
@@ -282,7 +282,8 @@ export class RemoteQueriesInterfaceManager {
}
}
private formatDate = (d: Date): string => {
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}`;

View File

@@ -21,11 +21,14 @@ import { assertNever } from '../pure/helpers-pure';
import { RemoteQueryHistoryItem } from './remote-query-history-item';
import { QueryHistoryManager } from '../query-history';
import { QueryStatus } from '../query-status';
import { DisposableObject } from '../pure/disposable-object';
import { QueryHistoryInfo } from '../query-results';
const autoDownloadMaxSize = 300 * 1024;
const autoDownloadMaxCount = 100;
export class RemoteQueriesManager {
const noop = () => { /* do nothing */ };
export class RemoteQueriesManager extends DisposableObject {
private readonly remoteQueriesMonitor: RemoteQueriesMonitor;
private readonly analysesResultsManager: AnalysesResultsManager;
private readonly interfaceManager: RemoteQueriesInterfaceManager;
@@ -37,9 +40,50 @@ export class RemoteQueriesManager {
private readonly storagePath: string,
logger: Logger,
) {
super();
this.analysesResultsManager = new AnalysesResultsManager(ctx, storagePath, logger);
this.interfaceManager = new RemoteQueriesInterfaceManager(ctx, logger, this.analysesResultsManager);
this.remoteQueriesMonitor = new RemoteQueriesMonitor(ctx, logger);
// Handle events from the query history manager
this.push(this.qhm.onDidAddQueryItem(this.handleAddQueryItem.bind(this)));
this.push(this.qhm.onDidRemoveQueryItem(this.handleRemoveQueryItem.bind(this)));
this.push(this.qhm.onWillOpenQueryItem(this.handleOpenQueryItem.bind(this)));
}
private async handleAddQueryItem(queryItem: QueryHistoryInfo) {
if (queryItem?.t === 'remote') {
if (!(await this.queryHistoryItemExists(queryItem))) {
// In this case, the query was deleted from disk, most likely because it was purged
// by another workspace. We should remove it from the history manager.
await this.qhm.handleRemoveHistoryItem(queryItem);
} else if (queryItem.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.
await commands.executeCommand('codeQL.monitorRemoteQuery', queryItem);
}
}
}
private async handleRemoveQueryItem(queryItem: QueryHistoryInfo) {
if (queryItem?.t === 'remote') {
await this.removeStorageDirectory(queryItem);
}
}
private async handleOpenQueryItem(queryItem: QueryHistoryInfo) {
if (queryItem?.t === 'remote') {
try {
const remoteQueryResult = await this.retrieveJsonFile(queryItem, 'query-result.json') as RemoteQueryResult;
// open results in the background
void this.openResults(queryItem.remoteQuery, remoteQueryResult).then(
noop,
err => void showAndLogErrorMessage(err)
);
} catch (e) {
void showAndLogErrorMessage(`Could not open query results. ${e}`);
}
}
}
public async runRemoteQuery(
@@ -56,66 +100,74 @@ export class RemoteQueriesManager {
progress,
token);
if (querySubmission && querySubmission.query) {
void commands.executeCommand('codeQL.monitorRemoteQuery', querySubmission.query);
if (querySubmission?.query) {
const query = querySubmission.query;
const queryId = this.createQueryId(query.queryName);
const queryHistoryItem: RemoteQueryHistoryItem = {
t: 'remote',
status: QueryStatus.InProgress,
completed: false,
queryId,
label: query.queryName,
remoteQuery: query,
};
await this.prepareStorageDirectory(queryHistoryItem);
await this.storeJsonFile(queryHistoryItem, 'query.json', query);
this.qhm.addQuery(queryHistoryItem);
await this.qhm.refreshTreeView();
}
}
public async monitorRemoteQuery(
query: RemoteQuery,
queryItem: RemoteQueryHistoryItem,
cancellationToken: CancellationToken
): Promise<void> {
const queryId = this.createQueryId(query.queryName);
await this.prepareStorageDirectory(queryId);
await this.storeFile(queryId, 'query.json', query);
const queryHistoryItem = new RemoteQueryHistoryItem(query.queryName, queryId, this.storagePath);
this.qhm.addQuery(queryHistoryItem);
const credentials = await Credentials.initialize(this.ctx);
const queryWorkflowResult = await this.remoteQueriesMonitor.monitorQuery(query, cancellationToken);
const queryWorkflowResult = await this.remoteQueriesMonitor.monitorQuery(queryItem.remoteQuery, cancellationToken);
const executionEndTime = new Date();
const executionEndTime = Date.now();
if (queryWorkflowResult.status === 'CompletedSuccessfully') {
const resultIndex = await getRemoteQueryIndex(credentials, query);
const resultIndex = await getRemoteQueryIndex(credentials, queryItem.remoteQuery);
if (!resultIndex) {
void showAndLogErrorMessage(`There was an issue retrieving the result for the query ${query.queryName}`);
void showAndLogErrorMessage(`There was an issue retrieving the result for the query ${queryItem.label}`);
return;
}
queryHistoryItem.completed = true;
queryHistoryItem.status = QueryStatus.Completed;
const queryResult = this.mapQueryResult(executionEndTime, resultIndex, queryId);
queryItem.completed = true;
queryItem.status = QueryStatus.Completed;
const queryResult = this.mapQueryResult(executionEndTime, resultIndex, queryItem.queryId);
await this.storeFile(queryId, 'query-result.json', queryResult);
await this.storeJsonFile(queryItem, '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(query, queryResult).then(
() => { /* do nothing */ },
void this.askToOpenResults(queryItem.remoteQuery, queryResult).then(
noop,
err => {
void showAndLogErrorMessage(err);
}
);
} else if (queryWorkflowResult.status === 'CompletedUnsuccessfully') {
queryHistoryItem.failureReason = queryWorkflowResult.error;
queryHistoryItem.status = QueryStatus.Failed;
await showAndLogErrorMessage(`Remote query execution failed. Error: ${queryWorkflowResult.error}`);
queryItem.failureReason = queryWorkflowResult.error;
queryItem.status = QueryStatus.Failed;
void showAndLogErrorMessage(`Remote query execution failed. Error: ${queryWorkflowResult.error}`);
} else if (queryWorkflowResult.status === 'Cancelled') {
queryHistoryItem.failureReason = 'Cancelled';
queryHistoryItem.status = QueryStatus.Failed;
await showAndLogErrorMessage('Remote query monitoring was cancelled');
queryItem.failureReason = 'Cancelled';
queryItem.status = QueryStatus.Failed;
void showAndLogErrorMessage('Remote query monitoring was cancelled');
} else if (queryWorkflowResult.status === 'InProgress') {
// Should not get here
await showAndLogErrorMessage(`Unexpected status: ${queryWorkflowResult.status}`);
// Should not get here. Only including this to ensure `assertNever` uses proper type checking.
void showAndLogErrorMessage(`Unexpected status: ${queryWorkflowResult.status}`);
} else {
// Ensure all cases are covered
assertNever(queryWorkflowResult.status);
}
this.qhm.refreshTreeView();
await this.qhm.refreshTreeView();
}
public async autoDownloadRemoteQueryResults(
@@ -138,7 +190,7 @@ export class RemoteQueriesManager {
results => this.interfaceManager.setAnalysisResults(results));
}
private mapQueryResult(executionEndTime: Date, resultIndex: RemoteQueryResultIndex, queryId: string): RemoteQueryResult {
private mapQueryResult(executionEndTime: number, resultIndex: RemoteQueryResultIndex, queryId: string): RemoteQueryResult {
const analysisSummaries = resultIndex.successes.map(item => ({
nwo: item.nwo,
@@ -163,13 +215,17 @@ export class RemoteQueriesManager {
};
}
public async openResults(query: RemoteQuery, queryResult: RemoteQueryResult) {
await this.interfaceManager.showResults(query, queryResult);
}
private async askToOpenResults(query: RemoteQuery, queryResult: RemoteQueryResult): Promise<void> {
const totalResultCount = queryResult.analysisSummaries.reduce((acc, cur) => acc + cur.resultCount, 0);
const message = `Query "${query.queryName}" run on ${query.repositories.length} repositories and returned ${totalResultCount} results`;
const shouldOpenView = await showInformationMessageWithAction(message, 'View');
if (shouldOpenView) {
await this.interfaceManager.showResults(query, queryResult);
await this.openResults(query, queryResult);
}
}
@@ -191,13 +247,27 @@ export class RemoteQueriesManager {
*
* @param queryName The name of the query that was run.
*/
private async prepareStorageDirectory(queryId: string): Promise<void> {
await createTimestampFile(path.join(this.storagePath, queryId));
private async prepareStorageDirectory(queryHistoryItem: RemoteQueryHistoryItem): Promise<void> {
await createTimestampFile(path.join(this.storagePath, queryHistoryItem.queryId));
}
private async storeFile(queryId: string, fileName: string, obj: any): Promise<void> {
const filePath = path.join(this.storagePath, queryId, fileName);
private async storeJsonFile<T>(queryHistoryItem: RemoteQueryHistoryItem, fileName: string, obj: T): Promise<void> {
const filePath = path.join(this.storagePath, queryHistoryItem.queryId, fileName);
await fs.writeFile(filePath, JSON.stringify(obj, null, 2), 'utf8');
}
private async retrieveJsonFile<T>(queryHistoryItem: RemoteQueryHistoryItem, fileName: string): Promise<T> {
const filePath = path.join(this.storagePath, queryHistoryItem.queryId, fileName);
return JSON.parse(await fs.readFile(filePath, 'utf8'));
}
private async removeStorageDirectory(queryItem: RemoteQueryHistoryItem): Promise<void> {
const filePath = path.join(this.storagePath, queryItem.queryId);
await fs.remove(filePath);
}
private async queryHistoryItemExists(queryItem: RemoteQueryHistoryItem): Promise<boolean> {
const filePath = path.join(this.storagePath, queryItem.queryId);
return await fs.pathExists(filePath);
}
}

View File

@@ -1,33 +1,15 @@
import * as fs from 'fs-extra';
import * as path from 'path';
import { QueryStatus } from '../query-status';
import { RemoteQuery } from './remote-query';
/**
* Information about a remote query.
*/
export class RemoteQueryHistoryItem {
readonly t = 'remote';
failureReason: string | undefined;
export interface RemoteQueryHistoryItem {
readonly t: 'remote';
failureReason?: string;
status: QueryStatus;
completed = false;
constructor(
public label: string, // TODO, the query label should have interpolation like local queries
public readonly queryId: string,
private readonly storagePath: string,
) {
this.status = QueryStatus.InProgress;
}
isCompleted(): boolean {
return this.completed;
}
async deleteQuery(): Promise<void> {
await fs.remove(this.querySaveDir);
}
get querySaveDir(): string {
return path.join(this.storagePath, this.queryId);
}
completed: boolean;
readonly queryId: string,
label: string, // TODO, the query label should have interpolation like local queries
remoteQuery: RemoteQuery,
}

View File

@@ -2,7 +2,7 @@ import { DownloadLink } from './download-link';
import { AnalysisFailure } from './shared/analysis-failure';
export interface RemoteQueryResult {
executionEndTime: Date;
executionEndTime: number; // Can't use a Date here since it needs to be serialized and desserialized.
analysisSummaries: AnalysisSummary[];
analysisFailures: AnalysisFailure[];
}

View File

@@ -6,6 +6,6 @@ export interface RemoteQuery {
queryText: string;
controllerRepository: Repository;
repositories: Repository[];
executionStartTime: Date;
executionStartTime: number; // Use number here since it needs to be serialized and desserialized.
actionsWorkflowRunId: number;
}

View File

@@ -303,7 +303,7 @@ export async function runRemoteQuery(
});
const workflowRunId = await runRemoteQueriesApiRequest(credentials, 'main', language, repositories, owner, repo, base64Pack, dryRun);
const queryStartTime = new Date();
const queryStartTime = Date.now();
const queryMetadata = await tryGetQueryMetadata(cliServer, queryFile);
if (dryRun) {
@@ -435,7 +435,7 @@ async function buildRemoteQueryEntity(
queryMetadata: QueryMetadata | undefined,
controllerRepoOwner: string,
controllerRepoName: string,
queryStartTime: Date,
queryStartTime: number,
workflowRunId: number
): Promise<RemoteQuery> {
// The query name is either the name as specified in the query metadata, or the file name.

View File

@@ -32,12 +32,12 @@ export const sampleRemoteQuery: RemoteQuery = {
name: 'repo5'
}
],
executionStartTime: new Date('2022-01-06T17:02:15.026Z'),
executionStartTime: new Date('2022-01-06T17:02:15.026Z').getTime(),
actionsWorkflowRunId: 1662757118
};
export const sampleRemoteQueryResult: RemoteQueryResult = {
executionEndTime: new Date('2022-01-06T17:04:37.026Z'),
executionEndTime: new Date('2022-01-06T17:04:37.026Z').getTime(),
analysisSummaries: [
{
nwo: 'big-corp/repo1',

View File

@@ -743,12 +743,12 @@ describe('query-history', () => {
extensionPath: vscode.Uri.file('/x/y/z').fsPath,
} as vscode.ExtensionContext,
configListener,
selectedCallback,
doCompareCallback
);
qhm.onWillOpenQueryItem(selectedCallback);
(qhm.treeDataProvider as any).history = [...allHistory];
await vscode.workspace.saveAll();
qhm.refreshTreeView();
await qhm.refreshTreeView();
return qhm;
}
});