From 4d5b40152c2ac82d5f5b0f1227ebf23c2a7c7acf Mon Sep 17 00:00:00 2001 From: Charis Kyriakou Date: Wed, 1 Jun 2022 11:37:49 +0100 Subject: [PATCH 1/7] Add ability of running MRVA against a whole org --- extensions/ql-vscode/src/pure/helpers-pure.ts | 6 ++ .../remote-queries/repository-selection.ts | 49 +++++++++---- .../src/remote-queries/run-remote-query.ts | 1 + .../repository-selection.test.ts | 72 +++++++++++++++---- 4 files changed, 101 insertions(+), 27 deletions(-) diff --git a/extensions/ql-vscode/src/pure/helpers-pure.ts b/extensions/ql-vscode/src/pure/helpers-pure.ts index 188a530c8fe..aa6401914ae 100644 --- a/extensions/ql-vscode/src/pure/helpers-pure.ts +++ b/extensions/ql-vscode/src/pure/helpers-pure.ts @@ -38,6 +38,12 @@ export const asyncFilter = async function (arr: T[], predicate: (arg0: T) => */ export const REPO_REGEX = /^(?:[a-zA-Z0-9]+-)*[a-zA-Z0-9]+\/[a-zA-Z0-9-_]+$/; +/** + * This regex matches GiHub organization strings. These are made up for alphanumeric + * characters or single hyphens, starting and ending in an alphanumeric character. + */ +export const ORG_REGEX = /^(?:[a-zA-Z0-9]+-)*[a-zA-Z0-9]+$/; + export function getErrorMessage(e: any) { return e instanceof Error ? e.message : String(e); } diff --git a/extensions/ql-vscode/src/remote-queries/repository-selection.ts b/extensions/ql-vscode/src/remote-queries/repository-selection.ts index 0bfce2c6e5b..e56d354ec7b 100644 --- a/extensions/ql-vscode/src/remote-queries/repository-selection.ts +++ b/extensions/ql-vscode/src/remote-queries/repository-selection.ts @@ -1,18 +1,20 @@ import { QuickPickItem, window } from 'vscode'; import { logger } from '../logging'; import { getRemoteRepositoryLists } from '../config'; -import { REPO_REGEX } from '../pure/helpers-pure'; +import { ORG_REGEX, REPO_REGEX } from '../pure/helpers-pure'; import { UserCancellationException } from '../commandRunner'; export interface RepositorySelection { repositories?: string[]; - repositoryLists?: string[] + repositoryLists?: string[]; + organisations?: string[]; } interface RepoListQuickPickItem extends QuickPickItem { repositories?: string[]; repositoryList?: string; - useCustomRepository?: boolean; + useCustomRepo?: boolean; + useAllReposOfOrg?: boolean; } /** @@ -22,6 +24,7 @@ interface RepoListQuickPickItem extends QuickPickItem { export async function getRepositorySelection(): Promise { const quickPickItems = [ createCustomRepoQuickPickItem(), + createAllReposOfOrgQuickPickItem(), ...createSystemDefinedRepoListsQuickPickItems(), ...createUserDefinedRepoListsQuickPickItems(), ]; @@ -41,13 +44,20 @@ export async function getRepositorySelection(): Promise { } else if (quickpick?.repositoryList) { void logger.log(`Selected repository list: ${quickpick.repositoryList}`); return { repositoryLists: [quickpick.repositoryList] }; - } else if (quickpick?.useCustomRepository) { + } else if (quickpick?.useCustomRepo) { const customRepo = await getCustomRepo(); if (!customRepo || !REPO_REGEX.test(customRepo)) { throw new UserCancellationException('Invalid repository format. Please enter a valid repository in the format / (e.g. github/codeql)'); } void logger.log(`Entered repository: ${customRepo}`); return { repositories: [customRepo] }; + } else if (quickpick?.useAllReposOfOrg) { + const org = await getOrganization(); + if (!org || !ORG_REGEX.test(org)) { + throw new UserCancellationException('Invalid organization format. Please enter a valid organization (e.g. github)'); + } + void logger.log(`Entered organization: ${org}`); + return { organisations: [org] }; } else { // We don't need to display a warning pop-up in this case, since the user just escaped out of the operation. // We set 'true' to make this a silent exception. @@ -61,17 +71,11 @@ export async function getRepositorySelection(): Promise { * @returns A boolean flag indicating if the selection is valid or not. */ export function isValidSelection(repoSelection: RepositorySelection): boolean { - if (repoSelection.repositories === undefined && repoSelection.repositoryLists === undefined) { - return false; - } - if (repoSelection.repositories !== undefined && repoSelection.repositories.length === 0) { - return false; - } - if (repoSelection.repositoryLists?.length === 0) { - return false; - } + const repositories = repoSelection.repositories || []; + const repositoryLists = repoSelection.repositoryLists || []; + const organisations = repoSelection.organisations || []; - return true; + return (repositories.length > 0 || repositoryLists.length > 0 || organisations.length > 0); } function createSystemDefinedRepoListsQuickPickItems(): RepoListQuickPickItem[] { @@ -101,11 +105,19 @@ function createUserDefinedRepoListsQuickPickItems(): RepoListQuickPickItem[] { function createCustomRepoQuickPickItem(): RepoListQuickPickItem { return { label: '$(edit) Enter a GitHub repository', - useCustomRepository: true, + useCustomRepo: true, alwaysShow: true, }; } +function createAllReposOfOrgQuickPickItem(): RepoListQuickPickItem { + return { + label: '$(edit) Enter a GitHub organization', + useAllReposOfOrg: true, + alwaysShow: true + }; +} + async function getCustomRepo(): Promise { return await window.showInputBox({ title: 'Enter a GitHub repository in the format / (e.g. github/codeql)', @@ -114,3 +126,10 @@ async function getCustomRepo(): Promise { ignoreFocusOut: true, }); } + +async function getOrganization(): Promise { + return await window.showInputBox({ + title: 'Enter a GitHub organization', + ignoreFocusOut: true, + }); +} 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 fd4543bb67a..1f957f9bf20 100644 --- a/extensions/ql-vscode/src/remote-queries/run-remote-query.ts +++ b/extensions/ql-vscode/src/remote-queries/run-remote-query.ts @@ -304,6 +304,7 @@ async function runRemoteQueriesApiRequest( language, repositories: repoSelection.repositories ?? undefined, repository_lists: repoSelection.repositoryLists ?? undefined, + organisations: repoSelection.organisations ?? undefined, query_pack: queryPackBase64, }; diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/repository-selection.test.ts b/extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/repository-selection.test.ts index 7c9cb9a04aa..e0b0cac7452 100644 --- a/extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/repository-selection.test.ts +++ b/extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/repository-selection.test.ts @@ -31,7 +31,7 @@ describe('repository-selection', function() { }); it('should allow selection from repo lists from your pre-defined config', async () => { - // fake return values + // Fake return values quickPickSpy.resolves( { repositories: ['foo/bar', 'foo/baz'] } ); @@ -42,18 +42,19 @@ describe('repository-selection', function() { } ); - // make the function call + // Make the function call const repoSelection = await mod.getRepositorySelection(); // Check that the return value is correct expect(repoSelection.repositoryLists).to.be.undefined; + expect(repoSelection.organizations).to.be.undefined; expect(repoSelection.repositories).to.deep.eq( ['foo/bar', 'foo/baz'] ); }); it('should allow selection from repo lists defined at the system level', async () => { - // fake return values + // Fake return values quickPickSpy.resolves( { repositoryList: 'top_100' } ); @@ -64,17 +65,62 @@ describe('repository-selection', function() { } ); - // make the function call + // Make the function call const repoSelection = await mod.getRepositorySelection(); // Check that the return value is correct expect(repoSelection.repositories).to.be.undefined; + expect(repoSelection.organizations).to.be.undefined; expect(repoSelection.repositoryLists).to.deep.eq( ['top_100'] ); }); - // Test the regex in various "good" cases + // Test the org regex in various "good" cases + const goodOrgs = [ + 'owner', + 'owner-with-hyphens', + 'ownerWithNumbers58' + ]; + goodOrgs.forEach(org => { + it(`should run on a valid org that you enter in the text box: ${org}`, async () => { + // Fake return values + quickPickSpy.resolves( + { useAllReposOfOrg: true } + ); + getRemoteRepositoryListsSpy.returns({}); // no pre-defined repo lists + showInputBoxSpy.resolves(org); + + // Make the function call + const repoSelection = await mod.getRepositorySelection(); + + // Check that the return value is correct + expect(repoSelection.repositories).to.be.undefined; + expect(repoSelection.repositoryLists).to.be.undefined; + expect(repoSelection.organisations).to.deep.eq([org]); + }); + }); + + // Test the org regex in various "bad" cases + const badOrgs = [ + 'invalid_owner', + 'owner-with-repo/repo' + ]; + badOrgs.forEach(org => { + it(`should show an error message if you enter an invalid org in the text box: ${org}`, async () => { + // Fake return values + quickPickSpy.resolves( + { useAllReposOfOrg: true } + ); + getRemoteRepositoryListsSpy.returns({}); // no pre-defined repo lists + showInputBoxSpy.resolves(org); + + // Function call should throw a UserCancellationException + await expect(mod.getRepositorySelection()).to.be.rejectedWith(UserCancellationException, 'Invalid organization format. Please enter a valid organization (e.g. github)'); + }); + }); + + // Test the repo regex in various "good" cases const goodRepos = [ 'owner/repo', 'owner-with-hyphens/repo-with-hyphens_and_underscores', @@ -82,24 +128,26 @@ describe('repository-selection', function() { ]; goodRepos.forEach(repo => { it(`should run on a valid repo that you enter in the text box: ${repo}`, async () => { - // fake return values + // Fake return values quickPickSpy.resolves( - { useCustomRepository: true } + { useCustomRepo: true } ); getRemoteRepositoryListsSpy.returns({}); // no pre-defined repo lists showInputBoxSpy.resolves(repo); - // make the function call + // Make the function call const repoSelection = await mod.getRepositorySelection(); // Check that the return value is correct + expect(repoSelection.repositoryLists).to.be.undefined; + expect(repoSelection.organizations).to.be.undefined; expect(repoSelection.repositories).to.deep.equal( [repo] ); }); }); - // Test the regex in various "bad" cases + // Test the repo regex in various "bad" cases const badRepos = [ 'invalid_owner/repo', 'owner/repo+some&invalid&stuff', @@ -108,14 +156,14 @@ describe('repository-selection', function() { ]; badRepos.forEach(repo => { it(`should show an error message if you enter an invalid repo in the text box: ${repo}`, async () => { - // fake return values + // Fake return values quickPickSpy.resolves( - { useCustomRepository: true } + { useCustomRepo: true } ); getRemoteRepositoryListsSpy.returns({}); // no pre-defined repo lists showInputBoxSpy.resolves(repo); - // function call should throw a UserCancellationException + // Function call should throw a UserCancellationException await expect(mod.getRepositorySelection()).to.be.rejectedWith(UserCancellationException, 'Invalid repository format'); }); }); From 22284b7dec131aa1afb1b37508b674b62cb885e7 Mon Sep 17 00:00:00 2001 From: Charis Kyriakou Date: Sat, 11 Jun 2022 11:17:51 +0200 Subject: [PATCH 2/7] Remove organisation to owner --- extensions/ql-vscode/src/pure/helpers-pure.ts | 4 +-- .../remote-queries/repository-selection.ts | 34 +++++++++---------- .../src/remote-queries/run-remote-query.ts | 2 +- .../repository-selection.test.ts | 14 ++++---- 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/extensions/ql-vscode/src/pure/helpers-pure.ts b/extensions/ql-vscode/src/pure/helpers-pure.ts index aa6401914ae..e3061b51cfe 100644 --- a/extensions/ql-vscode/src/pure/helpers-pure.ts +++ b/extensions/ql-vscode/src/pure/helpers-pure.ts @@ -39,10 +39,10 @@ export const asyncFilter = async function (arr: T[], predicate: (arg0: T) => export const REPO_REGEX = /^(?:[a-zA-Z0-9]+-)*[a-zA-Z0-9]+\/[a-zA-Z0-9-_]+$/; /** - * This regex matches GiHub organization strings. These are made up for alphanumeric + * This regex matches GiHub organization and user strings. These are made up for alphanumeric * characters or single hyphens, starting and ending in an alphanumeric character. */ -export const ORG_REGEX = /^(?:[a-zA-Z0-9]+-)*[a-zA-Z0-9]+$/; +export const OWNER_REGEX = /^(?:[a-zA-Z0-9]+-)*[a-zA-Z0-9]+$/; export function getErrorMessage(e: any) { return e instanceof Error ? e.message : String(e); diff --git a/extensions/ql-vscode/src/remote-queries/repository-selection.ts b/extensions/ql-vscode/src/remote-queries/repository-selection.ts index e56d354ec7b..393f82d3c0b 100644 --- a/extensions/ql-vscode/src/remote-queries/repository-selection.ts +++ b/extensions/ql-vscode/src/remote-queries/repository-selection.ts @@ -1,20 +1,20 @@ import { QuickPickItem, window } from 'vscode'; import { logger } from '../logging'; import { getRemoteRepositoryLists } from '../config'; -import { ORG_REGEX, REPO_REGEX } from '../pure/helpers-pure'; +import { OWNER_REGEX, REPO_REGEX } from '../pure/helpers-pure'; import { UserCancellationException } from '../commandRunner'; export interface RepositorySelection { repositories?: string[]; repositoryLists?: string[]; - organisations?: string[]; + owners?: string[]; } interface RepoListQuickPickItem extends QuickPickItem { repositories?: string[]; repositoryList?: string; useCustomRepo?: boolean; - useAllReposOfOrg?: boolean; + useAllReposOfOwner?: boolean; } /** @@ -24,7 +24,7 @@ interface RepoListQuickPickItem extends QuickPickItem { export async function getRepositorySelection(): Promise { const quickPickItems = [ createCustomRepoQuickPickItem(), - createAllReposOfOrgQuickPickItem(), + createAllReposOfOwnerQuickPickItem(), ...createSystemDefinedRepoListsQuickPickItems(), ...createUserDefinedRepoListsQuickPickItems(), ]; @@ -51,13 +51,13 @@ export async function getRepositorySelection(): Promise { } void logger.log(`Entered repository: ${customRepo}`); return { repositories: [customRepo] }; - } else if (quickpick?.useAllReposOfOrg) { - const org = await getOrganization(); - if (!org || !ORG_REGEX.test(org)) { - throw new UserCancellationException('Invalid organization format. Please enter a valid organization (e.g. github)'); + } else if (quickpick?.useAllReposOfOwner) { + const owner = await getOwner(); + if (!owner || !OWNER_REGEX.test(owner)) { + throw new UserCancellationException('Invalid user or organization format. Please enter a valid user or organization (e.g. github)'); } - void logger.log(`Entered organization: ${org}`); - return { organisations: [org] }; + void logger.log(`Entered owner: ${owner}`); + return { owners: [owner] }; } else { // We don't need to display a warning pop-up in this case, since the user just escaped out of the operation. // We set 'true' to make this a silent exception. @@ -73,9 +73,9 @@ export async function getRepositorySelection(): Promise { export function isValidSelection(repoSelection: RepositorySelection): boolean { const repositories = repoSelection.repositories || []; const repositoryLists = repoSelection.repositoryLists || []; - const organisations = repoSelection.organisations || []; + const owners = repoSelection.owners || []; - return (repositories.length > 0 || repositoryLists.length > 0 || organisations.length > 0); + return (repositories.length > 0 || repositoryLists.length > 0 || owners.length > 0); } function createSystemDefinedRepoListsQuickPickItems(): RepoListQuickPickItem[] { @@ -110,10 +110,10 @@ function createCustomRepoQuickPickItem(): RepoListQuickPickItem { }; } -function createAllReposOfOrgQuickPickItem(): RepoListQuickPickItem { +function createAllReposOfOwnerQuickPickItem(): RepoListQuickPickItem { return { - label: '$(edit) Enter a GitHub organization', - useAllReposOfOrg: true, + label: '$(edit) Enter a GitHub user or organization', + useAllReposOfOwner: true, alwaysShow: true }; } @@ -127,9 +127,9 @@ async function getCustomRepo(): Promise { }); } -async function getOrganization(): Promise { +async function getOwner(): Promise { return await window.showInputBox({ - title: 'Enter a GitHub organization', + title: 'Enter a GitHub user or organization', ignoreFocusOut: true, }); } 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 1f957f9bf20..442087a6f45 100644 --- a/extensions/ql-vscode/src/remote-queries/run-remote-query.ts +++ b/extensions/ql-vscode/src/remote-queries/run-remote-query.ts @@ -304,7 +304,7 @@ async function runRemoteQueriesApiRequest( language, repositories: repoSelection.repositories ?? undefined, repository_lists: repoSelection.repositoryLists ?? undefined, - organisations: repoSelection.organisations ?? undefined, + repository_owners: repoSelection.owners ?? undefined, query_pack: queryPackBase64, }; diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/repository-selection.test.ts b/extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/repository-selection.test.ts index e0b0cac7452..9e3e4eac07f 100644 --- a/extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/repository-selection.test.ts +++ b/extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/repository-selection.test.ts @@ -47,7 +47,7 @@ describe('repository-selection', function() { // Check that the return value is correct expect(repoSelection.repositoryLists).to.be.undefined; - expect(repoSelection.organizations).to.be.undefined; + expect(repoSelection.owners).to.be.undefined; expect(repoSelection.repositories).to.deep.eq( ['foo/bar', 'foo/baz'] ); @@ -70,7 +70,7 @@ describe('repository-selection', function() { // Check that the return value is correct expect(repoSelection.repositories).to.be.undefined; - expect(repoSelection.organizations).to.be.undefined; + expect(repoSelection.owners).to.be.undefined; expect(repoSelection.repositoryLists).to.deep.eq( ['top_100'] ); @@ -86,7 +86,7 @@ describe('repository-selection', function() { it(`should run on a valid org that you enter in the text box: ${org}`, async () => { // Fake return values quickPickSpy.resolves( - { useAllReposOfOrg: true } + { useAllReposOfOwner: true } ); getRemoteRepositoryListsSpy.returns({}); // no pre-defined repo lists showInputBoxSpy.resolves(org); @@ -97,7 +97,7 @@ describe('repository-selection', function() { // Check that the return value is correct expect(repoSelection.repositories).to.be.undefined; expect(repoSelection.repositoryLists).to.be.undefined; - expect(repoSelection.organisations).to.deep.eq([org]); + expect(repoSelection.owners).to.deep.eq([org]); }); }); @@ -110,13 +110,13 @@ describe('repository-selection', function() { it(`should show an error message if you enter an invalid org in the text box: ${org}`, async () => { // Fake return values quickPickSpy.resolves( - { useAllReposOfOrg: true } + { useAllReposOfOwner: true } ); getRemoteRepositoryListsSpy.returns({}); // no pre-defined repo lists showInputBoxSpy.resolves(org); // Function call should throw a UserCancellationException - await expect(mod.getRepositorySelection()).to.be.rejectedWith(UserCancellationException, 'Invalid organization format. Please enter a valid organization (e.g. github)'); + await expect(mod.getRepositorySelection()).to.be.rejectedWith(UserCancellationException, 'Invalid user or organization format. Please enter a valid user or organization (e.g. github)'); }); }); @@ -140,7 +140,7 @@ describe('repository-selection', function() { // Check that the return value is correct expect(repoSelection.repositoryLists).to.be.undefined; - expect(repoSelection.organizations).to.be.undefined; + expect(repoSelection.owners).to.be.undefined; expect(repoSelection.repositories).to.deep.equal( [repo] ); From 5256ff38a8c1bba02576b45893f279aa9b11ca7f Mon Sep 17 00:00:00 2001 From: Charis Kyriakou Date: Sat, 11 Jun 2022 11:22:37 +0200 Subject: [PATCH 3/7] More renames --- .../remote-queries/repository-selection.test.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/repository-selection.test.ts b/extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/repository-selection.test.ts index 9e3e4eac07f..faaf87efbc2 100644 --- a/extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/repository-selection.test.ts +++ b/extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/repository-selection.test.ts @@ -76,13 +76,13 @@ describe('repository-selection', function() { ); }); - // Test the org regex in various "good" cases - const goodOrgs = [ + // Test the owner regex in various "good" cases + const goodOwners = [ 'owner', 'owner-with-hyphens', 'ownerWithNumbers58' ]; - goodOrgs.forEach(org => { + goodOwners.forEach(org => { it(`should run on a valid org that you enter in the text box: ${org}`, async () => { // Fake return values quickPickSpy.resolves( @@ -101,12 +101,12 @@ describe('repository-selection', function() { }); }); - // Test the org regex in various "bad" cases - const badOrgs = [ + // Test the owner regex in various "bad" cases + const badOwners = [ 'invalid_owner', 'owner-with-repo/repo' ]; - badOrgs.forEach(org => { + badOwners.forEach(org => { it(`should show an error message if you enter an invalid org in the text box: ${org}`, async () => { // Fake return values quickPickSpy.resolves( From c86d2a4cf320ffd9608c5804899bd216207b9e27 Mon Sep 17 00:00:00 2001 From: Charis Kyriakou Date: Sat, 11 Jun 2022 11:24:41 +0200 Subject: [PATCH 4/7] More renames --- .../remote-queries/repository-selection.test.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/repository-selection.test.ts b/extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/repository-selection.test.ts index faaf87efbc2..72fb490cbeb 100644 --- a/extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/repository-selection.test.ts +++ b/extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/repository-selection.test.ts @@ -82,14 +82,14 @@ describe('repository-selection', function() { 'owner-with-hyphens', 'ownerWithNumbers58' ]; - goodOwners.forEach(org => { - it(`should run on a valid org that you enter in the text box: ${org}`, async () => { + goodOwners.forEach(owner => { + it(`should run on a valid owner that you enter in the text box: ${owner}`, async () => { // Fake return values quickPickSpy.resolves( { useAllReposOfOwner: true } ); getRemoteRepositoryListsSpy.returns({}); // no pre-defined repo lists - showInputBoxSpy.resolves(org); + showInputBoxSpy.resolves(owner); // Make the function call const repoSelection = await mod.getRepositorySelection(); @@ -97,7 +97,7 @@ describe('repository-selection', function() { // Check that the return value is correct expect(repoSelection.repositories).to.be.undefined; expect(repoSelection.repositoryLists).to.be.undefined; - expect(repoSelection.owners).to.deep.eq([org]); + expect(repoSelection.owners).to.deep.eq([owner]); }); }); @@ -106,14 +106,14 @@ describe('repository-selection', function() { 'invalid_owner', 'owner-with-repo/repo' ]; - badOwners.forEach(org => { - it(`should show an error message if you enter an invalid org in the text box: ${org}`, async () => { + badOwners.forEach(owner => { + it(`should show an error message if you enter an invalid owner in the text box: ${owner}`, async () => { // Fake return values quickPickSpy.resolves( { useAllReposOfOwner: true } ); getRemoteRepositoryListsSpy.returns({}); // no pre-defined repo lists - showInputBoxSpy.resolves(org); + showInputBoxSpy.resolves(owner); // Function call should throw a UserCancellationException await expect(mod.getRepositorySelection()).to.be.rejectedWith(UserCancellationException, 'Invalid user or organization format. Please enter a valid user or organization (e.g. github)'); From 8d6b9ff1950c0825bb78d98e2069aabccc0a89d4 Mon Sep 17 00:00:00 2001 From: Charis Kyriakou Date: Tue, 14 Jun 2022 09:43:51 +0100 Subject: [PATCH 5/7] Update extensions/ql-vscode/src/remote-queries/repository-selection.ts Co-authored-by: Andrew Eisenberg --- extensions/ql-vscode/src/remote-queries/repository-selection.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/ql-vscode/src/remote-queries/repository-selection.ts b/extensions/ql-vscode/src/remote-queries/repository-selection.ts index 393f82d3c0b..87f14cf508a 100644 --- a/extensions/ql-vscode/src/remote-queries/repository-selection.ts +++ b/extensions/ql-vscode/src/remote-queries/repository-selection.ts @@ -54,7 +54,7 @@ export async function getRepositorySelection(): Promise { } else if (quickpick?.useAllReposOfOwner) { const owner = await getOwner(); if (!owner || !OWNER_REGEX.test(owner)) { - throw new UserCancellationException('Invalid user or organization format. Please enter a valid user or organization (e.g. github)'); + throw new Error(`Invalid user or organization: ${owner}`); } void logger.log(`Entered owner: ${owner}`); return { owners: [owner] }; From c5f393438dbb2b369f5ace08bcfced3e7c0e0c23 Mon Sep 17 00:00:00 2001 From: Charis Kyriakou Date: Tue, 14 Jun 2022 12:45:08 +0100 Subject: [PATCH 6/7] Fix tests --- .../no-workspace/remote-queries/repository-selection.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/repository-selection.test.ts b/extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/repository-selection.test.ts index 72fb490cbeb..e23b6f41e19 100644 --- a/extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/repository-selection.test.ts +++ b/extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/repository-selection.test.ts @@ -116,7 +116,7 @@ describe('repository-selection', function() { showInputBoxSpy.resolves(owner); // Function call should throw a UserCancellationException - await expect(mod.getRepositorySelection()).to.be.rejectedWith(UserCancellationException, 'Invalid user or organization format. Please enter a valid user or organization (e.g. github)'); + await expect(mod.getRepositorySelection()).to.be.rejectedWith(Error, `Invalid user or organization: ${owner}`); }); }); From 88061ce698825f0b6ea8fa07878edf09322c8894 Mon Sep 17 00:00:00 2001 From: Charis Kyriakou Date: Fri, 17 Jun 2022 13:41:01 +0100 Subject: [PATCH 7/7] Update owner regex --- extensions/ql-vscode/src/pure/helpers-pure.ts | 4 ++-- .../remote-queries/repository-selection.test.ts | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/extensions/ql-vscode/src/pure/helpers-pure.ts b/extensions/ql-vscode/src/pure/helpers-pure.ts index 75d2439a836..8554befaecc 100644 --- a/extensions/ql-vscode/src/pure/helpers-pure.ts +++ b/extensions/ql-vscode/src/pure/helpers-pure.ts @@ -40,9 +40,9 @@ export const REPO_REGEX = /^[a-zA-Z0-9-_\.]+\/[a-zA-Z0-9-_\.]+$/; /** * This regex matches GiHub organization and user strings. These are made up for alphanumeric - * characters or single hyphens, starting and ending in an alphanumeric character. + * characters, hyphens, underscores or periods. */ -export const OWNER_REGEX = /^(?:[a-zA-Z0-9]+-)*[a-zA-Z0-9]+$/; +export const OWNER_REGEX = /^[a-zA-Z0-9-_\.]+$/; export function getErrorMessage(e: any) { return e instanceof Error ? e.message : String(e); diff --git a/extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/repository-selection.test.ts b/extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/repository-selection.test.ts index af3c1deadf6..020aca2fa8b 100644 --- a/extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/repository-selection.test.ts +++ b/extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/repository-selection.test.ts @@ -80,7 +80,9 @@ describe('repository-selection', function() { const goodOwners = [ 'owner', 'owner-with-hyphens', - 'ownerWithNumbers58' + 'ownerWithNumbers58', + 'owner_with_underscores', + 'owner.with.periods.' ]; goodOwners.forEach(owner => { it(`should run on a valid owner that you enter in the text box: ${owner}`, async () => { @@ -103,7 +105,7 @@ describe('repository-selection', function() { // Test the owner regex in various "bad" cases const badOwners = [ - 'invalid_owner', + 'invalid&owner', 'owner-with-repo/repo' ]; badOwners.forEach(owner => {