Skip to content

Commit 48ddc66

Browse files
committed
Merge branch 'aeisenberg/remote-queries-history' into aeisenberg/refactor-query-history-info
2 parents c78802a + 5bb2a76 commit 48ddc66

6 files changed

Lines changed: 79 additions & 49 deletions

File tree

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

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,14 @@ import { AnalysisResults, QueryResult } from './shared/analysis-result';
88
import { UserCancellationException } from '../commandRunner';
99
import * as os from 'os';
1010
import { sarifParser } from '../sarif-parser';
11-
import { createTimestampFile } from '../helpers';
12-
import { nanoid } from 'nanoid';
1311

1412
export class AnalysesResultsManager {
1513
// Store for the results of various analyses for a single remote query.
1614
private readonly analysesResults: AnalysisResults[];
1715

1816
constructor(
1917
private readonly ctx: ExtensionContext,
20-
private readonly storagePath: string,
18+
readonly storagePath: string,
2119
private readonly logger: Logger,
2220
) {
2321
this.analysesResults = [];
@@ -81,20 +79,6 @@ export class AnalysesResultsManager {
8179
return [...this.analysesResults];
8280
}
8381

84-
/**
85-
* Prepares a directory for storing analysis results for a single query run.
86-
* This directory initially contains only a timestamp file, which will be
87-
* used by the query history manager to determine when the directory
88-
* should be deleted.
89-
*
90-
* @param queryName The name of the query that was run.
91-
*/
92-
public async prepareDownloadDirectory(queryName: string): Promise<void> {
93-
// Prepare the storage directory.
94-
const artifactStorageDir = path.join(this.storagePath, `${queryName}-${nanoid()}`);
95-
await createTimestampFile(artifactStorageDir);
96-
}
97-
9882
private async downloadSingleAnalysisResults(
9983
analysis: AnalysisSummary,
10084
credentials: Credentials,
@@ -111,7 +95,7 @@ export class AnalysesResultsManager {
11195

11296
let artifactPath;
11397
try {
114-
artifactPath = await downloadArtifactFromLink(credentials, analysis.downloadLink, this.storagePath);
98+
artifactPath = await downloadArtifactFromLink(credentials, this.storagePath, analysis.downloadLink);
11599
}
116100
catch (e) {
117101
throw new Error(`Could not download the analysis results for ${analysis.nwo}: ${e.message}`);
Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,25 @@
11
/**
2-
* Represents a link to an artifact to be downloaded.
2+
* Represents a link to an artifact to be downloaded.
33
*/
44
export interface DownloadLink {
55
/**
6-
* A unique id of the artifact being downloaded.
6+
* A unique id of the artifact being downloaded.
77
*/
88
id: string;
99

1010
/**
1111
* The URL path to use against the GitHub API to download the
12-
* linked artifact.
12+
* linked artifact.
1313
*/
1414
urlPath: string;
1515

1616
/**
1717
* An optional path to follow inside the downloaded archive containing the artifact.
1818
*/
1919
innerFilePath?: string;
20+
21+
/**
22+
* A unique id of the remote query run. This is used to determine where to store artifacts and data from the run.
23+
*/
24+
queryId: string;
2025
}

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

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,25 +54,23 @@ export async function getRemoteQueryIndex(
5454

5555
export async function downloadArtifactFromLink(
5656
credentials: Credentials,
57-
downloadLink: DownloadLink,
58-
storagePath: string
57+
storagePath: string,
58+
downloadLink: DownloadLink
5959
): Promise<string> {
6060

6161
const octokit = await credentials.getOctokit();
6262

6363
// Download the zipped artifact.
6464
const response = await octokit.request(`GET ${downloadLink.urlPath}/zip`, {});
6565

66-
const zipFilePath = path.join(storagePath, `${downloadLink.id}.zip`);
66+
const zipFilePath = path.join(storagePath, downloadLink.queryId, `${downloadLink.id}.zip`);
6767
await saveFile(`${zipFilePath}`, response.data as ArrayBuffer);
6868

6969
// Extract the zipped artifact.
70-
const extractedPath = path.join(storagePath, downloadLink.id);
70+
const extractedPath = path.join(storagePath, downloadLink.queryId, downloadLink.id);
7171
await unzipFile(zipFilePath, extractedPath);
7272

73-
return downloadLink.innerFilePath
74-
? path.join(extractedPath, downloadLink.innerFilePath)
75-
: extractedPath;
73+
return path.join(extractedPath, downloadLink.innerFilePath || '');
7674
}
7775

7876
/**

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { AnalysisSummary, RemoteQueryResult } from './remote-query-result';
2424
import { RemoteQuery } from './remote-query';
2525
import { RemoteQueryResult as RemoteQueryResultViewModel } from './shared/remote-query-result';
2626
import { AnalysisSummary as AnalysisResultViewModel } from './shared/remote-query-result';
27-
import { showAndLogWarningMessage, tmpDir } from '../helpers';
27+
import { showAndLogWarningMessage } from '../helpers';
2828
import { URLSearchParams } from 'url';
2929
import { SHOW_QUERY_TEXT_MSG } from '../query-history';
3030
import { AnalysesResultsManager } from './analyses-results-manager';
@@ -98,7 +98,7 @@ export class RemoteQueriesInterfaceManager {
9898
enableFindWidget: true,
9999
retainContextWhenHidden: true,
100100
localResourceRoots: [
101-
Uri.file(tmpDir.name),
101+
Uri.file(this.analysesResultsManager.storagePath),
102102
Uri.file(path.join(this.ctx.extensionPath, 'out')),
103103
],
104104
}
@@ -224,7 +224,7 @@ export class RemoteQueriesInterfaceManager {
224224

225225
private async viewAnalysisResults(msg: RemoteQueryViewAnalysisResultsMessage): Promise<void> {
226226
const downloadLink = msg.analysisSummary.downloadLink;
227-
const filePath = path.join(tmpDir.name, downloadLink.id, downloadLink.innerFilePath || '');
227+
const filePath = path.join(this.analysesResultsManager.storagePath, downloadLink.queryId, downloadLink.id, downloadLink.innerFilePath || '');
228228

229229
const sarifViewerExtensionId = 'MS-SarifVSCode.sarif-viewer';
230230

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

Lines changed: 53 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import { CancellationToken, commands, ExtensionContext, Uri, window } from 'vscode';
2+
import { nanoid } from 'nanoid';
3+
import * as path from 'path';
4+
import * as fs from 'fs-extra';
5+
26
import { Credentials } from '../authentication';
37
import { CodeQLCliServer } from '../cli';
48
import { ProgressCallback } from '../commandRunner';
5-
import { showAndLogErrorMessage, showInformationMessageWithAction } from '../helpers';
9+
import { createTimestampFile, showAndLogErrorMessage, showInformationMessageWithAction } from '../helpers';
610
import { Logger } from '../logging';
711
import { runRemoteQuery } from './run-remote-query';
812
import { RemoteQueriesInterfaceManager } from './remote-queries-interface';
@@ -13,6 +17,7 @@ import { RemoteQueryResultIndex } from './remote-query-result-index';
1317
import { RemoteQueryResult } from './remote-query-result';
1418
import { DownloadLink } from './download-link';
1519
import { AnalysesResultsManager } from './analyses-results-manager';
20+
import { assertNever } from '../pure/helpers-pure';
1621

1722
const autoDownloadMaxSize = 300 * 1024;
1823
const autoDownloadMaxCount = 100;
@@ -25,7 +30,7 @@ export class RemoteQueriesManager {
2530
constructor(
2631
private readonly ctx: ExtensionContext,
2732
private readonly cliServer: CodeQLCliServer,
28-
readonly storagePath: string,
33+
private readonly storagePath: string,
2934
logger: Logger,
3035
) {
3136
this.analysesResultsManager = new AnalysesResultsManager(ctx, storagePath, logger);
@@ -58,19 +63,24 @@ export class RemoteQueriesManager {
5863
): Promise<void> {
5964
const credentials = await Credentials.initialize(this.ctx);
6065

61-
const queryResult = await this.remoteQueriesMonitor.monitorQuery(query, cancellationToken);
66+
const queryWorkflowResult = await this.remoteQueriesMonitor.monitorQuery(query, cancellationToken);
6267

6368
const executionEndTime = new Date();
6469

65-
if (queryResult.status === 'CompletedSuccessfully') {
70+
if (queryWorkflowResult.status === 'CompletedSuccessfully') {
6671
const resultIndex = await getRemoteQueryIndex(credentials, query);
6772
if (!resultIndex) {
6873
void showAndLogErrorMessage(`There was an issue retrieving the result for the query ${query.queryName}`);
6974
return;
7075
}
7176

72-
const queryResult = this.mapQueryResult(executionEndTime, resultIndex);
73-
await this.analysesResultsManager.prepareDownloadDirectory(query.queryName);
77+
const queryId = this.createQueryId(query.queryName);
78+
await this.prepareStorageDirectory(queryId);
79+
const queryResult = this.mapQueryResult(executionEndTime, resultIndex, queryId);
80+
81+
// Write the query result to the storage directory.
82+
const queryResultFilePath = path.join(this.storagePath, queryId, 'query-result.json');
83+
await fs.writeFile(queryResultFilePath, JSON.stringify(queryResult, null, 2), 'utf8');
7484

7585
// Kick off auto-download of results.
7686
void commands.executeCommand('codeQL.autoDownloadRemoteQueryResults', queryResult);
@@ -81,13 +91,19 @@ export class RemoteQueriesManager {
8191
const shouldOpenView = await showInformationMessageWithAction(message, 'View');
8292
if (shouldOpenView) {
8393
await this.interfaceManager.showResults(query, queryResult);
84-
8594
}
86-
} else if (queryResult.status === 'CompletedUnsuccessfully') {
87-
await showAndLogErrorMessage(`Remote query execution failed. Error: ${queryResult.error}`);
88-
return;
89-
} else if (queryResult.status === 'Cancelled') {
95+
} else if (queryWorkflowResult.status === 'CompletedUnsuccessfully') {
96+
await showAndLogErrorMessage(`Remote query execution failed. Error: ${queryWorkflowResult.error}`);
97+
98+
} else if (queryWorkflowResult.status === 'Cancelled') {
9099
await showAndLogErrorMessage('Remote query monitoring was cancelled');
100+
101+
} else if (queryWorkflowResult.status === 'InProgress') {
102+
// Should not get here
103+
await showAndLogErrorMessage(`Unexpected status: ${queryWorkflowResult.status}`);
104+
} else {
105+
// Ensure all cases are covered
106+
assertNever(queryWorkflowResult.status);
91107
}
92108
}
93109

@@ -111,21 +127,44 @@ export class RemoteQueriesManager {
111127
results => this.interfaceManager.setAnalysisResults(results));
112128
}
113129

114-
private mapQueryResult(executionEndTime: Date, resultIndex: RemoteQueryResultIndex): RemoteQueryResult {
130+
private mapQueryResult(executionEndTime: Date, resultIndex: RemoteQueryResultIndex, queryId: string): RemoteQueryResult {
115131
const analysisSummaries = resultIndex.items.map(item => ({
116132
nwo: item.nwo,
117133
resultCount: item.resultCount,
118134
fileSizeInBytes: item.sarifFileSize ? item.sarifFileSize : item.bqrsFileSize,
119135
downloadLink: {
120136
id: item.artifactId.toString(),
121137
urlPath: `${resultIndex.artifactsUrlPath}/${item.artifactId}`,
122-
innerFilePath: item.sarifFileSize ? 'results.sarif' : 'results.bqrs'
138+
innerFilePath: item.sarifFileSize ? 'results.sarif' : 'results.bqrs',
139+
queryId,
123140
} as DownloadLink
124141
}));
125142

126143
return {
127144
executionEndTime,
128-
analysisSummaries
145+
analysisSummaries,
129146
};
130147
}
148+
149+
/**
150+
* Generates a unique id for this query, suitable for determining the storage location for the downloaded query artifacts.
151+
* @param queryName
152+
* @returns
153+
*/
154+
private createQueryId(queryName: string): string {
155+
return `${queryName}-${nanoid()}`;
156+
157+
}
158+
159+
/**
160+
* Prepares a directory for storing analysis results for a single query run.
161+
* This directory contains a timestamp file, which will be
162+
* used by the query history manager to determine when the directory
163+
* should be deleted.
164+
*
165+
* @param queryName The name of the query that was run.
166+
*/
167+
private async prepareStorageDirectory(queryId: string): Promise<void> {
168+
await createTimestampFile(path.join(this.storagePath, queryId));
169+
}
131170
}

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ export const sampleRemoteQueryResult: RemoteQueryResult = {
4646
downloadLink: {
4747
id: '137697017',
4848
urlPath: '/repos/big-corp/controller-repo/actions/artifacts/137697017',
49-
innerFilePath: 'results.sarif'
49+
innerFilePath: 'results.sarif',
50+
queryId: 'query.ql-123-xyz'
5051
}
5152
},
5253
{
@@ -56,7 +57,8 @@ export const sampleRemoteQueryResult: RemoteQueryResult = {
5657
downloadLink: {
5758
id: '137697018',
5859
urlPath: '/repos/big-corp/controller-repo/actions/artifacts/137697018',
59-
innerFilePath: 'results.sarif'
60+
innerFilePath: 'results.sarif',
61+
queryId: 'query.ql-123-xyz'
6062
}
6163
},
6264
{
@@ -66,7 +68,8 @@ export const sampleRemoteQueryResult: RemoteQueryResult = {
6668
downloadLink: {
6769
id: '137697019',
6870
urlPath: '/repos/big-corp/controller-repo/actions/artifacts/137697019',
69-
innerFilePath: 'results.sarif'
71+
innerFilePath: 'results.sarif',
72+
queryId: 'query.ql-123-xyz'
7073
}
7174
},
7275
{
@@ -76,7 +79,8 @@ export const sampleRemoteQueryResult: RemoteQueryResult = {
7679
downloadLink: {
7780
id: '137697020',
7881
urlPath: '/repos/big-corp/controller-repo/actions/artifacts/137697020',
79-
innerFilePath: 'results.sarif'
82+
innerFilePath: 'results.sarif',
83+
queryId: 'query.ql-123-xyz'
8084
}
8185
}
8286
]

0 commit comments

Comments
 (0)