Skip to content

Commit f8690bc

Browse files
authored
Auto-download analyses results (#1098)
1 parent b0410ec commit f8690bc

4 files changed

Lines changed: 83 additions & 20 deletions

File tree

extensions/ql-vscode/src/extension.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ import { CodeQlStatusBarHandler } from './status-bar';
8080
import { Credentials } from './authentication';
8181
import { RemoteQueriesManager } from './remote-queries/remote-queries-manager';
8282
import { RemoteQuery } from './remote-queries/remote-query';
83+
import { RemoteQueryResult } from './remote-queries/remote-query-result';
8384
import { URLSearchParams } from 'url';
8485
import { RemoteQueriesInterfaceManager } from './remote-queries/remote-queries-interface';
8586
import { sampleRemoteQuery, sampleRemoteQueryResult } from './remote-queries/sample-data';
@@ -778,7 +779,7 @@ async function activateWithInstalledDistribution(
778779
);
779780

780781
void logger.log('Initializing remote queries interface.');
781-
const rqm = new RemoteQueriesManager(ctx, logger, cliServer);
782+
const rqm = new RemoteQueriesManager(ctx, cliServer, logger);
782783

783784
registerRemoteQueryTextProvider();
784785

@@ -816,6 +817,13 @@ async function activateWithInstalledDistribution(
816817
await rqm.monitorRemoteQuery(query, token);
817818
}));
818819

