diff --git a/extensions/ql-vscode/src/remote-queries/run-remote-query.ts b/extensions/ql-vscode/src/remote-queries/run-remote-query.ts index a2184332c4d..ce3bd9fb051 100644 --- a/extensions/ql-vscode/src/remote-queries/run-remote-query.ts +++ b/extensions/ql-vscode/src/remote-queries/run-remote-query.ts @@ -142,7 +142,7 @@ async function findPackRoot(queryFile: string): Promise { while (!(await fs.pathExists(path.join(dir, 'qlpack.yml')))) { dir = path.dirname(dir); if (isFileSystemRoot(dir)) { - // there is no qlpack.yml in this direcory or any parent directory. + // there is no qlpack.yml in this directory or any parent directory. // just use the query file's directory as the pack root. return path.dirname(queryFile); } @@ -349,36 +349,44 @@ async function runRemoteQueriesApiRequest( const eol = os.EOL; const eol2 = os.EOL + os.EOL; +/** + * Returns "N repository" if N is one, "N repositories" otherwise. + */ +function pluralizeRepositories(numRepositories: number) { + return `${numRepositories} ${numRepositories === 1 ? 'repository' : 'repositories'}`; +} + // exported for testing only export function parseResponse(owner: string, repo: string, response: QueriesResponse) { const repositoriesQueried = response.repositories_queried; const numRepositoriesQueried = repositoriesQueried.length; - const popupMessage = `Successfully scheduled runs on ${numRepositoriesQueried} repositories. [Click here to see the progress](https://github.com/${owner}/${repo}/actions/runs/${response.workflow_run_id}).` + const popupMessage = `Successfully scheduled runs on ${pluralizeRepositories(numRepositoriesQueried)}. [Click here to see the progress](https://github.com/${owner}/${repo}/actions/runs/${response.workflow_run_id}).` + (response.errors ? `${eol2}Some repositories could not be scheduled. See extension log for details.` : ''); - let logMessage = `Successfully scheduled runs on ${numRepositoriesQueried} repositories. See https://github.com/${owner}/${repo}/actions/runs/${response.workflow_run_id}.`; + let logMessage = `Successfully scheduled runs on ${pluralizeRepositories(numRepositoriesQueried)}. See https://github.com/${owner}/${repo}/actions/runs/${response.workflow_run_id}.`; logMessage += `${eol2}Repositories queried:${eol}${repositoriesQueried.join(', ')}`; if (response.errors) { const { invalid_repositories, repositories_without_database, private_repositories, cutoff_repositories, cutoff_repositories_count } = response.errors; logMessage += `${eol2}Some repositories could not be scheduled.`; if (invalid_repositories?.length) { - logMessage += `${eol2}${invalid_repositories.length} repositories were invalid and could not be found:${eol}${invalid_repositories.join(', ')}`; + logMessage += `${eol2}${pluralizeRepositories(invalid_repositories.length)} invalid and could not be found:${eol}${invalid_repositories.join(', ')}`; } if (repositories_without_database?.length) { - logMessage += `${eol2}${repositories_without_database.length} repositories did not have a CodeQL database available:${eol}${repositories_without_database.join(', ')}`; + logMessage += `${eol2}${pluralizeRepositories(repositories_without_database.length)} did not have a CodeQL database available:${eol}${repositories_without_database.join(', ')}`; logMessage += `${eol}For each public repository that has not yet been added to the database service, we will try to create a database next time the store is updated.`; } if (private_repositories?.length) { - logMessage += `${eol2}${private_repositories.length} repositories are not public:${eol}${private_repositories.join(', ')}`; + logMessage += `${eol2}${pluralizeRepositories(private_repositories.length)} not public:${eol}${private_repositories.join(', ')}`; logMessage += `${eol}When using a public controller repository, only public repositories can be queried.`; } if (cutoff_repositories_count) { - logMessage += `${eol2}${cutoff_repositories_count} repositories over the limit for a single request`; + logMessage += `${eol2}${pluralizeRepositories(cutoff_repositories_count)} over the limit for a single request`; if (cutoff_repositories) { logMessage += `:${eol}${cutoff_repositories.join(', ')}`; if (cutoff_repositories_count !== cutoff_repositories.length) { - logMessage += `${eol}...${eol}And ${cutoff_repositories_count - cutoff_repositories.length} more repositrories.`; + const moreRepositories = cutoff_repositories_count - cutoff_repositories.length; + logMessage += `${eol}...${eol}And another ${pluralizeRepositories(moreRepositories)}.`; } } else { logMessage += '.'; diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/run-remote-query.test.ts b/extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/run-remote-query.test.ts index 5525aa2dc8b..b8b1785eec4 100644 --- a/extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/run-remote-query.test.ts +++ b/extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/run-remote-query.test.ts @@ -41,7 +41,7 @@ describe('run-remote-query', () => { '', 'Some repositories could not be scheduled.', '', - '2 repositories were invalid and could not be found:', + '2 repositories invalid and could not be found:', 'e/f, g/h'].join(os.EOL) ); }); @@ -96,7 +96,7 @@ describe('run-remote-query', () => { '', 'Some repositories could not be scheduled.', '', - '2 repositories are not public:', + '2 repositories not public:', 'e/f, g/h', 'When using a public controller repository, only public repositories can be queried.'].join(os.EOL) ); @@ -181,7 +181,7 @@ describe('run-remote-query', () => { '', 'Some repositories could not be scheduled.', '', - '2 repositories were invalid and could not be found:', + '2 repositories invalid and could not be found:', 'e/f, g/h', '', '2 repositories did not have a CodeQL database available:', @@ -189,5 +189,50 @@ describe('run-remote-query', () => { 'For each public repository that has not yet been added to the database service, we will try to create a database next time the store is updated.'].join(os.EOL) ); }); + + it('should parse a response with one repo of each category, and not pluralize "repositories"', () => { + const result = parseResponse('org', 'name', { + workflow_run_id: 123, + repositories_queried: ['a/b'], + errors: { + private_repositories: ['e/f'], + cutoff_repositories: ['i/j'], + cutoff_repositories_count: 1, + invalid_repositories: ['m/n'], + repositories_without_database: ['q/r'], + } + }); + + expect(result.popupMessage).to.equal( + ['Successfully scheduled runs on 1 repository. [Click here to see the progress](https://github.com/org/name/actions/runs/123).', + '', + 'Some repositories could not be scheduled. See extension log for details.'].join(os.EOL) + ); + expect(result.logMessage).to.equal( + [ + 'Successfully scheduled runs on 1 repository. See https://github.com/org/name/actions/runs/123.', + '', + 'Repositories queried:', + 'a/b', + '', + 'Some repositories could not be scheduled.', + '', + '1 repository invalid and could not be found:', + 'm/n', + '', + '1 repository did not have a CodeQL database available:', + 'q/r', + 'For each public repository that has not yet been added to the database service, we will try to create a database next time the store is updated.', + '', + '1 repository not public:', + 'e/f', + 'When using a public controller repository, only public repositories can be queried.', + '', + '1 repository over the limit for a single request:', + 'i/j', + 'Repositories were selected based on how recently they had been updated.', + ].join(os.EOL) + ); + }); }); });