Skip to content

Commit 82ada54

Browse files
committed
Add sorting to variant analysis results
Sort by stars, number of results, and name. This also includes a graphql query that retrieves all the stars for relevant repositories.
1 parent 0fdfeb3 commit 82ada54

File tree

11 files changed

+281
-42
lines changed

11 files changed

+281
-42
lines changed

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: 10 additions & 3 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';
@@ -182,7 +182,6 @@ export class RemoteQueriesManager extends DisposableObject {
182182
}
183183

184184
private mapQueryResult(executionEndTime: number, resultIndex: RemoteQueryResultIndex, queryId: string): RemoteQueryResult {
185-
186185
const analysisSummaries = resultIndex.successes.map(item => ({
187186
nwo: item.nwo,
188187
databaseSha: item.sha || 'HEAD',
@@ -281,6 +280,8 @@ export class RemoteQueriesManager extends DisposableObject {
281280
queryItem.failureReason = undefined;
282281
const queryResult = this.mapQueryResult(executionEndTime, resultIndex, queryItem.queryId);
283282

283+
await this.addStargazers(resultIndex, credentials, queryResult);
284+
284285
await this.storeJsonFile(queryItem, 'query-result.json', queryResult);
285286

286287
// Kick off auto-download of results in the background.
@@ -303,7 +304,13 @@ export class RemoteQueriesManager extends DisposableObject {
303304
}
304305
}
305306

306-
// Pulled from the analysis results manager, so that we can get access to
307+
private async addStargazers(resultIndex: RemoteQueryResultIndex, credentials: Credentials, queryResult: RemoteQueryResult) {
308+
const nwos = resultIndex.successes.map(s => s.nwo);
309+
const stargazers = await getStargazers(credentials, nwos);
310+
queryResult.analysisSummaries.forEach(analysis => analysis.starCount = stargazers[analysis.nwo]);
311+
}
312+
313+
// Pulled from the analysis results manager, so that we can get access to
307314
// analyses results from the "export results" command.
308315
public getAnalysesResults(queryId: string): AnalysisResults[] {
309316
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
}

extensions/ql-vscode/src/remote-queries/view/RemoteQueries.tsx

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import AnalysisAlertResult from './AnalysisAlertResult';
2121
import RawResultsTable from './RawResultsTable';
2222
import RepositoriesSearch from './RepositoriesSearch';
2323
import ActionButton from './ActionButton';
24+
import StarCount from './StarCount';
25+
import SortRepoFilter, { Sort, sorter } from './SortRepoFilter';
2426

2527
const numOfReposInContractedMode = 10;
2628

@@ -125,10 +127,13 @@ const Failures = (queryResult: RemoteQueryResult) => {
125127

126128
const SummaryTitleWithResults = ({
127129
queryResult,
128-
analysesResults
130+
analysesResults,
131+
sort, setSort
129132
}: {
130133
queryResult: RemoteQueryResult,
131-
analysesResults: AnalysisResults[]
134+
analysesResults: AnalysisResults[],
135+
sort: Sort,
136+
setSort: (sort: Sort) => void
132137
}) => {
133138
const showDownloadButton = queryResult.totalResultCount !== sumAnalysesResults(analysesResults);
134139

@@ -140,6 +145,11 @@ const SummaryTitleWithResults = ({
140145
text="Download all"
141146
onClick={() => downloadAllAnalysesResults(queryResult)} />
142147
}
148+
149+
<SortRepoFilter
150+
sort={sort}
151+
setSort={setSort}
152+
/>
143153
</div>
144154
);
145155
};
@@ -180,7 +190,7 @@ const SummaryItem = ({
180190
analysisSummary: AnalysisSummary,
181191
analysisResults: AnalysisResults | undefined
182192
}) => (
183-
<span>
193+
<>
184194
<span className="vscode-codeql__analysis-item"><RepoIcon size={16} /></span>
185195
<span className="vscode-codeql__analysis-item">{analysisSummary.nwo}</span>
186196
<span className="vscode-codeql__analysis-item"><Badge text={analysisSummary.resultCount.toString()} /></span>
@@ -189,15 +199,20 @@ const SummaryItem = ({
189199
analysisSummary={analysisSummary}
190200
analysisResults={analysisResults} />
191201
</span>
192-
</span>
202+
<StarCount starCount={analysisSummary.starCount} />
203+
</>
193204
);
194205

195206
const Summary = ({
196207
queryResult,
197-
analysesResults
208+
analysesResults,
209+
sort,
210+
setSort
198211
}: {
199212
queryResult: RemoteQueryResult,
200-
analysesResults: AnalysisResults[]
213+
analysesResults: AnalysisResults[],
214+
sort: Sort,
215+
setSort: (sort: Sort) => void
201216
}) => {
202217
const [repoListExpanded, setRepoListExpanded] = useState(false);
203218
const numOfReposToShow = repoListExpanded ? queryResult.analysisSummaries.length : numOfReposInContractedMode;
@@ -209,17 +224,21 @@ const Summary = ({
209224
? <SummaryTitleNoResults />
210225
: <SummaryTitleWithResults
211226
queryResult={queryResult}
212-
analysesResults={analysesResults} />
227+
analysesResults={analysesResults}
228+
sort={sort}
229+
setSort={setSort} />
213230
}
214231

215232
<ul className="vscode-codeql__flat-list">
216-
{queryResult.analysisSummaries.slice(0, numOfReposToShow).map((summary, i) =>
217-
<li key={summary.nwo} className="vscode-codeql__analysis-summaries-list-item">
218-
<SummaryItem
219-
analysisSummary={summary}
220-
analysisResults={analysesResults.find(a => a.nwo === summary.nwo)} />
221-
</li>
222-
)}
233+
{queryResult.analysisSummaries.slice(0, numOfReposToShow)
234+
.sort(sorter(sort))
235+
.map((summary, i) =>
236+
<li key={summary.nwo} className="vscode-codeql__analysis-summaries-list-item">
237+
<SummaryItem
238+
analysisSummary={summary}
239+
analysisResults={analysesResults.find(a => a.nwo === summary.nwo)} />
240+
</li>
241+
)}
223242
</ul>
224243
{
225244
queryResult.analysisSummaries.length > numOfReposInContractedMode &&
@@ -304,11 +323,13 @@ const RepoAnalysisResults = (analysisResults: AnalysisResults) => {
304323
const AnalysesResults = ({
305324
queryResult,
306325
analysesResults,
307-
totalResults
326+
totalResults,
327+
sort,
308328
}: {
309329
queryResult: RemoteQueryResult,
310330
analysesResults: AnalysisResults[],
311-
totalResults: number
331+
totalResults: number,
332+
sort: Sort
312333
}) => {
313334
const totalAnalysesResults = sumAnalysesResults(analysesResults);
314335
const [filterValue, setFilterValue] = React.useState('');
@@ -343,6 +364,7 @@ const AnalysesResults = ({
343364
{analysesResults
344365
.filter(a => a.interpretedResults.length > 0 || a.rawResults)
345366
.filter(a => a.nwo.toLowerCase().includes(filterValue.toLowerCase()))
367+
.sort(sorter(sort))
346368
.map(r =>
347369
<li key={r.nwo} className="vscode-codeql__analyses-results-list-item">
348370
<RepoAnalysisResults {...r} />
@@ -355,6 +377,7 @@ const AnalysesResults = ({
355377
export function RemoteQueries(): JSX.Element {
356378
const [queryResult, setQueryResult] = useState<RemoteQueryResult>(emptyQueryResult);
357379
const [analysesResults, setAnalysesResults] = useState<AnalysisResults[]>([]);
380+
const [sort, setSort] = useState<Sort>('name');
358381

359382
useEffect(() => {
360383
window.addEventListener('message', (evt: MessageEvent) => {
@@ -384,11 +407,16 @@ export function RemoteQueries(): JSX.Element {
384407
<ViewTitle>{queryResult.queryTitle}</ViewTitle>
385408
<QueryInfo {...queryResult} />
386409
<Failures {...queryResult} />
387-
<Summary queryResult={queryResult} analysesResults={analysesResults} />
410+
<Summary
411+
queryResult={queryResult}
412+
analysesResults={analysesResults}
413+
sort={sort}
414+
setSort={setSort} />
388415
<AnalysesResults
389416
queryResult={queryResult}
390417
analysesResults={analysesResults}
391-
totalResults={queryResult.totalResultCount} />
418+
totalResults={queryResult.totalResultCount}
419+
sort={sort} />
392420
</ThemeProvider>
393421
</div>
394422
);

0 commit comments

Comments
 (0)