Skip to content

Commit 405a6c9

Browse files
authored
Merge pull request #1353 from github/aeisenberg/sort-remote-results
Add sorting to variant analysis results
2 parents 7b33441 + 3611b1f commit 405a6c9

File tree

17 files changed

+364
-47
lines changed

17 files changed

+364
-47
lines changed

.github/workflows/main.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,17 @@ jobs:
118118
- name: Run integration tests (Linux)
119119
if: matrix.os == 'ubuntu-latest'
120120
working-directory: extensions/ql-vscode
121+
env:
122+
VSCODE_CODEQL_GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
121123
run: |
122124
sudo apt-get install xvfb
123125
/usr/bin/xvfb-run npm run integration
124126
125127
- name: Run integration tests (Windows)
126128
if: matrix.os == 'windows-latest'
127129
working-directory: extensions/ql-vscode
130+
env:
131+
VSCODE_CODEQL_GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
128132
run: |
129133
npm run integration
130134

extensions/ql-vscode/src/authentication.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,25 @@ export class Credentials {
3535
return c;
3636
}
3737

38-
private async createOctokit(createIfNone: boolean): Promise<Octokit.Octokit | undefined> {
38+
/**
39+
* Initializes an instance of credentials with an octokit instance using
40+
* a token from the user's GitHub account. This method is meant to be
41+
* used non-interactive environments such as tests.
42+
*
43+
* @param overrideToken The GitHub token to use for authentication.
44+
* @returns An instance of credentials.
45+
*/
46+
static async initializeWithToken(overrideToken: string) {
47+
const c = new Credentials();
48+
c.octokit = await c.createOctokit(false, overrideToken);
49+
return c;
50+
}
51+
52+
private async createOctokit(createIfNone: boolean, overrideToken?: string): Promise<Octokit.Octokit | undefined> {
53+
if (overrideToken) {
54+
return new Octokit.Octokit({ auth: overrideToken });
55+
}
56+
3957
const session = await vscode.authentication.getSession(GITHUB_AUTH_PROVIDER_ID, SCOPES, { createIfNone });
4058

4159
if (session) {

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,9 @@ export class AnalysesResultsManager {
116116
const analysisResults: AnalysisResults = {
117117
nwo: analysis.nwo,
118118
status: 'InProgress',
119-
interpretedResults: []
119+
interpretedResults: [],
120+
resultCount: analysis.resultCount,
121+
starCount: analysis.starCount,
120122
};
121123
const queryId = analysis.downloadLink.queryId;
122124
const resultsForQuery = this.internalGetAnalysesResults(queryId);

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

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import * as unzipper from 'unzipper';
22
import * as path from 'path';
33
import * as fs from 'fs-extra';
4-
import { showAndLogWarningMessage, tmpDir } from '../helpers';
4+
import { showAndLogErrorMessage, showAndLogWarningMessage, tmpDir } from '../helpers';
55
import { Credentials } from '../authentication';
66
import { logger } from '../logging';
77
import { RemoteQueryWorkflowResult } from './remote-query-workflow-result';
88
import { DownloadLink, createDownloadPath } from './download-link';
99
import { RemoteQuery } from './remote-query';
1010
import { RemoteQueryFailureIndexItem, RemoteQueryResultIndex, RemoteQuerySuccessIndexItem } from './remote-query-result-index';
11+
import { getErrorMessage } from '../pure/helpers-pure';
1112

1213
interface ApiSuccessIndexItem {
1314
nwo: string;
@@ -332,3 +333,71 @@ export async function createGist(
332333
}
333334
return response.data.html_url;
334335
}
336+
337+
const stargazersQuery = `query Stars($repos: String!, $pageSize: Int!, $cursor: String) {
338+
search(
339+
query: $repos
340+
type: REPOSITORY
341+
first: $pageSize
342+
after: $cursor
343+
) {
344+
edges {
345+
node {
346+
... on Repository {
347+
name
348+
owner {
349+
login
350+
}
351+
stargazerCount
352+
}
353+
}
354+
cursor
355+
}
356+
}
357+
}`;
358+
359+
type StargazersQueryResponse = {
360+
search: {
361+
edges: {
362+
cursor: string;
363+
node: {
364+
name: string;
365+
owner: {
366+
login: string;
367+
};
368+
stargazerCount: number;
369+
}
370+
}[]
371+
}
372+
};
373+
374+
export async function getStargazers(credentials: Credentials, nwos: string[], pageSize = 100): Promise<Record<string, number>> {
375+
const octokit = await credentials.getOctokit();
376+
const repos = `repo:${nwos.join(' repo:')} fork:true`;
377+
let cursor = null;
378+
const stargazers: Record<string, number> = {};
379+
try {
380+
do {
381+
const response: StargazersQueryResponse = await octokit.graphql({
382+
query: stargazersQuery,
383+
repos,
384+
pageSize,
385+
cursor
386+
});
387+
cursor = response.search.edges.length === pageSize ? response.search.edges[pageSize - 1].cursor : null;
388+
389+
for (const edge of response.search.edges) {
390+
const node = edge.node;
391+
const owner = node.owner.login;
392+
const name = node.name;
393+
const stargazerCount = node.stargazerCount;
394+
stargazers[`${owner}/${name}`] = stargazerCount;
395+
}
396+
397+
} while (cursor);
398+
} catch (e) {
399+
void showAndLogErrorMessage(`Error retrieving repository metadata for variant analysis: ${getErrorMessage(e)}`);
400+
}
401+
402+
return stargazers;
403+
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,8 @@ export class RemoteQueriesInterfaceManager {
306306
databaseSha: analysisResult.databaseSha || 'HEAD',
307307
resultCount: analysisResult.resultCount,
308308
downloadLink: analysisResult.downloadLink,
309-
fileSize: this.formatFileSize(analysisResult.fileSizeInBytes)
309+
fileSize: this.formatFileSize(analysisResult.fileSizeInBytes),
310+
starCount: analysisResult.starCount
310311
}));
311312
}
312313
}

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

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { runRemoteQuery } from './run-remote-query';
1212
import { RemoteQueriesInterfaceManager } from './remote-queries-interface';
1313
import { RemoteQuery } from './remote-query';
1414
import { RemoteQueriesMonitor } from './remote-queries-monitor';
15-
import { getRemoteQueryIndex } from './gh-actions-api-client';
15+
import { getRemoteQueryIndex, getStargazers } from './gh-actions-api-client';
1616
import { RemoteQueryResultIndex } from './remote-query-result-index';
1717
import { RemoteQueryResult } from './remote-query-result';
1818
import { DownloadLink } from './download-link';
@@ -181,8 +181,12 @@ export class RemoteQueriesManager extends DisposableObject {
181181
results => this.interfaceManager.setAnalysisResults(results, queryResult.queryId));
182182
}
183183

