Skip to content

Commit 1216fce

Browse files
authored
Download and process analyses results (#1089)
1 parent c598306 commit 1216fce

File tree

7 files changed

+198
-44
lines changed

7 files changed

+198
-44
lines changed

extensions/ql-vscode/src/extension.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ import { URLSearchParams } from 'url';
8484
import { RemoteQueriesInterfaceManager } from './remote-queries/remote-queries-interface';
8585
import { sampleRemoteQuery, sampleRemoteQueryResult } from './remote-queries/sample-data';
8686
import { handleDownloadPacks, handleInstallPackDependencies } from './packaging';
87+
import { AnalysesResultsManager } from './remote-queries/analyses-results-manager';
8788

8889
/**
8990
* extension.ts
@@ -817,7 +818,8 @@ async function activateWithInstalledDistribution(
817818

818819
ctx.subscriptions.push(
819820
commandRunner('codeQL.showFakeRemoteQueryResults', async () => {
820-
const rqim = new RemoteQueriesInterfaceManager(ctx, logger);
821+
const analysisResultsManager = new AnalysesResultsManager(ctx, logger);
822+
const rqim = new RemoteQueriesInterfaceManager(ctx, logger, analysisResultsManager);
821823
await rqim.showResults(sampleRemoteQuery, sampleRemoteQueryResult);
822824
}));
823825

extensions/ql-vscode/src/pure/interface-types.ts

+11-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as sarif from 'sarif';
2-
import { DownloadLink } from '../remote-queries/download-link';
3-
import { RemoteQueryResult } from '../remote-queries/shared/remote-query-result';
2+
import { AnalysisResults } from '../remote-queries/shared/analysis-result';
3+
import { AnalysisSummary, RemoteQueryResult } from '../remote-queries/shared/remote-query-result';
44
import { RawResultSet, ResultRow, ResultSetSchema, Column, ResolvableLocationValue } from './bqrs-cli-types';
55

66
/**
@@ -381,7 +381,8 @@ export type FromRemoteQueriesMessage =
381381
| RemoteQueryDownloadAllAnalysesResultsMessage;
382382

383383
export type ToRemoteQueriesMessage =
384-
| SetRemoteQueryResultMessage;
384+
| SetRemoteQueryResultMessage
385+
| SetAnalysesResultsMessage;
385386

386387
export interface RemoteQueryLoadedMessage {
387388
t: 'remoteQueryLoaded';
@@ -392,18 +393,22 @@ export interface SetRemoteQueryResultMessage {
392393
queryResult: RemoteQueryResult
393394
}
394395

396+
export interface SetAnalysesResultsMessage {
397+
t: 'setAnalysesResults';
398+
analysesResults: AnalysisResults[];
399+
}
400+
395401
export interface RemoteQueryErrorMessage {
396402
t: 'remoteQueryError';
397403
error: string;
398404
}
399405

400406
export interface RemoteQueryDownloadAnalysisResultsMessage {
401407
t: 'remoteQueryDownloadAnalysisResults';
402-
nwo: string
403-
downloadLink: DownloadLink;
408+
analysisSummary: AnalysisSummary
404409
}
405410

406411
export interface RemoteQueryDownloadAllAnalysesResultsMessage {
407412
t: 'remoteQueryDownloadAllAnalysesResults';
408-
downloadLink: DownloadLink;
413+
analysisSummaries: AnalysisSummary[];
409414
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { ExtensionContext } from 'vscode';
2+
import { Credentials } from '../authentication';
3+
import { Logger } from '../logging';
4+
import { downloadArtifactFromLink } from './gh-actions-api-client';
5+
import * as path from 'path';
6+
import * as fs from 'fs-extra';
7+
import { AnalysisSummary } from './shared/remote-query-result';
8+
import * as sarif from 'sarif';
9+
import { AnalysisResults, QueryResult } from './shared/analysis-result';
10+
11+
export class AnalysesResultsManager {
12+
// Store for the results of various analyses for a single remote query.
13+
private readonly analysesResults: AnalysisResults[];
14+
15+
constructor(
16+
private readonly ctx: ExtensionContext,
17+
private readonly logger: Logger,
18+
) {
19+
this.analysesResults = [];
20+
}
21+
22+
public async downloadAnalysisResults(
23+
analysisSummary: AnalysisSummary,
24+
): Promise<void> {
25+
if (this.analysesResults.some(x => x.nwo === analysisSummary.nwo)) {
26+
// We already have the results for this analysis, don't download again.
27+
return;
28+
}
29+
30+
const credentials = await Credentials.initialize(this.ctx);
31+
32+
void this.logger.log(`Downloading and processing results for ${analysisSummary.nwo}`);
33+
34+
await this.downloadSingleAnalysisResults(analysisSummary, credentials);
35+
}
36+
37+
public async downloadAllResults(
38+
analysisSummaries: AnalysisSummary[],
39+
): Promise<void> {
40+
const credentials = await Credentials.initialize(this.ctx);
41+
42+
void this.logger.log('Downloading and processing all results');
43+
44+
for (const analysis of analysisSummaries) {
45+
await this.downloadSingleAnalysisResults(analysis, credentials);
46+
}
47+
}
48+
49+
public getAnalysesResults(): AnalysisResults[] {
50+
return [...this.analysesResults];
51+
}
52+
53+
private async downloadSingleAnalysisResults(
54+
analysis: AnalysisSummary,
55+
credentials: Credentials
56+
): Promise<void> {
57+
const artifactPath = await downloadArtifactFromLink(credentials, analysis.downloadLink);
58+
59+
let analysisResults: AnalysisResults;
60+
61+
if (path.extname(artifactPath) === '.sarif') {
62+
const queryResults = await this.readResults(artifactPath);
63+
analysisResults = { nwo: analysis.nwo, results: queryResults };
64+
} else {
65+
void this.logger.log('Cannot download results. Only alert and path queries are fully supported.');
66+
analysisResults = { nwo: analysis.nwo, results: [] };
67+
}
68+
69+
this.analysesResults.push(analysisResults);
70+
}
71+
72+
private async readResults(filePath: string): Promise<QueryResult[]> {
73+
const queryResults: QueryResult[] = [];
74+
75+
const sarifContents = await fs.readFile(filePath, 'utf8');
76+
const sarifLog = JSON.parse(sarifContents) as sarif.Log;
77+
78+
// Read the sarif file and extract information that we want to display
79+
// in the UI. For now we're only getting the message texts but we'll gradually
80+
// extract more information based on the UX we want to build.
81+
82+
sarifLog.runs?.forEach(run => {
83+
run?.results?.forEach(result => {
84+
if (result?.message?.text) {
85+
queryResults.push({
86+
message: result.message.text
87+
});
88+
}
89+
});
90+
});
91+
92+
return queryResults;
93+
}
94+
}

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

+24-25
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ import {
77
workspace,
88
} from 'vscode';
99
import * as path from 'path';
10-
import * as vscode from 'vscode';
11-
import * as fs from 'fs-extra';
1210

1311
import { tmpDir } from '../run-queries';
1412
import {
1513
ToRemoteQueriesMessage,
1614
FromRemoteQueriesMessage,
15+
RemoteQueryDownloadAnalysisResultsMessage,
16+
RemoteQueryDownloadAllAnalysesResultsMessage,
1717
} from '../pure/interface-types';
1818
import { Logger } from '../logging';
1919
import { getHtmlForWebview } from '../interface-utils';
@@ -22,21 +22,21 @@ import { AnalysisSummary, RemoteQueryResult } from './remote-query-result';
2222
import { RemoteQuery } from './remote-query';
2323
import { RemoteQueryResult as RemoteQueryResultViewModel } from './shared/remote-query-result';
2424
import { AnalysisSummary as AnalysisResultViewModel } from './shared/remote-query-result';
25-
import { downloadArtifactFromLink } from './gh-actions-api-client';
26-
import { Credentials } from '../authentication';
27-
import { showAndLogWarningMessage, showInformationMessageWithAction } from '../helpers';
25+
import { showAndLogWarningMessage } from '../helpers';
2826
import { URLSearchParams } from 'url';
2927
import { SHOW_QUERY_TEXT_MSG } from '../query-history';
30-
import { DownloadLink } from './download-link';
28+
import { AnalysesResultsManager } from './analyses-results-manager';
29+
import { AnalysisResults } from './shared/analysis-result';
3130

3231
export class RemoteQueriesInterfaceManager {
3332
private panel: WebviewPanel | undefined;
3433
private panelLoaded = false;
3534
private panelLoadedCallBacks: (() => void)[] = [];
3635

3736
constructor(
38-
private ctx: ExtensionContext,
39-
private logger: Logger,
37+
private readonly ctx: ExtensionContext,
38+
private readonly logger: Logger,
39+
private readonly analysesResultsManager: AnalysesResultsManager
4040
) {
4141
this.panelLoadedCallBacks.push(() => {
4242
void logger.log('Remote queries view loaded');
@@ -190,32 +190,31 @@ export class RemoteQueriesInterfaceManager {
190190
await this.openVirtualFile(msg.queryText);
191191
break;
192192
case 'remoteQueryDownloadAnalysisResults':
193-
await this.handleDownloadLinkClicked(msg.downloadLink);
193+
await this.downloadAnalysisResults(msg);
194194
break;
195195
case 'remoteQueryDownloadAllAnalysesResults':
196-
await this.handleDownloadLinkClicked(msg.downloadLink);
196+
await this.downloadAllAnalysesResults(msg);
197197
break;
198198
default:
199199
assertNever(msg);
200200
}
201201
}
202202

203-
private async handleDownloadLinkClicked(downloadLink: DownloadLink): Promise<void> {
204-
const credentials = await Credentials.initialize(this.ctx);
203+
private async downloadAnalysisResults(msg: RemoteQueryDownloadAnalysisResultsMessage): Promise<void> {
204+
await this.analysesResultsManager.downloadAnalysisResults(msg.analysisSummary);
205+
await this.setAnalysisResults(this.analysesResultsManager.getAnalysesResults());
206+
}
205207

206-
const filePath = await downloadArtifactFromLink(credentials, downloadLink);
207-
const isDir = (await fs.stat(filePath)).isDirectory();
208-
const message = `Result file saved at ${filePath}`;
209-
if (isDir) {
210-
await vscode.window.showInformationMessage(message);
211-
}
212-
else {
213-
const shouldOpenResults = await showInformationMessageWithAction(message, 'Open');
214-
if (shouldOpenResults) {
215-
const textDocument = await vscode.workspace.openTextDocument(filePath);
216-
await vscode.window.showTextDocument(textDocument, vscode.ViewColumn.One);
217-
}
218-
}
208+
private async downloadAllAnalysesResults(msg: RemoteQueryDownloadAllAnalysesResultsMessage): Promise<void> {
209+
await this.analysesResultsManager.downloadAllResults(msg.analysisSummaries);
210+
await this.setAnalysisResults(this.analysesResultsManager.getAnalysesResults());
211+
}
212+
213+
private async setAnalysisResults(analysesResults: AnalysisResults[]): Promise<void> {
214+
await this.postMessage({
215+
t: 'setAnalysesResults',
216+
analysesResults: analysesResults
217+
});
219218
}
220219

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

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

+4-1
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,18 @@ import { getRemoteQueryIndex } from './gh-actions-api-client';
1212
import { RemoteQueryResultIndex } from './remote-query-result-index';
1313
import { RemoteQueryResult } from './remote-query-result';
1414
import { DownloadLink } from './download-link';
15+
import { AnalysesResultsManager } from './analyses-results-manager';
1516

1617
export class RemoteQueriesManager {
1718
private readonly remoteQueriesMonitor: RemoteQueriesMonitor;
19+
private readonly analysesResultsManager: AnalysesResultsManager;
1820

1921
constructor(
2022
private readonly ctx: ExtensionContext,
2123
private readonly logger: Logger,
2224
private readonly cliServer: CodeQLCliServer
2325
) {
26+
this.analysesResultsManager = new AnalysesResultsManager(ctx, logger);
2427
this.remoteQueriesMonitor = new RemoteQueriesMonitor(ctx, logger);
2528
}
2629

@@ -67,7 +70,7 @@ export class RemoteQueriesManager {
6770

6871
const shouldOpenView = await showInformationMessageWithAction(message, 'View');
6972
if (shouldOpenView) {
70-
const rqim = new RemoteQueriesInterfaceManager(this.ctx, this.logger);
73+
const rqim = new RemoteQueriesInterfaceManager(this.ctx, this.logger, this.analysesResultsManager);
7174
await rqim.showResults(query, queryResult);
7275
}
7376
} else if (queryResult.status === 'CompletedUnsuccessfully') {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export interface AnalysisResults {
2+
nwo: string;
3+
results: QueryResult[];
4+
}
5+
6+
export interface QueryResult {
7+
message?: string;
8+
}

0 commit comments

Comments
 (0)