Skip to content

Commit ab9cf46

Browse files
Merge pull request #1559 from github/elenatanasoiu/download-variant-analysis-results
Download variant analysis results
2 parents 9422c6d + bb7246b commit ab9cf46

14 files changed

+463
-51
lines changed

extensions/ql-vscode/src/extension.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,12 @@ import { createInitialQueryInfo } from './run-queries-shared';
105105
import { LegacyQueryRunner } from './legacy-query-server/legacyRunner';
106106
import { QueryRunner } from './queryRunner';
107107
import { VariantAnalysisView } from './remote-queries/variant-analysis-view';
108-
import { VariantAnalysisMonitor } from './remote-queries/variant-analysis-monitor';
109108
import { VariantAnalysis } from './remote-queries/shared/variant-analysis';
109+
import {
110+
VariantAnalysis as VariantAnalysisApiResponse,
111+
VariantAnalysisScannedRepository as ApiVariantAnalysisScannedRepository
112+
} from './remote-queries/gh-api/variant-analysis';
113+
import { VariantAnalysisManager } from './remote-queries/variant-analysis-manager';
110114

111115
/**
112116
* extension.ts
@@ -896,13 +900,23 @@ async function activateWithInstalledDistribution(
896900
})
897901
);
898902

899-
const variantAnalysisMonitor = new VariantAnalysisMonitor(ctx, logger);
903+
const variantAnalysisManager = new VariantAnalysisManager(ctx, logger);
900904
ctx.subscriptions.push(
901905
commandRunner('codeQL.monitorVariantAnalysis', async (
902906
variantAnalysis: VariantAnalysis,
903907
token: CancellationToken
904908
) => {
905-
await variantAnalysisMonitor.monitorVariantAnalysis(variantAnalysis, token);
909+
await variantAnalysisManager.monitorVariantAnalysis(variantAnalysis, token);
910+
})
911+
);
912+
913+
ctx.subscriptions.push(
914+
commandRunner('codeQL.autoDownloadVariantAnalysisResult', async (
915+
scannedRepo: ApiVariantAnalysisScannedRepository,
916+
variantAnalysisSummary: VariantAnalysisApiResponse,
917+
token: CancellationToken
918+
) => {
919+
await variantAnalysisManager.autoDownloadVariantAnalysisResult(scannedRepo, variantAnalysisSummary, token);
906920
})
907921
);
908922

extensions/ql-vscode/src/remote-queries/gh-api/gh-api-client.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,19 @@ export async function getVariantAnalysisRepo(
7474
return response.data;
7575
}
7676

77+
export async function getVariantAnalysisRepoResult(
78+
credentials: Credentials,
79+
downloadUrl: string,
80+
): Promise<unknown> {
81+
const octokit = await credentials.getOctokit();
82+
83+
const response: OctokitResponse<VariantAnalysisRepoTask> = await octokit.request(
84+
`GET ${downloadUrl}`
85+
);
86+
87+
return response.data;
88+
}
89+
7790
export async function getRepositoryFromNwo(
7891
credentials: Credentials,
7992
owner: string,
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import * as ghApiClient from './gh-api/gh-api-client';
2+
import * as path from 'path';
3+
import * as fs from 'fs-extra';
4+
import { CancellationToken, ExtensionContext } from 'vscode';
5+
import { DisposableObject } from '../pure/disposable-object';
6+
import { Logger } from '../logging';
7+
import { Credentials } from '../authentication';
8+
import { VariantAnalysisMonitor } from './variant-analysis-monitor';
9+
import {
10+
VariantAnalysis as VariantAnalysisApiResponse,
11+
VariantAnalysisRepoTask,
12+
VariantAnalysisScannedRepository as ApiVariantAnalysisScannedRepository
13+
} from './gh-api/variant-analysis';
14+
import { VariantAnalysis } from './shared/variant-analysis';
15+
import { getErrorMessage } from '../pure/helpers-pure';
16+
17+
export class VariantAnalysisManager extends DisposableObject {
18+
private readonly variantAnalysisMonitor: VariantAnalysisMonitor;
19+
20+
constructor(
21+
private readonly ctx: ExtensionContext,
22+
logger: Logger,
23+
) {
24+
super();
25+
this.variantAnalysisMonitor = new VariantAnalysisMonitor(ctx, logger);
26+
}
27+
28+
public async monitorVariantAnalysis(
29+
variantAnalysis: VariantAnalysis,
30+
cancellationToken: CancellationToken
31+
): Promise<void> {
32+
await this.variantAnalysisMonitor.monitorVariantAnalysis(variantAnalysis, cancellationToken);
33+
}
34+
35+
public async autoDownloadVariantAnalysisResult(
36+
scannedRepo: ApiVariantAnalysisScannedRepository,
37+
variantAnalysisSummary: VariantAnalysisApiResponse,
38+
cancellationToken: CancellationToken
39+
): Promise<void> {
40+
41+
const credentials = await Credentials.initialize(this.ctx);
42+
if (!credentials) { throw Error('Error authenticating with GitHub'); }
43+
44+
if (cancellationToken && cancellationToken.isCancellationRequested) {
45+
return;
46+
}
47+
48+
let repoTask: VariantAnalysisRepoTask;
49+
try {
50+
repoTask = await ghApiClient.getVariantAnalysisRepo(
51+
credentials,
52+
variantAnalysisSummary.controller_repo.id,
53+
variantAnalysisSummary.id,
54+
scannedRepo.repository.id
55+
);
56+
}
57+
catch (e) { throw new Error(`Could not download the results for variant analysis with id: ${variantAnalysisSummary.id}. Error: ${getErrorMessage(e)}`); }
58+
59+
if (repoTask.artifact_url) {
60+
const resultDirectory = path.join(
61+
this.ctx.globalStorageUri.fsPath,
62+
'variant-analyses',
63+
`${variantAnalysisSummary.id}`,
64+
scannedRepo.repository.full_name
65+
);
66+
67+
const storagePath = path.join(
68+
resultDirectory,
69+
scannedRepo.repository.full_name
70+
);
71+
72+
const result = await ghApiClient.getVariantAnalysisRepoResult(
73+
credentials,
74+
repoTask.artifact_url
75+
);
76+
77+
fs.mkdirSync(resultDirectory, { recursive: true });
78+
await fs.writeFile(storagePath, JSON.stringify(result, null, 2), 'utf8');
79+
}
80+
}
81+
}

extensions/ql-vscode/src/remote-queries/variant-analysis-monitor.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import * as vscode from 'vscode';
1+
import { ExtensionContext, CancellationToken, commands } from 'vscode';
22
import { Credentials } from '../authentication';
33
import { Logger } from '../logging';
44
import * as ghApiClient from './gh-api/gh-api-client';
@@ -17,14 +17,14 @@ export class VariantAnalysisMonitor {
1717
public static sleepTime = 5000;
1818

1919
constructor(
20-
private readonly extensionContext: vscode.ExtensionContext,
20+
private readonly extensionContext: ExtensionContext,
2121
private readonly logger: Logger
2222
) {
2323
}
2424

2525
public async monitorVariantAnalysis(
2626
variantAnalysis: VariantAnalysis,
27-
cancellationToken: vscode.CancellationToken
27+
cancellationToken: CancellationToken
2828
): Promise<VariantAnalysisMonitorResult> {
2929

3030
const credentials = await Credentials.initialize(this.extensionContext);
@@ -64,6 +64,7 @@ export class VariantAnalysisMonitor {
6464
if (variantAnalysisSummary.scanned_repositories) {
6565
variantAnalysisSummary.scanned_repositories.forEach(scannedRepo => {
6666
if (!scannedReposDownloaded.includes(scannedRepo.repository.id) && scannedRepo.analysis_status === 'succeeded') {
67+
void commands.executeCommand('codeQL.autoDownloadVariantAnalysisResult', scannedRepo, variantAnalysisSummary);
6768
scannedReposDownloaded.push(scannedRepo.repository.id);
6869
}
6970
});

extensions/ql-vscode/src/vscode-tests/cli-integration/remote-queries/run-remote-query.test.ts

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { assert, expect } from 'chai';
22
import * as path from 'path';
33
import * as sinon from 'sinon';
4-
import { CancellationToken, extensions, QuickPickItem, Uri, window } from 'vscode';
4+
import { CancellationTokenSource, extensions, QuickPickItem, Uri, window } from 'vscode';
55
import * as fs from 'fs-extra';
66
import * as os from 'os';
77
import * as yaml from 'js-yaml';
@@ -32,7 +32,7 @@ describe('Remote queries', function() {
3232

3333
let cli: CodeQLCliServer;
3434
let credentials: Credentials = {} as unknown as Credentials;
35-
let token: CancellationToken;
35+
let cancellationTokenSource: CancellationTokenSource;
3636
let progress: sinon.SinonSpy;
3737
let showQuickPickSpy: sinon.SinonStub;
3838
let getRepositoryFromNwoStub: sinon.SinonStub;
@@ -55,9 +55,15 @@ describe('Remote queries', function() {
5555
this.skip();
5656
}
5757
credentials = {} as unknown as Credentials;
58-
token = {
59-
isCancellationRequested: false
60-
} as unknown as CancellationToken;
58+
59+
cancellationTokenSource = {
60+
token: {
61+
isCancellationRequested: false,
62+
onCancellationRequested: sandbox.stub()
63+
},
64+
cancel: sandbox.stub(),
65+
dispose: sandbox.stub()
66+
};
6167

6268
progress = sandbox.spy();
6369
// Should not have asked for a language
@@ -88,7 +94,7 @@ describe('Remote queries', function() {
8894
it('should run a remote query that is part of a qlpack', async () => {
8995
const fileUri = getFile('data-remote-qlpack/in-pack.ql');
9096

91-
const querySubmissionResult = await runRemoteQuery(cli, credentials, fileUri, true, progress, token);
97+
const querySubmissionResult = await runRemoteQuery(cli, credentials, fileUri, true, progress, cancellationTokenSource.token);
9298
expect(querySubmissionResult).to.be.ok;
9399
const queryPackRootDir = querySubmissionResult!.queryDirPath!;
94100
printDirectoryContents(queryPackRootDir);
@@ -149,7 +155,7 @@ describe('Remote queries', function() {
149155
it('should run a remote query that is not part of a qlpack', async () => {
150156
const fileUri = getFile('data-remote-no-qlpack/in-pack.ql');
151157

152-
const querySubmissionResult = await runRemoteQuery(cli, credentials, fileUri, true, progress, token);
158+
const querySubmissionResult = await runRemoteQuery(cli, credentials, fileUri, true, progress, cancellationTokenSource.token);
153159
expect(querySubmissionResult).to.be.ok;
154160
const queryPackRootDir = querySubmissionResult!.queryDirPath!;
155161

@@ -212,7 +218,7 @@ describe('Remote queries', function() {
212218
it('should run a remote query that is nested inside a qlpack', async () => {
213219
const fileUri = getFile('data-remote-qlpack-nested/subfolder/in-pack.ql');
214220

215-
const querySubmissionResult = await runRemoteQuery(cli, credentials, fileUri, true, progress, token);
221+
const querySubmissionResult = await runRemoteQuery(cli, credentials, fileUri, true, progress, cancellationTokenSource.token);
216222
expect(querySubmissionResult).to.be.ok;
217223
const queryPackRootDir = querySubmissionResult!.queryDirPath!;
218224

@@ -274,9 +280,9 @@ describe('Remote queries', function() {
274280
it('should cancel a run before uploading', async () => {
275281
const fileUri = getFile('data-remote-no-qlpack/in-pack.ql');
276282

277-
const promise = runRemoteQuery(cli, credentials, fileUri, true, progress, token);
283+
const promise = runRemoteQuery(cli, credentials, fileUri, true, progress, cancellationTokenSource.token);
278284

279-
token.isCancellationRequested = true;
285+
cancellationTokenSource.token.isCancellationRequested = true;
280286

281287
try {
282288
await promise;
@@ -300,7 +306,7 @@ describe('Remote queries', function() {
300306
it('should run a variant analysis that is part of a qlpack', async () => {
301307
const fileUri = getFile('data-remote-qlpack/in-pack.ql');
302308

303-
const querySubmissionResult = await runRemoteQuery(cli, credentials, fileUri, true, progress, token);
309+
const querySubmissionResult = await runRemoteQuery(cli, credentials, fileUri, true, progress, cancellationTokenSource.token);
304310
expect(querySubmissionResult).to.be.ok;
305311
const variantAnalysis = querySubmissionResult!.variantAnalysis!;
306312
expect(variantAnalysis.id).to.be.equal(mockApiResponse.id);
@@ -313,7 +319,7 @@ describe('Remote queries', function() {
313319
it('should run a remote query that is not part of a qlpack', async () => {
314320
const fileUri = getFile('data-remote-no-qlpack/in-pack.ql');
315321

316-
const querySubmissionResult = await runRemoteQuery(cli, credentials, fileUri, true, progress, token);
322+
const querySubmissionResult = await runRemoteQuery(cli, credentials, fileUri, true, progress, cancellationTokenSource.token);
317323
expect(querySubmissionResult).to.be.ok;
318324
const variantAnalysis = querySubmissionResult!.variantAnalysis!;
319325
expect(variantAnalysis.id).to.be.equal(mockApiResponse.id);
@@ -326,7 +332,7 @@ describe('Remote queries', function() {
326332
it('should run a remote query that is nested inside a qlpack', async () => {
327333
const fileUri = getFile('data-remote-qlpack-nested/subfolder/in-pack.ql');
328334

329-
const querySubmissionResult = await runRemoteQuery(cli, credentials, fileUri, true, progress, token);
335+
const querySubmissionResult = await runRemoteQuery(cli, credentials, fileUri, true, progress, cancellationTokenSource.token);
330336
expect(querySubmissionResult).to.be.ok;
331337
const variantAnalysis = querySubmissionResult!.variantAnalysis!;
332338
expect(variantAnalysis.id).to.be.equal(mockApiResponse.id);
@@ -339,9 +345,9 @@ describe('Remote queries', function() {
339345
it('should cancel a run before uploading', async () => {
340346
const fileUri = getFile('data-remote-no-qlpack/in-pack.ql');
341347

342-
const promise = runRemoteQuery(cli, credentials, fileUri, true, progress, token);
348+
const promise = runRemoteQuery(cli, credentials, fileUri, true, progress, cancellationTokenSource.token);
343349

344-
token.isCancellationRequested = true;
350+
cancellationTokenSource.token.isCancellationRequested = true;
345351

346352
try {
347353
await promise;

0 commit comments

Comments
 (0)