184-
private mapQueryResult(executionEndTime: number, resultIndex: RemoteQueryResultIndex, queryId: string): RemoteQueryResult {
185-
184+
private mapQueryResult(
185+
executionEndTime: number,
186+
resultIndex: RemoteQueryResultIndex,
187+
queryId: string,
188+
stargazers: Record<string, number>
189+
): RemoteQueryResult {
186190
const analysisSummaries = resultIndex.successes.map(item => ({
187191
nwo: item.nwo,
188192
databaseSha: item.sha || 'HEAD',
@@ -193,6 +197,7 @@ export class RemoteQueriesManager extends DisposableObject {
193197
urlPath: `${resultIndex.artifactsUrlPath}/${item.artifactId}`,
194198
innerFilePath: item.sarifFileSize ? 'results.sarif' : 'results.bqrs',
195199
queryId,
200+
starCount: stargazers[item.nwo]
196201
} as DownloadLink
197202
}));
198203
const analysisFailures = resultIndex.failures.map(item => ({
@@ -279,7 +284,8 @@ export class RemoteQueriesManager extends DisposableObject {
279284
queryItem.completed = true;
280285
queryItem.status = QueryStatus.Completed;
281286
queryItem.failureReason = undefined;
282-
const queryResult = this.mapQueryResult(executionEndTime, resultIndex, queryItem.queryId);
287+
const stargazers = await this.getStargazersCount(resultIndex, credentials);
288+
const queryResult = this.mapQueryResult(executionEndTime, resultIndex, queryItem.queryId, stargazers);
283289

284290
await this.storeJsonFile(queryItem, 'query-result.json', queryResult);
285291

@@ -303,7 +309,12 @@ export class RemoteQueriesManager extends DisposableObject {
303309
}
304310
}
305311

306-
// Pulled from the analysis results manager, so that we can get access to
312+
private async getStargazersCount(resultIndex: RemoteQueryResultIndex, credentials: Credentials) {
313+
const nwos = resultIndex.successes.map(s => s.nwo);
314+
return await getStargazers(credentials, nwos);
315+
}
316+
317+
// Pulled from the analysis results manager, so that we can get access to
307318
// analyses results from the "export results" command.
308319
public getAnalysesResults(queryId: string): AnalysisResults[] {
309320
return [...this.analysesResultsManager.getAnalysesResults(queryId)];

extensions/ql-vscode/src/remote-queries/remote-query-result.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,17 @@ import { DownloadLink } from './download-link';
22
import { AnalysisFailure } from './shared/analysis-failure';
33

44
export interface RemoteQueryResult {
5-
executionEndTime: number; // Can't use a Date here since it needs to be serialized and desserialized.
6-
analysisSummaries: AnalysisSummary[];
7-
analysisFailures: AnalysisFailure[];
8-
queryId: string;
5+
executionEndTime: number, // Can't use a Date here since it needs to be serialized and desserialized.
6+
analysisSummaries: AnalysisSummary[],
7+
analysisFailures: AnalysisFailure[],
8+
queryId: string,
99
}
1010

1111
export interface AnalysisSummary {
1212
nwo: string,
1313
databaseSha: string,
1414
resultCount: number,
1515
downloadLink: DownloadLink,
16-
fileSizeInBytes: number
16+
fileSizeInBytes: number,
17+
starCount?: number,
1718
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ export interface AnalysisResults {
77
status: AnalysisResultStatus;
88
interpretedResults: AnalysisAlert[];
99
rawResults?: AnalysisRawResults;
10+
resultCount: number,
11+
starCount?: number,
1012
}
1113

1214
export interface AnalysisRawResults {

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

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,19 @@ import { DownloadLink } from '../download-link';
22
import { AnalysisFailure } from './analysis-failure';
33

44
export interface RemoteQueryResult {
5-
queryTitle: string;
6-
queryFileName: string;
7-
queryFilePath: string;
8-
queryText: string;
9-
language: string;
10-
workflowRunUrl: string;
11-
totalRepositoryCount: number;
12-
affectedRepositoryCount: number;
13-
totalResultCount: number;
14-
executionTimestamp: string;
15-
executionDuration: string;
16-
analysisSummaries: AnalysisSummary[];
17-
analysisFailures: AnalysisFailure[];
5+
queryTitle: string,
6+
queryFileName: string,
7+
queryFilePath: string,
8+
queryText: string,
9+
language: string,
10+
workflowRunUrl: string,
11+
totalRepositoryCount: number,
12+
affectedRepositoryCount: number,
13+
totalResultCount: number,
14+
executionTimestamp: string,
15+
executionDuration: string,
16+
analysisSummaries: AnalysisSummary[],
17+
analysisFailures: AnalysisFailure[],
1818
}
1919

2020
export interface AnalysisSummary {
@@ -23,4 +23,5 @@ export interface AnalysisSummary {
2323
resultCount: number,
2424
downloadLink: DownloadLink,
2525
fileSize: string,
26+
starCount?: number,
2627
}

0 commit comments

Comments
 (0)