diff --git a/extensions/ql-vscode/src/extension.ts b/extensions/ql-vscode/src/extension.ts index 39c26e2e6..c5d16765a 100644 --- a/extensions/ql-vscode/src/extension.ts +++ b/extensions/ql-vscode/src/extension.ts @@ -467,16 +467,16 @@ async function activateWithInstalledDistribution( const localQueryResultsView = new ResultsView(ctx, dbm, cliServer, queryServerLogger, labelProvider); ctx.subscriptions.push(localQueryResultsView); - void logger.log('Initializing remote queries manager.'); - const rqm = new RemoteQueriesManager(ctx, cliServer, queryStorageDir, logger); - ctx.subscriptions.push(rqm); - void logger.log('Initializing variant analysis manager.'); const variantAnalysisStorageDir = path.join(ctx.globalStorageUri.fsPath, 'variant-analyses'); await fs.ensureDir(variantAnalysisStorageDir); const variantAnalysisManager = new VariantAnalysisManager(ctx, cliServer, variantAnalysisStorageDir, logger); ctx.subscriptions.push(variantAnalysisManager); + void logger.log('Initializing remote queries manager.'); + const rqm = new RemoteQueriesManager(ctx, cliServer, queryStorageDir, logger, variantAnalysisManager); + ctx.subscriptions.push(rqm); + void logger.log('Initializing query history.'); const qhm = new QueryHistoryManager( qs, diff --git a/extensions/ql-vscode/src/remote-queries/remote-queries-manager.ts b/extensions/ql-vscode/src/remote-queries/remote-queries-manager.ts index 71319ecba..6350c0b5f 100644 --- a/extensions/ql-vscode/src/remote-queries/remote-queries-manager.ts +++ b/extensions/ql-vscode/src/remote-queries/remote-queries-manager.ts @@ -22,6 +22,7 @@ import { assertNever } from '../pure/helpers-pure'; import { QueryStatus } from '../query-status'; import { DisposableObject } from '../pure/disposable-object'; import { AnalysisResults } from './shared/analysis-result'; +import { VariantAnalysisManager } from './variant-analysis-manager'; const autoDownloadMaxSize = 300 * 1024; const autoDownloadMaxCount = 100; @@ -56,6 +57,7 @@ export class RemoteQueriesManager extends DisposableObject { private readonly remoteQueriesMonitor: RemoteQueriesMonitor; private readonly analysesResultsManager: AnalysesResultsManager; + private readonly variantAnalysisManager: VariantAnalysisManager; private readonly view: RemoteQueriesView; constructor( @@ -63,11 +65,13 @@ export class RemoteQueriesManager extends DisposableObject { private readonly cliServer: CodeQLCliServer, private readonly storagePath: string, logger: Logger, + variantAnalysisManager: VariantAnalysisManager, ) { super(); this.analysesResultsManager = new AnalysesResultsManager(ctx, cliServer, storagePath, logger); this.view = new RemoteQueriesView(ctx, logger, this.analysesResultsManager); this.remoteQueriesMonitor = new RemoteQueriesMonitor(ctx, logger); + this.variantAnalysisManager = variantAnalysisManager; this.remoteQueryAddedEventEmitter = this.push(new EventEmitter()); this.remoteQueryRemovedEventEmitter = this.push(new EventEmitter()); @@ -123,7 +127,8 @@ export class RemoteQueriesManager extends DisposableObject { credentials, uri || window.activeTextEditor?.document.uri, false, progress, - token); + token, + this.variantAnalysisManager); if (querySubmission?.query) { const query = querySubmission.query; diff --git a/extensions/ql-vscode/src/remote-queries/run-remote-query.ts b/extensions/ql-vscode/src/remote-queries/run-remote-query.ts index d6505ac67..bc1a4e41b 100644 --- a/extensions/ql-vscode/src/remote-queries/run-remote-query.ts +++ b/extensions/ql-vscode/src/remote-queries/run-remote-query.ts @@ -29,6 +29,7 @@ import { getRepositorySelection, isValidSelection, RepositorySelection } from '. import { parseVariantAnalysisQueryLanguage, VariantAnalysisSubmission } from './shared/variant-analysis'; import { Repository } from './shared/repository'; import { processVariantAnalysis } from './variant-analysis-processor'; +import { VariantAnalysisManager } from './variant-analysis-manager'; export interface QlPack { name: string; @@ -182,7 +183,8 @@ export async function runRemoteQuery( uri: Uri | undefined, dryRun: boolean, progress: ProgressCallback, - token: CancellationToken + token: CancellationToken, + variantAnalysisManager: VariantAnalysisManager ): Promise { if (!(await cliServer.cliConstraints.supportsRemoteQueries())) { throw new Error(`Variant analysis is not supported by this version of CodeQL. Please upgrade to v${cli.CliVersionConstraint.CLI_VERSION_REMOTE_QUERIES @@ -273,6 +275,8 @@ export async function runRemoteQuery( const processedVariantAnalysis = processVariantAnalysis(variantAnalysisSubmission, variantAnalysisResponse); + variantAnalysisManager.onVariantAnalysisSubmitted(processedVariantAnalysis); + void logger.log(`Variant analysis:\n${JSON.stringify(processedVariantAnalysis, null, 2)}`); void showAndLogInformationMessage(`Variant analysis ${processedVariantAnalysis.query.name} submitted for processing`); diff --git a/extensions/ql-vscode/src/remote-queries/variant-analysis-manager.ts b/extensions/ql-vscode/src/remote-queries/variant-analysis-manager.ts index 424ffcca7..50805f79c 100644 --- a/extensions/ql-vscode/src/remote-queries/variant-analysis-manager.ts +++ b/extensions/ql-vscode/src/remote-queries/variant-analysis-manager.ts @@ -1,5 +1,5 @@ import * as ghApiClient from './gh-api/gh-api-client'; -import { CancellationToken, ExtensionContext } from 'vscode'; +import { CancellationToken, EventEmitter, ExtensionContext } from 'vscode'; import { DisposableObject } from '../pure/disposable-object'; import { Logger } from '../logging'; import { Credentials } from '../authentication'; @@ -21,6 +21,9 @@ import { VariantAnalysisResultsManager } from './variant-analysis-results-manage import { CodeQLCliServer } from '../cli'; export class VariantAnalysisManager extends DisposableObject implements VariantAnalysisViewManager { + private readonly _onVariantAnalysisAdded = this.push(new EventEmitter()); + readonly onVariantAnalysisAdded = this._onVariantAnalysisAdded.event; + private readonly variantAnalysisMonitor: VariantAnalysisMonitor; private readonly variantAnalysisResultsManager: VariantAnalysisResultsManager; private readonly views = new Map(); @@ -73,6 +76,10 @@ export class VariantAnalysisManager extends DisposableObject implements VariantA await this.getView(variantAnalysis.id)?.updateView(variantAnalysis); } + public onVariantAnalysisSubmitted(variantAnalysis: VariantAnalysis): void { + this._onVariantAnalysisAdded.fire(variantAnalysis); + } + private async onRepoStateUpdated(variantAnalysisId: number, repoState: VariantAnalysisScannedRepositoryState): Promise { await this.getView(variantAnalysisId)?.updateRepoState(repoState); } diff --git a/extensions/ql-vscode/src/vscode-tests/cli-integration/remote-queries/run-remote-query.test.ts b/extensions/ql-vscode/src/vscode-tests/cli-integration/remote-queries/run-remote-query.test.ts index 187169e6a..5a16c9913 100644 --- a/extensions/ql-vscode/src/vscode-tests/cli-integration/remote-queries/run-remote-query.test.ts +++ b/extensions/ql-vscode/src/vscode-tests/cli-integration/remote-queries/run-remote-query.test.ts @@ -1,7 +1,7 @@ import { assert, expect } from 'chai'; import * as path from 'path'; import * as sinon from 'sinon'; -import { CancellationTokenSource, extensions, QuickPickItem, Uri, window } from 'vscode'; +import { CancellationTokenSource, ExtensionContext, extensions, QuickPickItem, Uri, window } from 'vscode'; import * as fs from 'fs-extra'; import * as os from 'os'; import * as yaml from 'js-yaml'; @@ -21,6 +21,9 @@ import { import { Repository } from '../../../remote-queries/gh-api/repository'; import { VariantAnalysisStatus } from '../../../remote-queries/shared/variant-analysis'; import { createMockApiResponse } from '../../factories/remote-queries/gh-api/variant-analysis-api-response'; +import { createMockExtensionContext } from '../../no-workspace'; +import { VariantAnalysisManager } from '../../../remote-queries/variant-analysis-manager'; +import { OutputChannelLogger } from '../../../logging'; describe('Remote queries', function() { const baseDir = path.join(__dirname, '../../../../src/vscode-tests/cli-integration'); @@ -37,6 +40,9 @@ describe('Remote queries', function() { let showQuickPickSpy: sinon.SinonStub; let getRepositoryFromNwoStub: sinon.SinonStub; let liveResultsStub: sinon.SinonStub; + let ctx: ExtensionContext; + let logger: any; + let variantAnalysisManager: VariantAnalysisManager; // use `function` so we have access to `this` beforeEach(async function() { @@ -49,6 +55,10 @@ describe('Remote queries', function() { throw new Error('Extension not initialized. Make sure cli is downloaded and installed properly.'); } + ctx = createMockExtensionContext(); + logger = new OutputChannelLogger('test-logger'); + variantAnalysisManager = new VariantAnalysisManager(ctx, cli, 'fake-storage-dir', logger); + if (!(await cli.cliConstraints.supportsRemoteQueries())) { console.log(`Remote queries are not supported on CodeQL CLI v${CliVersionConstraint.CLI_VERSION_REMOTE_QUERIES }. Skipping this test.`); @@ -94,7 +104,7 @@ describe('Remote queries', function() { it('should run a remote query that is part of a qlpack', async () => { const fileUri = getFile('data-remote-qlpack/in-pack.ql'); - const querySubmissionResult = await runRemoteQuery(cli, credentials, fileUri, true, progress, cancellationTokenSource.token); + const querySubmissionResult = await runRemoteQuery(cli, credentials, fileUri, true, progress, cancellationTokenSource.token, variantAnalysisManager); expect(querySubmissionResult).to.be.ok; const queryPackRootDir = querySubmissionResult!.queryDirPath!; printDirectoryContents(queryPackRootDir); @@ -155,7 +165,7 @@ describe('Remote queries', function() { it('should run a remote query that is not part of a qlpack', async () => { const fileUri = getFile('data-remote-no-qlpack/in-pack.ql'); - const querySubmissionResult = await runRemoteQuery(cli, credentials, fileUri, true, progress, cancellationTokenSource.token); + const querySubmissionResult = await runRemoteQuery(cli, credentials, fileUri, true, progress, cancellationTokenSource.token, variantAnalysisManager); expect(querySubmissionResult).to.be.ok; const queryPackRootDir = querySubmissionResult!.queryDirPath!; @@ -218,7 +228,7 @@ describe('Remote queries', function() { it('should run a remote query that is nested inside a qlpack', async () => { const fileUri = getFile('data-remote-qlpack-nested/subfolder/in-pack.ql'); - const querySubmissionResult = await runRemoteQuery(cli, credentials, fileUri, true, progress, cancellationTokenSource.token); + const querySubmissionResult = await runRemoteQuery(cli, credentials, fileUri, true, progress, cancellationTokenSource.token, variantAnalysisManager); expect(querySubmissionResult).to.be.ok; const queryPackRootDir = querySubmissionResult!.queryDirPath!; @@ -280,7 +290,7 @@ describe('Remote queries', function() { it('should cancel a run before uploading', async () => { const fileUri = getFile('data-remote-no-qlpack/in-pack.ql'); - const promise = runRemoteQuery(cli, credentials, fileUri, true, progress, cancellationTokenSource.token); + const promise = runRemoteQuery(cli, credentials, fileUri, true, progress, cancellationTokenSource.token, variantAnalysisManager); cancellationTokenSource.token.isCancellationRequested = true; @@ -306,7 +316,7 @@ describe('Remote queries', function() { it('should run a variant analysis that is part of a qlpack', async () => { const fileUri = getFile('data-remote-qlpack/in-pack.ql'); - const querySubmissionResult = await runRemoteQuery(cli, credentials, fileUri, true, progress, cancellationTokenSource.token); + const querySubmissionResult = await runRemoteQuery(cli, credentials, fileUri, true, progress, cancellationTokenSource.token, variantAnalysisManager); expect(querySubmissionResult).to.be.ok; const variantAnalysis = querySubmissionResult!.variantAnalysis!; expect(variantAnalysis.id).to.be.equal(mockApiResponse.id); @@ -319,7 +329,7 @@ describe('Remote queries', function() { it('should run a remote query that is not part of a qlpack', async () => { const fileUri = getFile('data-remote-no-qlpack/in-pack.ql'); - const querySubmissionResult = await runRemoteQuery(cli, credentials, fileUri, true, progress, cancellationTokenSource.token); + const querySubmissionResult = await runRemoteQuery(cli, credentials, fileUri, true, progress, cancellationTokenSource.token, variantAnalysisManager); expect(querySubmissionResult).to.be.ok; const variantAnalysis = querySubmissionResult!.variantAnalysis!; expect(variantAnalysis.id).to.be.equal(mockApiResponse.id); @@ -332,7 +342,7 @@ describe('Remote queries', function() { it('should run a remote query that is nested inside a qlpack', async () => { const fileUri = getFile('data-remote-qlpack-nested/subfolder/in-pack.ql'); - const querySubmissionResult = await runRemoteQuery(cli, credentials, fileUri, true, progress, cancellationTokenSource.token); + const querySubmissionResult = await runRemoteQuery(cli, credentials, fileUri, true, progress, cancellationTokenSource.token, variantAnalysisManager); expect(querySubmissionResult).to.be.ok; const variantAnalysis = querySubmissionResult!.variantAnalysis!; expect(variantAnalysis.id).to.be.equal(mockApiResponse.id); @@ -345,7 +355,7 @@ describe('Remote queries', function() { it('should cancel a run before uploading', async () => { const fileUri = getFile('data-remote-no-qlpack/in-pack.ql'); - const promise = runRemoteQuery(cli, credentials, fileUri, true, progress, cancellationTokenSource.token); + const promise = runRemoteQuery(cli, credentials, fileUri, true, progress, cancellationTokenSource.token, variantAnalysisManager); cancellationTokenSource.token.isCancellationRequested = true;