820+
ctx.subscriptions.push(
821+
commandRunner('codeQL.autoDownloadRemoteQueryResults', async (
822+
queryResult: RemoteQueryResult,
823+
token: CancellationToken) => {
824+
await rqm.autoDownloadRemoteQueryResults(queryResult, token);
825+
}));
826+
819827
ctx.subscriptions.push(
820828
commandRunner('codeQL.showFakeRemoteQueryResults', async () => {
821829
const analysisResultsManager = new AnalysesResultsManager(ctx, logger);

extensions/ql-vscode/src/remote-queries/analyses-results-manager.ts

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ExtensionContext } from 'vscode';
1+
import { CancellationToken, ExtensionContext } from 'vscode';
22
import { Credentials } from '../authentication';
33
import { Logger } from '../logging';
44
import { downloadArtifactFromLink } from './gh-actions-api-client';
@@ -7,6 +7,7 @@ import * as fs from 'fs-extra';
77
import { AnalysisSummary } from './shared/remote-query-result';
88
import * as sarif from 'sarif';
99
import { AnalysisResults, QueryResult } from './shared/analysis-result';
10+
import { UserCancellationException } from '../commandRunner';
1011

1112
export class AnalysesResultsManager {
1213
// Store for the results of various analyses for a single remote query.
@@ -21,6 +22,7 @@ export class AnalysesResultsManager {
2122

2223
public async downloadAnalysisResults(
2324
analysisSummary: AnalysisSummary,
25+
publishResults: (analysesResults: AnalysisResults[]) => Promise<void>
2426
): Promise<void> {
2527
if (this.analysesResults.some(x => x.nwo === analysisSummary.nwo)) {
2628
// We already have the results for this analysis, don't download again.
@@ -32,17 +34,35 @@ export class AnalysesResultsManager {
3234
void this.logger.log(`Downloading and processing results for ${analysisSummary.nwo}`);
3335

3436
await this.downloadSingleAnalysisResults(analysisSummary, credentials);
37+
await publishResults(this.analysesResults);
3538
}
3639

37-
public async downloadAllResults(
38-
analysisSummaries: AnalysisSummary[],
40+
public async downloadAnalysesResults(
41+
analysesToDownload: AnalysisSummary[],
42+
token: CancellationToken | undefined,
43+
publishResults: (analysesResults: AnalysisResults[]) => Promise<void>
3944
): Promise<void> {
4045
const credentials = await Credentials.initialize(this.ctx);
4146

42-
void this.logger.log('Downloading and processing all results');
47+
void this.logger.log('Downloading and processing analyses results');
4348

44-
for (const analysis of analysisSummaries) {
45-
await this.downloadSingleAnalysisResults(analysis, credentials);
49+
const batchSize = 3;
50+
const numOfBatches = Math.ceil(analysesToDownload.length / batchSize);
51+
52+
for (let i = 0; i < analysesToDownload.length; i += batchSize) {
53+
if (token?.isCancellationRequested) {
54+
throw new UserCancellationException('Downloading of analyses results has been cancelled', true);
55+
}
56+
57+
const batch = analysesToDownload.slice(i, i + batchSize);
58+
const batchTasks = batch.map(analysis => this.downloadSingleAnalysisResults(analysis, credentials));
59+
60+
const nwos = batch.map(a => a.nwo).join(', ');
61+
void this.logger.log(`Downloading batch ${Math.floor(i / batchSize) + 1} of ${numOfBatches} (${nwos})`);
62+
63+
await Promise.all(batchTasks);
64+
65+
await publishResults(this.analysesResults);
4666
}
4767
}
4868

extensions/ql-vscode/src/remote-queries/remote-queries-interface.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ export class RemoteQueriesInterfaceManager {
5151
t: 'setRemoteQueryResult',
5252
queryResult: this.buildViewModel(query, queryResult)
5353
});
54+
55+
await this.setAnalysisResults(this.analysesResultsManager.getAnalysesResults());
5456
}
5557

5658
/**
@@ -201,20 +203,25 @@ export class RemoteQueriesInterfaceManager {
201203
}
202204

203205
private async downloadAnalysisResults(msg: RemoteQueryDownloadAnalysisResultsMessage): Promise<void> {
204-
await this.analysesResultsManager.downloadAnalysisResults(msg.analysisSummary);
205-
await this.setAnalysisResults(this.analysesResultsManager.getAnalysesResults());
206+
await this.analysesResultsManager.downloadAnalysisResults(
207+
msg.analysisSummary,
208+
results => this.setAnalysisResults(results));
206209
}
207210

208211
private async downloadAllAnalysesResults(msg: RemoteQueryDownloadAllAnalysesResultsMessage): Promise<void> {
209-
await this.analysesResultsManager.downloadAllResults(msg.analysisSummaries);
210-
await this.setAnalysisResults(this.analysesResultsManager.getAnalysesResults());
212+
await this.analysesResultsManager.downloadAnalysesResults(
213+
msg.analysisSummaries,
214+
undefined,
215+
results => this.setAnalysisResults(results));
211216
}
212217

213-
private async setAnalysisResults(analysesResults: AnalysisResults[]): Promise<void> {
214-
await this.postMessage({
215-
t: 'setAnalysesResults',
216-
analysesResults: analysesResults
217-
});
218+
public async setAnalysisResults(analysesResults: AnalysisResults[]): Promise<void> {
219+
if (this.panel?.active) {
220+
await this.postMessage({
221+
t: 'setAnalysesResults',
222+
analysesResults: analysesResults
223+
});
224+
}
218225
}
219226

220227
private postMessage(msg: ToRemoteQueriesMessage): Thenable<boolean> {

extensions/ql-vscode/src/remote-queries/remote-queries-manager.ts

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,21 @@ import { RemoteQueryResult } from './remote-query-result';
1414
import { DownloadLink } from './download-link';
1515
import { AnalysesResultsManager } from './analyses-results-manager';
1616

17+
const autoDownloadMaxSize = 300 * 1024;
18+
const autoDownloadMaxCount = 100;
19+
1720
export class RemoteQueriesManager {
1821
private readonly remoteQueriesMonitor: RemoteQueriesMonitor;
1922
private readonly analysesResultsManager: AnalysesResultsManager;
23+
private readonly interfaceManager: RemoteQueriesInterfaceManager;
2024

2125
constructor(
2226
private readonly ctx: ExtensionContext,
23-
private readonly logger: Logger,
24-
private readonly cliServer: CodeQLCliServer
27+
private readonly cliServer: CodeQLCliServer,
28+
logger: Logger,
2529
) {
2630
this.analysesResultsManager = new AnalysesResultsManager(ctx, logger);
31+
this.interfaceManager = new RemoteQueriesInterfaceManager(ctx, logger, this.analysesResultsManager);
2732
this.remoteQueriesMonitor = new RemoteQueriesMonitor(ctx, logger);
2833
}
2934

@@ -65,13 +70,16 @@ export class RemoteQueriesManager {
6570

6671
const queryResult = this.mapQueryResult(executionEndTime, resultIndex);
6772

73+
// Kick off auto-download of results.
74+
void commands.executeCommand('codeQL.autoDownloadRemoteQueryResults', queryResult);
75+
6876
const totalResultCount = queryResult.analysisSummaries.reduce((acc, cur) => acc + cur.resultCount, 0);
6977
const message = `Query "${query.queryName}" run on ${query.repositories.length} repositories and returned ${totalResultCount} results`;
7078

7179
const shouldOpenView = await showInformationMessageWithAction(message, 'View');
7280
if (shouldOpenView) {
73-
const rqim = new RemoteQueriesInterfaceManager(this.ctx, this.logger, this.analysesResultsManager);
74-
await rqim.showResults(query, queryResult);
81+
await this.interfaceManager.showResults(query, queryResult);
82+
7583
}
7684
} else if (queryResult.status === 'CompletedUnsuccessfully') {
7785
await showAndLogErrorMessage(`Remote query execution failed. Error: ${queryResult.error}`);
@@ -81,6 +89,26 @@ export class RemoteQueriesManager {
8189
}
8290
}
8391

92+
public async autoDownloadRemoteQueryResults(
93+
queryResult: RemoteQueryResult,
94+
token: CancellationToken
95+
): Promise<void> {
96+
const analysesToDownload = queryResult.analysisSummaries
97+
.filter(a => a.fileSizeInBytes < autoDownloadMaxSize)
98+
.slice(0, autoDownloadMaxCount)
99+
.map(a => ({
100+
nwo: a.nwo,
101+
resultCount: a.resultCount,
102+
downloadLink: a.downloadLink,
103+
fileSize: String(a.fileSizeInBytes)
104+
}));
105+
106+
await this.analysesResultsManager.downloadAnalysesResults(
107+
analysesToDownload,
108+
token,
109+
results => this.interfaceManager.setAnalysisResults(results));
110+
}
111+
84112
private mapQueryResult(executionEndTime: Date, resultIndex: RemoteQueryResultIndex): RemoteQueryResult {
85113
const analysisSummaries = resultIndex.items.map(item => ({
86114
nwo: item.nwo,

0 commit comments

Comments
 (0)