Merge pull request #1362 from github/aeisenberg/last-update-sort

Add sort MRVA results by last updated
This commit is contained in:
Andrew Eisenberg
2022-05-26 09:15:37 -07:00
committed by GitHub
15 changed files with 116 additions and 25 deletions

View File

@@ -119,6 +119,7 @@ export class AnalysesResultsManager {
interpretedResults: [],
resultCount: analysis.resultCount,
starCount: analysis.starCount,
lastUpdated: analysis.lastUpdated,
};
const queryId = analysis.downloadLink.queryId;
const resultsForQuery = this.internalGetAnalysesResults(queryId);

View File

@@ -334,7 +334,7 @@ export async function createGist(
return response.data.html_url;
}
const stargazersQuery = `query Stars($repos: String!, $pageSize: Int!, $cursor: String) {
const repositoriesMetadataQuery = `query Stars($repos: String!, $pageSize: Int!, $cursor: String) {
search(
query: $repos
type: REPOSITORY
@@ -349,6 +349,7 @@ const stargazersQuery = `query Stars($repos: String!, $pageSize: Int!, $cursor:
login
}
stargazerCount
updatedAt
}
}
cursor
@@ -356,7 +357,7 @@ const stargazersQuery = `query Stars($repos: String!, $pageSize: Int!, $cursor:
}
}`;
type StargazersQueryResponse = {
type RepositoriesMetadataQueryResponse = {
search: {
edges: {
cursor: string;
@@ -366,20 +367,23 @@ type StargazersQueryResponse = {
login: string;
};
stargazerCount: number;
updatedAt: string; // Actually a ISO Date string
}
}[]
}
};
export async function getStargazers(credentials: Credentials, nwos: string[], pageSize = 100): Promise<Record<string, number>> {
export type RepositoriesMetadata = Record<string, { starCount: number, lastUpdated: number }>
export async function getRepositoriesMetadata(credentials: Credentials, nwos: string[], pageSize = 100): Promise<RepositoriesMetadata> {
const octokit = await credentials.getOctokit();
const repos = `repo:${nwos.join(' repo:')} fork:true`;
let cursor = null;
const stargazers: Record<string, number> = {};
const metadata: RepositoriesMetadata = {};
try {
do {
const response: StargazersQueryResponse = await octokit.graphql({
query: stargazersQuery,
const response: RepositoriesMetadataQueryResponse = await octokit.graphql({
query: repositoriesMetadataQuery,
repos,
pageSize,
cursor
@@ -390,8 +394,11 @@ export async function getStargazers(credentials: Credentials, nwos: string[], pa
const node = edge.node;
const owner = node.owner.login;
const name = node.name;
const stargazerCount = node.stargazerCount;
stargazers[`${owner}/${name}`] = stargazerCount;
const starCount = node.stargazerCount;
const lastUpdated = Date.now() - new Date(node.updatedAt).getTime();
metadata[`${owner}/${name}`] = {
starCount, lastUpdated
};
}
} while (cursor);
@@ -399,5 +406,5 @@ export async function getStargazers(credentials: Credentials, nwos: string[], pa
void showAndLogErrorMessage(`Error retrieving repository metadata for variant analysis: ${getErrorMessage(e)}`);
}
return stargazers;
return metadata;
}

View File

@@ -307,7 +307,8 @@ export class RemoteQueriesInterfaceManager {
resultCount: analysisResult.resultCount,
downloadLink: analysisResult.downloadLink,
fileSize: this.formatFileSize(analysisResult.fileSizeInBytes),
starCount: analysisResult.starCount
starCount: analysisResult.starCount,
lastUpdated: analysisResult.lastUpdated
}));
}
}

View File

@@ -12,7 +12,7 @@ import { runRemoteQuery } from './run-remote-query';
import { RemoteQueriesInterfaceManager } from './remote-queries-interface';
import { RemoteQuery } from './remote-query';
import { RemoteQueriesMonitor } from './remote-queries-monitor';
import { getRemoteQueryIndex, getStargazers } from './gh-actions-api-client';
import { getRemoteQueryIndex, getRepositoriesMetadata, RepositoriesMetadata } from './gh-actions-api-client';
import { RemoteQueryResultIndex } from './remote-query-result-index';
import { RemoteQueryResult } from './remote-query-result';
import { DownloadLink } from './download-link';
@@ -185,19 +185,20 @@ export class RemoteQueriesManager extends DisposableObject {
executionEndTime: number,
resultIndex: RemoteQueryResultIndex,
queryId: string,
stargazers: Record<string, number>
metadata: RepositoriesMetadata
): RemoteQueryResult {
const analysisSummaries = resultIndex.successes.map(item => ({
nwo: item.nwo,
databaseSha: item.sha || 'HEAD',
resultCount: item.resultCount,
fileSizeInBytes: item.sarifFileSize ? item.sarifFileSize : item.bqrsFileSize,
starCount: metadata[item.nwo].starCount,
lastUpdated: metadata[item.nwo].lastUpdated,
downloadLink: {
id: item.artifactId.toString(),
urlPath: `${resultIndex.artifactsUrlPath}/${item.artifactId}`,
innerFilePath: item.sarifFileSize ? 'results.sarif' : 'results.bqrs',
queryId,
starCount: stargazers[item.nwo]
queryId
} as DownloadLink
}));
const analysisFailures = resultIndex.failures.map(item => ({
@@ -284,8 +285,8 @@ export class RemoteQueriesManager extends DisposableObject {
queryItem.completed = true;
queryItem.status = QueryStatus.Completed;
queryItem.failureReason = undefined;
const stargazers = await this.getStargazersCount(resultIndex, credentials);
const queryResult = this.mapQueryResult(executionEndTime, resultIndex, queryItem.queryId, stargazers);
const metadata = await this.getRepositoriesMetadata(resultIndex, credentials);
const queryResult = this.mapQueryResult(executionEndTime, resultIndex, queryItem.queryId, metadata);
await this.storeJsonFile(queryItem, 'query-result.json', queryResult);
@@ -309,9 +310,9 @@ export class RemoteQueriesManager extends DisposableObject {
}
}
private async getStargazersCount(resultIndex: RemoteQueryResultIndex, credentials: Credentials) {
private async getRepositoriesMetadata(resultIndex: RemoteQueryResultIndex, credentials: Credentials) {
const nwos = resultIndex.successes.map(s => s.nwo);
return await getStargazers(credentials, nwos);
return await getRepositoriesMetadata(credentials, nwos);
}
// Pulled from the analysis results manager, so that we can get access to

View File

@@ -15,4 +15,5 @@ export interface AnalysisSummary {
downloadLink: DownloadLink,
fileSizeInBytes: number,
starCount?: number,
lastUpdated?: number,
}

View File

@@ -9,6 +9,7 @@ export interface AnalysisResults {
rawResults?: AnalysisRawResults;
resultCount: number,
starCount?: number,
lastUpdated?: number,
}
export interface AnalysisRawResults {

View File

@@ -24,4 +24,5 @@ export interface AnalysisSummary {
downloadLink: DownloadLink,
fileSize: string,
starCount?: number,
lastUpdated?: number,
}

View File

@@ -0,0 +1,62 @@
import * as React from 'react';
import { RepoPushIcon } from '@primer/octicons-react';
import styled from 'styled-components';
const IconContainer = styled.span`
flex-grow: 0;
text-align: right;
margin-right: 0;
`;
const Duration = styled.span`
text-align: left;
width: 8em;
margin-left: 0.5em;
`;
type Props = { lastUpdated?: number };
const LastUpdated = ({ lastUpdated }: Props) => (
Number.isFinite(lastUpdated) ? (
<>
<IconContainer>
<RepoPushIcon size={16} />
</IconContainer>
<Duration>
{humanizeDuration(lastUpdated)}
</Duration>
</>
) : (
<></>
)
);
export default LastUpdated;
const formatter = new Intl.RelativeTimeFormat('en', {
numeric: 'auto'
});
// All these are approximate, specifically months and years
const MINUTE_IN_MILLIS = 1000 * 60;
const HOUR_IN_MILLIS = 60 * MINUTE_IN_MILLIS;
const DAY_IN_MILLIS = 24 * HOUR_IN_MILLIS;
const MONTH_IN_MILLIS = 30 * DAY_IN_MILLIS;
const YEAR_IN_MILLIS = 365 * DAY_IN_MILLIS;
function humanizeDuration(diff?: number) {
if (!diff) {
return '';
}
if (diff < HOUR_IN_MILLIS) {
return formatter.format(- Math.floor(diff / MINUTE_IN_MILLIS), 'minute');
} else if (diff < DAY_IN_MILLIS) {
return formatter.format(- Math.floor(diff / HOUR_IN_MILLIS), 'hour');
} else if (diff < MONTH_IN_MILLIS) {
return formatter.format(- Math.floor(diff / DAY_IN_MILLIS), 'day');
} else if (diff < YEAR_IN_MILLIS) {
return formatter.format(- Math.floor(diff / MONTH_IN_MILLIS), 'month');
} else {
return formatter.format(- Math.floor(diff / YEAR_IN_MILLIS), 'year');
}
}

View File

@@ -23,6 +23,7 @@ import RepositoriesSearch from './RepositoriesSearch';
import ActionButton from './ActionButton';
import StarCount from './StarCount';
import SortRepoFilter, { Sort, sorter } from './SortRepoFilter';
import LastUpdated from './LastUpdated';
const numOfReposInContractedMode = 10;
@@ -200,6 +201,7 @@ const SummaryItem = ({
analysisResults={analysisResults} />
</span>
<StarCount starCount={analysisSummary.starCount} />
<LastUpdated lastUpdated={analysisSummary.lastUpdated} />
</>
);

View File

@@ -9,7 +9,7 @@ const SortWrapper = styled.span`
margin-right: 0;
`;
export type Sort = 'name' | 'stars' | 'results';
export type Sort = 'name' | 'stars' | 'results' | 'lastUpdated';
type Props = {
sort: Sort;
setSort: (sort: Sort) => void;
@@ -19,12 +19,14 @@ type Sortable = {
nwo: string;
starCount?: number;
resultCount?: number;
lastUpdated?: number;
};
const sortBy = [
{ name: 'Sort by Name', sort: 'name' },
{ name: 'Sort by Results', sort: 'results' },
{ name: 'Sort by Stars', sort: 'stars' },
{ name: 'Sort by Last Updated', sort: 'lastUpdated' },
];
export function sorter(sort: Sort): (left: Sortable, right: Sortable) => number {
@@ -37,6 +39,12 @@ export function sorter(sort: Sort): (left: Sortable, right: Sortable) => number
return stars;
}
}
if (sort === 'lastUpdated') {
const lastUpdated = (right.lastUpdated || 0) - (left.lastUpdated || 0);
if (lastUpdated !== 0) {
return lastUpdated;
}
}
if (sort === 'results') {
const results = (right.resultCount || 0) - (left.resultCount || 0);
if (results !== 0) {
@@ -44,13 +52,11 @@ export function sorter(sort: Sort): (left: Sortable, right: Sortable) => number
}
}
// Fall back on name compare if results or stars are equal
// Fall back on name compare if results, stars, or lastUpdated are equal
return left.nwo.localeCompare(right.nwo, undefined, { sensitivity: 'base' });
};
}
// FIXME These styles are not correct. Need to figure out
// why the theme is not being applied to the ActionMenu
const SortRepoFilter = ({ sort, setSort }: Props) => {
return <SortWrapper>
<ActionMenu>
@@ -72,7 +78,6 @@ const SortRepoFilter = ({ sort, setSort }: Props) => {
</ActionMenu.Overlay>
</ActionMenu>
</SortWrapper>;
};
export default SortRepoFilter;

View File

@@ -12,6 +12,7 @@ const Count = styled.span`
text-align: left;
width: 2em;
margin-left: 0.5em;
margin-right: 1.5em;
`;
type Props = { starCount?: number };

View File

@@ -5,6 +5,7 @@
"nwo": "github/vscode-codeql",
"resultCount": 15,
"starCount": 1,
"lastUpdated": 1653447088649,
"fileSizeInBytes": 191025,
"downloadLink": {
"id": "171543249",
@@ -17,6 +18,7 @@
"nwo": "other/hucairz",
"resultCount": 15,
"starCount": 1,
"lastUpdated": 1653447088649,
"fileSizeInBytes": 191025,
"downloadLink": {
"id": "11111111",

View File

@@ -5,6 +5,7 @@
"nwo": "github/vscode-codeql",
"resultCount": 5,
"starCount": 1,
"lastUpdated": 1653447088649,
"fileSizeInBytes": 81237,
"downloadLink": {
"id": "171544171",

View File

@@ -2,7 +2,7 @@ import { fail } from 'assert';
import { expect } from 'chai';
import * as sinon from 'sinon';
import { Credentials } from '../../../authentication';
import { cancelRemoteQuery, getStargazers as getStargazersCount } from '../../../remote-queries/gh-actions-api-client';
import { cancelRemoteQuery, getRepositoriesMetadata } from '../../../remote-queries/gh-actions-api-client';
import { RemoteQuery } from '../../../remote-queries/remote-query';
describe('gh-actions-api-client mock responses', () => {
@@ -61,7 +61,7 @@ describe('gh-actions-api-client real responses', function() {
}
const credentials = await Credentials.initializeWithToken(process.env.VSCODE_CODEQL_GITHUB_TOKEN!);
const stargazers = await getStargazersCount(credentials, [
const stargazers = await getRepositoriesMetadata(credentials, [
'github/codeql',
'github/vscode-codeql',
'rails/rails',

View File

@@ -254,6 +254,7 @@ describe('Remote queries and query history manager', function() {
nwo: 'github/vscode-codeql',
status: 'InProgress',
resultCount: 15,
lastUpdated: 1653447088649,
starCount: 1
}]);
@@ -261,11 +262,13 @@ describe('Remote queries and query history manager', function() {
nwo: 'github/vscode-codeql',
status: 'InProgress',
resultCount: 15,
lastUpdated: 1653447088649,
starCount: 1
}, {
nwo: 'other/hucairz',
status: 'InProgress',
resultCount: 15,
lastUpdated: 1653447088649,
starCount: 1
}]);
@@ -277,11 +280,13 @@ describe('Remote queries and query history manager', function() {
nwo: 'github/vscode-codeql',
status: 'Completed',
resultCount: 15,
lastUpdated: 1653447088649,
starCount: 1
}, {
nwo: 'other/hucairz',
status: 'Completed',
resultCount: 15,
lastUpdated: 1653447088649,
starCount: 1
}]);