Skip to content

Commit 5a9b49b

Browse files
authored
Show remote analyses results status (#1108)
1 parent 0672133 commit 5a9b49b

14 files changed

Lines changed: 1462 additions & 112 deletions

extensions/ql-vscode/package-lock.json

Lines changed: 1179 additions & 71 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

extensions/ql-vscode/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,6 +1005,7 @@
10051005
},
10061006
"dependencies": {
10071007
"@octokit/rest": "^18.5.6",
1008+
"@primer/react": "^34.3.0",
10081009
"child-process-promise": "^2.2.1",
10091010
"classnames": "~2.2.6",
10101011
"fs-extra": "^9.0.1",
@@ -1019,6 +1020,7 @@
10191020
"stream": "^0.0.2",
10201021
"stream-chain": "~2.2.4",
10211022
"stream-json": "~1.7.3",
1023+
"styled-components": "^5.3.3",
10221024
"tmp": "^0.1.0",
10231025
"tmp-promise": "~3.0.2",
10241026
"tree-kill": "~1.2.2",

extensions/ql-vscode/src/compare/compare-interface.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,8 @@ export class CompareInterfaceManager extends DisposableObject {
137137
panel.webview.html = getHtmlForWebview(
138138
panel.webview,
139139
scriptPathOnDisk,
140-
[stylesheetPathOnDisk]
140+
[stylesheetPathOnDisk],
141+
false
141142
);
142143
this.push(panel.webview.onDidReceiveMessage(
143144
async (e) => this.handleMsgFromView(e),

extensions/ql-vscode/src/extension.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ import { RemoteQuery } from './remote-queries/remote-query';
8383
import { RemoteQueryResult } from './remote-queries/remote-query-result';
8484
import { URLSearchParams } from 'url';
8585
import { RemoteQueriesInterfaceManager } from './remote-queries/remote-queries-interface';
86-
import { sampleRemoteQuery, sampleRemoteQueryResult } from './remote-queries/sample-data';
86+
import * as sampleData from './remote-queries/sample-data';
8787
import { handleDownloadPacks, handleInstallPackDependencies } from './packaging';
8888
import { AnalysesResultsManager } from './remote-queries/analyses-results-manager';
8989

@@ -839,7 +839,11 @@ async function activateWithInstalledDistribution(
839839
commandRunner('codeQL.showFakeRemoteQueryResults', async () => {
840840
const analysisResultsManager = new AnalysesResultsManager(ctx, logger);
841841
const rqim = new RemoteQueriesInterfaceManager(ctx, logger, analysisResultsManager);
842-
await rqim.showResults(sampleRemoteQuery, sampleRemoteQueryResult);
842+
await rqim.showResults(sampleData.sampleRemoteQuery, sampleData.sampleRemoteQueryResult);
843+
844+
await rqim.setAnalysisResults(sampleData.sampleAnalysesResultsStage1);
845+
await rqim.setAnalysisResults(sampleData.sampleAnalysesResultsStage2);
846+
await rqim.setAnalysisResults(sampleData.sampleAnalysesResultsStage3);
843847
}));
844848

845849
ctx.subscriptions.push(

extensions/ql-vscode/src/interface-utils.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ export function getHtmlForWebview(
119119
webview: Webview,
120120
scriptUriOnDisk: Uri,
121121
stylesheetUrisOnDisk: Uri[],
122+
allowInlineStyles: boolean
122123
): string {
123124
// Convert the on-disk URIs into webview URIs.
124125
const scriptWebviewUri = webview.asWebviewUri(scriptUriOnDisk);
@@ -128,8 +129,13 @@ export function getHtmlForWebview(
128129
// Use a nonce in the content security policy to uniquely identify the above resources.
129130
const nonce = getNonce();
130131

131-
const stylesheetsHtmlLines = stylesheetWebviewUris.map(stylesheetWebviewUri =>
132-
`<link nonce="${nonce}" rel="stylesheet" href="${stylesheetWebviewUri}">`);
132+
const stylesheetsHtmlLines = allowInlineStyles
133+
? stylesheetWebviewUris.map(uri => createStylesLinkWithoutNonce(uri))
134+
: stylesheetWebviewUris.map(uri => createStylesLinkWithNonce(nonce, uri));
135+
136+
const styleSrc = allowInlineStyles
137+
? 'https://*.vscode-webview.net/ vscode-file: \'unsafe-inline\''
138+
: `'nonce-${nonce}'`;
133139

134140
/*
135141
* Content security policy:
@@ -143,7 +149,7 @@ export function getHtmlForWebview(
143149
<html>
144150
<head>
145151
<meta http-equiv="Content-Security-Policy"
146-
content="default-src 'none'; script-src 'nonce-${nonce}'; style-src 'nonce-${nonce}'; connect-src ${webview.cspSource};">
152+
content="default-src 'none'; script-src 'nonce-${nonce}'; style-src ${styleSrc}; connect-src ${webview.cspSource};">
147153
${stylesheetsHtmlLines.join(` ${os.EOL}`)}
148154
</head>
149155
<body>
@@ -243,3 +249,11 @@ export async function jumpToLocation(
243249
}
244250
}
245251
}
252+
253+
function createStylesLinkWithNonce(nonce: string, uri: Uri): string {
254+
return `<link nonce="${nonce}" rel="stylesheet" href="${uri}">`;
255+
}
256+
257+
function createStylesLinkWithoutNonce(uri: Uri): string {
258+
return `<link rel="stylesheet" href="${uri}">`;
259+
}

extensions/ql-vscode/src/interface.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,8 @@ export class InterfaceManager extends DisposableObject {
194194
panel.webview.html = getHtmlForWebview(
195195
panel.webview,
196196
scriptPathOnDisk,
197-
[stylesheetPathOnDisk]
197+
[stylesheetPathOnDisk],
198+
false
198199
);
199200
this.push(panel.webview.onDidReceiveMessage(
200201
async (e) => this.handleMsgFromView(e),

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

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import * as path from 'path';
66
import { AnalysisSummary } from './shared/remote-query-result';
77
import { AnalysisResults, QueryResult } from './shared/analysis-result';
88
import { UserCancellationException } from '../commandRunner';
9+
import * as os from 'os';
910
import { sarifParser } from '../sarif-parser';
1011

1112
export class AnalysesResultsManager {
@@ -32,8 +33,7 @@ export class AnalysesResultsManager {
3233

3334
void this.logger.log(`Downloading and processing results for ${analysisSummary.nwo}`);
3435

35-
await this.downloadSingleAnalysisResults(analysisSummary, credentials);
36-
await publishResults(this.analysesResults);
36+
await this.downloadSingleAnalysisResults(analysisSummary, credentials, publishResults);
3737
}
3838

3939
public async downloadAnalysesResults(
@@ -47,21 +47,30 @@ export class AnalysesResultsManager {
4747

4848
const batchSize = 3;
4949
const numOfBatches = Math.ceil(analysesToDownload.length / batchSize);
50+
const allFailures = [];
5051

5152
for (let i = 0; i < analysesToDownload.length; i += batchSize) {
5253
if (token?.isCancellationRequested) {
5354
throw new UserCancellationException('Downloading of analyses results has been cancelled', true);
5455
}
5556

5657
const batch = analysesToDownload.slice(i, i + batchSize);
57-
const batchTasks = batch.map(analysis => this.downloadSingleAnalysisResults(analysis, credentials));
58+
const batchTasks = batch.map(analysis => this.downloadSingleAnalysisResults(analysis, credentials, publishResults));
5859

5960
const nwos = batch.map(a => a.nwo).join(', ');
6061
void this.logger.log(`Downloading batch ${Math.floor(i / batchSize) + 1} of ${numOfBatches} (${nwos})`);
6162

62-
await Promise.all(batchTasks);
63+
const taskResults = await Promise.allSettled(batchTasks);
64+
const failedTasks = taskResults.filter(x => x.status === 'rejected') as Array<PromiseRejectedResult>;
65+
if (failedTasks.length > 0) {
66+
const failures = failedTasks.map(t => t.reason.message);
67+
failures.forEach(f => void this.logger.log(f));
68+
allFailures.push(...failures);
69+
}
70+
}
6371

64-
await publishResults(this.analysesResults);
72+
if (allFailures.length > 0) {
73+
throw Error(allFailures.join(os.EOL));
6574
}
6675
}
6776

@@ -71,21 +80,36 @@ export class AnalysesResultsManager {
7180

7281
private async downloadSingleAnalysisResults(
7382
analysis: AnalysisSummary,
74-
credentials: Credentials
83+
credentials: Credentials,
84+
publishResults: (analysesResults: AnalysisResults[]) => Promise<void>
7585
): Promise<void> {
76-
const artifactPath = await downloadArtifactFromLink(credentials, analysis.downloadLink);
86+
const analysisResults: AnalysisResults = {
87+
nwo: analysis.nwo,
88+
status: 'InProgress',
89+
results: []
90+
};
91+
92+
this.analysesResults.push(analysisResults);
93+
void publishResults(this.analysesResults);
7794

78-
let analysisResults: AnalysisResults;
95+
let artifactPath;
96+
try {
97+
artifactPath = await downloadArtifactFromLink(credentials, analysis.downloadLink);
98+
}
99+
catch (e) {
100+
throw new Error(`Could not download the analysis results for ${analysis.nwo}: ${e.message}`);
101+
}
79102

80103
if (path.extname(artifactPath) === '.sarif') {
81104
const queryResults = await this.readResults(artifactPath);
82-
analysisResults = { nwo: analysis.nwo, results: queryResults };
105+
analysisResults.results = queryResults;
106+
analysisResults.status = 'Completed';
83107
} else {
84108
void this.logger.log('Cannot download results. Only alert and path queries are fully supported.');
85-
analysisResults = { nwo: analysis.nwo, results: [] };
109+
analysisResults.status = 'Failed';
86110
}
87111

88-
this.analysesResults.push(analysisResults);
112+
void publishResults(this.analysesResults);
89113
}
90114

91115
private async readResults(filePath: string): Promise<QueryResult[]> {

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,8 @@ export class RemoteQueriesInterfaceManager {
125125
panel.webview.html = getHtmlForWebview(
126126
panel.webview,
127127
scriptPathOnDisk,
128-
[baseStylesheetUriOnDisk, stylesheetPathOnDisk]
128+
[baseStylesheetUriOnDisk, stylesheetPathOnDisk],
129+
true
129130
);
130131
ctx.subscriptions.push(
131132
panel.webview.onDidReceiveMessage(

extensions/ql-vscode/src/remote-queries/sample-data.ts

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { RemoteQuery } from './remote-query';
22
import { RemoteQueryResult } from './remote-query-result';
3+
import { AnalysisResults } from './shared/analysis-result';
34

45
export const sampleRemoteQuery: RemoteQuery = {
56
queryName: 'Inefficient regular expression',
@@ -84,3 +85,95 @@ export const sampleRemoteQueryResult: RemoteQueryResult = {
8485
}
8586
]
8687
};
88+
89+
90+
const createAnalysisResults = (n: number) => Array(n).fill({ 'message': 'Sample text' });
91+
92+
export const sampleAnalysesResultsStage1: AnalysisResults[] = [
93+
{
94+
nwo: 'big-corp/repo1',
95+
status: 'InProgress',
96+
results: []
97+
},
98+
{
99+
nwo: 'big-corp/repo2',
100+
status: 'InProgress',
101+
results: []
102+
103+
},
104+
{
105+
nwo: 'big-corp/repo3',
106+
status: 'InProgress',
107+
results: []
108+
},
109+
// No entries for repo4
110+
];
111+
112+
export const sampleAnalysesResultsStage2: AnalysisResults[] = [
113+
{
114+
nwo: 'big-corp/repo1',
115+
status: 'Completed',
116+
results: createAnalysisResults(85)
117+
},
118+
{
119+
nwo: 'big-corp/repo2',
120+
status: 'Completed',
121+
results: createAnalysisResults(20)
122+
},
123+
{
124+
nwo: 'big-corp/repo3',
125+
status: 'InProgress',
126+
results: []
127+
},
128+
{
129+
nwo: 'big-corp/repo4',
130+
status: 'InProgress',
131+
results: []
132+
},
133+
];
134+
135+
export const sampleAnalysesResultsStage3: AnalysisResults[] = [
136+
{
137+
nwo: 'big-corp/repo1',
138+
status: 'Completed',
139+
results: createAnalysisResults(85)
140+
},
141+
{
142+
nwo: 'big-corp/repo2',
143+
status: 'Completed',
144+
results: createAnalysisResults(20)
145+
},
146+
{
147+
nwo: 'big-corp/repo3',
148+
status: 'Completed',
149+
results: createAnalysisResults(8)
150+
},
151+
{
152+
nwo: 'big-corp/repo4',
153+
status: 'Completed',
154+
results: createAnalysisResults(3)
155+
},
156+
];
157+
158+
export const sampleAnalysesResultsWithFailure: AnalysisResults[] = [
159+
{
160+
nwo: 'big-corp/repo1',
161+
status: 'Completed',
162+
results: createAnalysisResults(85)
163+
},
164+
{
165+
nwo: 'big-corp/repo2',
166+
status: 'Completed',
167+
results: createAnalysisResults(20)
168+
},
169+
{
170+
nwo: 'big-corp/repo3',
171+
status: 'Failed',
172+
results: []
173+
},
174+
{
175+
nwo: 'big-corp/repo4',
176+
status: 'Completed',
177+
results: createAnalysisResults(3)
178+
},
179+
];

extensions/ql-vscode/src/remote-queries/shared/analysis-result.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
export type AnalysisResultStatus = 'InProgress' | 'Completed' | 'Failed';
2+
13
export interface AnalysisResults {
24
nwo: string;
5+
status: AnalysisResultStatus;
36
results: QueryResult[];
47
}
58

0 commit comments

Comments
 (0)