Add ability of running MRVA against a whole org (#1372)
This commit is contained in:
@@ -38,6 +38,12 @@ export const asyncFilter = async function <T>(arr: T[], predicate: (arg0: T) =>
|
||||
*/
|
||||
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, hyphens, underscores or periods.
|
||||
*/
|
||||
export const OWNER_REGEX = /^[a-zA-Z0-9-_\.]+$/;
|
||||
|
||||
export function getErrorMessage(e: any) {
|
||||
return e instanceof Error ? e.message : String(e);
|
||||
}
|
||||
|
||||
@@ -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 { OWNER_REGEX, REPO_REGEX } from '../pure/helpers-pure';
|
||||
import { UserCancellationException } from '../commandRunner';
|
||||
|
||||
export interface RepositorySelection {
|
||||
repositories?: string[];
|
||||
repositoryLists?: string[]
|
||||
repositoryLists?: string[];
|
||||
owners?: string[];
|
||||
}
|
||||
|
||||
interface RepoListQuickPickItem extends QuickPickItem {
|
||||
repositories?: string[];
|
||||
repositoryList?: string;
|
||||
useCustomRepository?: boolean;
|
||||
useCustomRepo?: boolean;
|
||||
useAllReposOfOwner?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -22,6 +24,7 @@ interface RepoListQuickPickItem extends QuickPickItem {
|
||||
export async function getRepositorySelection(): Promise<RepositorySelection> {
|
||||
const quickPickItems = [
|
||||
createCustomRepoQuickPickItem(),
|
||||
createAllReposOfOwnerQuickPickItem(),
|
||||
...createSystemDefinedRepoListsQuickPickItems(),
|
||||
...createUserDefinedRepoListsQuickPickItems(),
|
||||
];
|
||||
@@ -41,13 +44,20 @@ export async function getRepositorySelection(): Promise<RepositorySelection> {
|
||||
} 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 <owner>/<repo> (e.g. github/codeql)');
|
||||
}
|
||||
void logger.log(`Entered repository: ${customRepo}`);
|
||||
return { repositories: [customRepo] };
|
||||
} else if (quickpick?.useAllReposOfOwner) {
|
||||
const owner = await getOwner();
|
||||
if (!owner || !OWNER_REGEX.test(owner)) {
|
||||
throw new Error(`Invalid user or organization: ${owner}`);
|
||||
}
|
||||
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.
|
||||
@@ -61,17 +71,11 @@ export async function getRepositorySelection(): Promise<RepositorySelection> {
|
||||
* @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 owners = repoSelection.owners || [];
|
||||
|
||||
return true;
|
||||
return (repositories.length > 0 || repositoryLists.length > 0 || owners.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 createAllReposOfOwnerQuickPickItem(): RepoListQuickPickItem {
|
||||
return {
|
||||
label: '$(edit) Enter a GitHub user or organization',
|
||||
useAllReposOfOwner: true,
|
||||
alwaysShow: true
|
||||
};
|
||||
}
|
||||
|
||||
async function getCustomRepo(): Promise<string | undefined> {
|
||||
return await window.showInputBox({
|
||||
title: 'Enter a GitHub repository in the format <owner>/<repo> (e.g. github/codeql)',
|
||||
@@ -114,3 +126,10 @@ async function getCustomRepo(): Promise<string | undefined> {
|
||||
ignoreFocusOut: true,
|
||||
});
|
||||
}
|
||||
|
||||
async function getOwner(): Promise<string | undefined> {
|
||||
return await window.showInputBox({
|
||||
title: 'Enter a GitHub user or organization',
|
||||
ignoreFocusOut: true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -304,6 +304,7 @@ async function runRemoteQueriesApiRequest(
|
||||
language,
|
||||
repositories: repoSelection.repositories ?? undefined,
|
||||
repository_lists: repoSelection.repositoryLists ?? undefined,
|
||||
repository_owners: repoSelection.owners ?? undefined,
|
||||
query_pack: queryPackBase64,
|
||||
};
|
||||
|
||||
|
||||
@@ -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.owners).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,64 @@ 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.owners).to.be.undefined;
|
||||
expect(repoSelection.repositoryLists).to.deep.eq(
|
||||
['top_100']
|
||||
);
|
||||
});
|
||||
|
||||
// Test the regex in various "good" cases
|
||||
// Test the owner regex in various "good" cases
|
||||
const goodOwners = [
|
||||
'owner',
|
||||
'owner-with-hyphens',
|
||||
'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 () => {
|
||||
// Fake return values
|
||||
quickPickSpy.resolves(
|
||||
{ useAllReposOfOwner: true }
|
||||
);
|
||||
getRemoteRepositoryListsSpy.returns({}); // no pre-defined repo lists
|
||||
showInputBoxSpy.resolves(owner);
|
||||
|
||||
// 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.owners).to.deep.eq([owner]);
|
||||
});
|
||||
});
|
||||
|
||||
// Test the owner regex in various "bad" cases
|
||||
const badOwners = [
|
||||
'invalid&owner',
|
||||
'owner-with-repo/repo'
|
||||
];
|
||||
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(owner);
|
||||
|
||||
// Function call should throw a UserCancellationException
|
||||
await expect(mod.getRepositorySelection()).to.be.rejectedWith(Error, `Invalid user or organization: ${owner}`);
|
||||
});
|
||||
});
|
||||
|
||||
// Test the repo regex in various "good" cases
|
||||
const goodRepos = [
|
||||
'owner/repo',
|
||||
'owner_with.symbols-/repo.with-symbols_',
|
||||
@@ -82,24 +130,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.owners).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 +158,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');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user