Remove isVariantAnalysisReposPanelEnabled feature flag and old flows (#2096)
This commit is contained in:
@@ -579,10 +579,6 @@ export function isVariantAnalysisLiveResultsEnabled(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
export function isVariantAnalysisReposPanelEnabled(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Settings for mocking the GitHub API.
|
||||
const MOCK_GH_API_SERVER = new Setting("mockGitHubApiServer", ROOT_SETTING);
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { DbConfigStore } from "./config/db-config-store";
|
||||
import { DbManager } from "./db-manager";
|
||||
import { DbPanel } from "./ui/db-panel";
|
||||
import { DbSelectionDecorationProvider } from "./ui/db-selection-decoration-provider";
|
||||
import { isCanary, isVariantAnalysisReposPanelEnabled } from "../config";
|
||||
import { isCanary } from "../config";
|
||||
|
||||
export class DbModule extends DisposableObject {
|
||||
public readonly dbManager: DbManager;
|
||||
@@ -36,7 +36,7 @@ export class DbModule extends DisposableObject {
|
||||
return true;
|
||||
}
|
||||
|
||||
return isCanary() && isVariantAnalysisReposPanelEnabled();
|
||||
return isCanary();
|
||||
}
|
||||
|
||||
private async initialize(app: App): Promise<void> {
|
||||
|
||||
@@ -638,7 +638,7 @@ async function activateWithInstalledDistribution(
|
||||
cliServer,
|
||||
variantAnalysisStorageDir,
|
||||
variantAnalysisResultsManager,
|
||||
dbModule?.dbManager, // the dbModule is only needed when variantAnalysisReposPanel is enabled
|
||||
dbModule?.dbManager,
|
||||
);
|
||||
ctx.subscriptions.push(variantAnalysisManager);
|
||||
ctx.subscriptions.push(variantAnalysisResultsManager);
|
||||
|
||||
@@ -1,12 +1,3 @@
|
||||
import { pathExists as fs_pathExists, stat, readFile } from "fs-extra";
|
||||
import { QuickPickItem, window } from "vscode";
|
||||
import { extLogger } from "../common";
|
||||
import {
|
||||
getRemoteRepositoryLists,
|
||||
getRemoteRepositoryListsPath,
|
||||
isVariantAnalysisReposPanelEnabled,
|
||||
} from "../config";
|
||||
import { OWNER_REGEX, REPO_REGEX } from "../pure/helpers-pure";
|
||||
import { UserCancellationException } from "../commandRunner";
|
||||
import { DbManager } from "../databases/db-manager";
|
||||
import { DbItemKind } from "../databases/db-item";
|
||||
@@ -17,18 +8,6 @@ export interface RepositorySelection {
|
||||
owners?: string[];
|
||||
}
|
||||
|
||||
interface RepoListQuickPickItem extends QuickPickItem {
|
||||
repositories?: string[];
|
||||
repositoryList?: string;
|
||||
useCustomRepo?: boolean;
|
||||
useAllReposOfOwner?: boolean;
|
||||
}
|
||||
|
||||
interface RepoList {
|
||||
label: string;
|
||||
repositories: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the repositories or repository lists to run the query against.
|
||||
* @returns The user selection.
|
||||
@@ -36,101 +15,35 @@ interface RepoList {
|
||||
export async function getRepositorySelection(
|
||||
dbManager?: DbManager,
|
||||
): Promise<RepositorySelection> {
|
||||
if (isVariantAnalysisReposPanelEnabled()) {
|
||||
const selectedDbItem = dbManager?.getSelectedDbItem();
|
||||
if (selectedDbItem) {
|
||||
switch (selectedDbItem.kind) {
|
||||
case DbItemKind.LocalDatabase || DbItemKind.LocalList:
|
||||
const selectedDbItem = dbManager?.getSelectedDbItem();
|
||||
if (selectedDbItem) {
|
||||
switch (selectedDbItem.kind) {
|
||||
case DbItemKind.LocalDatabase || DbItemKind.LocalList:
|
||||
throw new UserCancellationException(
|
||||
"Local databases and lists are not supported yet.",
|
||||
);
|
||||
case DbItemKind.RemoteSystemDefinedList:
|
||||
return { repositoryLists: [selectedDbItem.listName] };
|
||||
case DbItemKind.RemoteUserDefinedList:
|
||||
if (selectedDbItem.repos.length === 0) {
|
||||
throw new UserCancellationException(
|
||||
"Local databases and lists are not supported yet.",
|
||||
"The selected repository list is empty. Please add repositories to it before running a variant analysis.",
|
||||
);
|
||||
case DbItemKind.RemoteSystemDefinedList:
|
||||
return { repositoryLists: [selectedDbItem.listName] };
|
||||
case DbItemKind.RemoteUserDefinedList:
|
||||
if (selectedDbItem.repos.length === 0) {
|
||||
throw new UserCancellationException(
|
||||
"The selected repository list is empty. Please add repositories to it before running a variant analysis.",
|
||||
);
|
||||
} else {
|
||||
return {
|
||||
repositories: selectedDbItem.repos.map(
|
||||
(repo) => repo.repoFullName,
|
||||
),
|
||||
};
|
||||
}
|
||||
case DbItemKind.RemoteOwner:
|
||||
return { owners: [selectedDbItem.ownerName] };
|
||||
case DbItemKind.RemoteRepo:
|
||||
return { repositories: [selectedDbItem.repoFullName] };
|
||||
}
|
||||
} else {
|
||||
throw new UserCancellationException(
|
||||
"Please select a remote database to run the query against.",
|
||||
);
|
||||
} else {
|
||||
return {
|
||||
repositories: selectedDbItem.repos.map((repo) => repo.repoFullName),
|
||||
};
|
||||
}
|
||||
case DbItemKind.RemoteOwner:
|
||||
return { owners: [selectedDbItem.ownerName] };
|
||||
case DbItemKind.RemoteRepo:
|
||||
return { repositories: [selectedDbItem.repoFullName] };
|
||||
}
|
||||
}
|
||||
|
||||
const quickPickItems = [
|
||||
createCustomRepoQuickPickItem(),
|
||||
createAllReposOfOwnerQuickPickItem(),
|
||||
...createSystemDefinedRepoListsQuickPickItems(),
|
||||
...(await createUserDefinedRepoListsQuickPickItems()),
|
||||
];
|
||||
|
||||
const options = {
|
||||
placeHolder:
|
||||
"Select a repository list. You can define repository lists in the `codeQL.variantAnalysis.repositoryLists` setting.",
|
||||
ignoreFocusOut: true,
|
||||
};
|
||||
|
||||
const quickpick = await window.showQuickPick<RepoListQuickPickItem>(
|
||||
quickPickItems,
|
||||
options,
|
||||
throw new UserCancellationException(
|
||||
"Please select a remote database to run the query against.",
|
||||
);
|
||||
|
||||
if (!quickpick) {
|
||||
// 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.
|
||||
throw new UserCancellationException("No repositories selected", true);
|
||||
}
|
||||
|
||||
if (quickpick.repositories?.length) {
|
||||
void extLogger.log(
|
||||
`Selected repositories: ${quickpick.repositories.join(", ")}`,
|
||||
);
|
||||
return { repositories: quickpick.repositories };
|
||||
} else if (quickpick.repositoryList) {
|
||||
void extLogger.log(`Selected repository list: ${quickpick.repositoryList}`);
|
||||
return { repositoryLists: [quickpick.repositoryList] };
|
||||
} else if (quickpick.useCustomRepo) {
|
||||
const customRepo = await getCustomRepo();
|
||||
if (customRepo === undefined) {
|
||||
// The user cancelled, do nothing.
|
||||
throw new UserCancellationException("No repositories selected", true);
|
||||
}
|
||||
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 extLogger.log(`Entered repository: ${customRepo}`);
|
||||
return { repositories: [customRepo] };
|
||||
} else if (quickpick.useAllReposOfOwner) {
|
||||
const owner = await getOwner();
|
||||
if (owner === undefined) {
|
||||
// The user cancelled, do nothing.
|
||||
throw new UserCancellationException("No repositories selected", true);
|
||||
}
|
||||
if (!owner || !OWNER_REGEX.test(owner)) {
|
||||
throw new Error(`Invalid user or organization: ${owner}`);
|
||||
}
|
||||
void extLogger.log(`Entered owner: ${owner}`);
|
||||
return { owners: [owner] };
|
||||
} else {
|
||||
// This means the user has selected something, but there is nothing actually linked to this item. We want to show
|
||||
// this to the user.
|
||||
throw new UserCancellationException("No repositories selected", false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -147,133 +60,3 @@ export function isValidSelection(repoSelection: RepositorySelection): boolean {
|
||||
repositories.length > 0 || repositoryLists.length > 0 || owners.length > 0
|
||||
);
|
||||
}
|
||||
|
||||
function createSystemDefinedRepoListsQuickPickItems(): RepoListQuickPickItem[] {
|
||||
const topNs = [10, 100, 1000];
|
||||
|
||||
return topNs.map(
|
||||
(n) =>
|
||||
({
|
||||
label: `$(star) Top ${n}`,
|
||||
repositoryList: `top_${n}`,
|
||||
alwaysShow: true,
|
||||
} as RepoListQuickPickItem),
|
||||
);
|
||||
}
|
||||
|
||||
async function readExternalRepoLists(): Promise<RepoList[]> {
|
||||
const repoLists: RepoList[] = [];
|
||||
|
||||
const path = getRemoteRepositoryListsPath();
|
||||
if (!path) {
|
||||
return repoLists;
|
||||
}
|
||||
|
||||
await validateExternalRepoListsFile(path);
|
||||
const json = await readExternalRepoListsJson(path);
|
||||
|
||||
for (const [repoListName, repositories] of Object.entries(json)) {
|
||||
if (!Array.isArray(repositories)) {
|
||||
throw Error(
|
||||
"Invalid repository lists file. It should contain an array of repositories for each list.",
|
||||
);
|
||||
}
|
||||
|
||||
repoLists.push({
|
||||
label: repoListName,
|
||||
repositories,
|
||||
});
|
||||
}
|
||||
|
||||
return repoLists;
|
||||
}
|
||||
|
||||
async function validateExternalRepoListsFile(path: string): Promise<void> {
|
||||
const pathExists = await fs_pathExists(path);
|
||||
if (!pathExists) {
|
||||
throw Error(`External repository lists file does not exist at ${path}`);
|
||||
}
|
||||
|
||||
const pathStat = await stat(path);
|
||||
if (pathStat.isDirectory()) {
|
||||
throw Error(
|
||||
"External repository lists path should not point to a directory",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function readExternalRepoListsJson(
|
||||
path: string,
|
||||
): Promise<Record<string, unknown>> {
|
||||
let json;
|
||||
|
||||
try {
|
||||
const fileContents = await readFile(path, "utf8");
|
||||
json = await JSON.parse(fileContents);
|
||||
} catch (error) {
|
||||
throw Error("Invalid repository lists file. It should contain valid JSON.");
|
||||
}
|
||||
|
||||
if (Array.isArray(json)) {
|
||||
throw Error(
|
||||
"Invalid repository lists file. It should be an object mapping names to a list of repositories.",
|
||||
);
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
function readRepoListsFromSettings(): RepoList[] {
|
||||
const repoLists = getRemoteRepositoryLists();
|
||||
if (!repoLists) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Object.entries(repoLists).map<RepoList>(([label, repositories]) => ({
|
||||
label,
|
||||
repositories,
|
||||
}));
|
||||
}
|
||||
|
||||
async function createUserDefinedRepoListsQuickPickItems(): Promise<
|
||||
RepoListQuickPickItem[]
|
||||
> {
|
||||
const repoListsFromSetings = readRepoListsFromSettings();
|
||||
const repoListsFromExternalFile = await readExternalRepoLists();
|
||||
|
||||
return [...repoListsFromSetings, ...repoListsFromExternalFile];
|
||||
}
|
||||
|
||||
function createCustomRepoQuickPickItem(): RepoListQuickPickItem {
|
||||
return {
|
||||
label: "$(edit) Enter a GitHub repository",
|
||||
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)",
|
||||
placeHolder: "<owner>/<repo>",
|
||||
prompt:
|
||||
"Tip: you can save frequently used repositories in the `codeQL.variantAnalysis.repositoryLists` setting",
|
||||
ignoreFocusOut: true,
|
||||
});
|
||||
}
|
||||
|
||||
async function getOwner(): Promise<string | undefined> {
|
||||
return await window.showInputBox({
|
||||
title: "Enter a GitHub user or organization",
|
||||
ignoreFocusOut: true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -223,7 +223,7 @@ export async function prepareRemoteQueryRun(
|
||||
uri: Uri | undefined,
|
||||
progress: ProgressCallback,
|
||||
token: CancellationToken,
|
||||
dbManager?: DbManager, // the dbManager is only needed when variantAnalysisReposPanel is enabled
|
||||
dbManager?: DbManager,
|
||||
): Promise<PreparedRemoteQuery> {
|
||||
if (!uri?.fsPath.endsWith(".ql")) {
|
||||
throw new UserCancellationException("Not a CodeQL query file.");
|
||||
|
||||
@@ -60,7 +60,6 @@ import {
|
||||
} from "../pure/variant-analysis-filter-sort";
|
||||
import { URLSearchParams } from "url";
|
||||
import { DbManager } from "../databases/db-manager";
|
||||
import { isVariantAnalysisReposPanelEnabled } from "../config";
|
||||
import { App } from "../common/app";
|
||||
import { redactableError } from "../pure/errors";
|
||||
|
||||
@@ -106,7 +105,7 @@ export class VariantAnalysisManager
|
||||
private readonly cliServer: CodeQLCliServer,
|
||||
private readonly storagePath: string,
|
||||
private readonly variantAnalysisResultsManager: VariantAnalysisResultsManager,
|
||||
private readonly dbManager?: DbManager, // the dbManager is only needed when variantAnalysisReposPanel is enabled
|
||||
private readonly dbManager?: DbManager,
|
||||
) {
|
||||
super();
|
||||
this.variantAnalysisMonitor = this.push(
|
||||
@@ -635,25 +634,15 @@ export class VariantAnalysisManager
|
||||
return;
|
||||
}
|
||||
|
||||
let text: string[];
|
||||
if (isVariantAnalysisReposPanelEnabled()) {
|
||||
text = [
|
||||
"{",
|
||||
` "name": "new-repo-list",`,
|
||||
` "repositories": [`,
|
||||
...fullNames.slice(0, -1).map((repo) => ` "${repo}",`),
|
||||
` "${fullNames[fullNames.length - 1]}"`,
|
||||
` ]`,
|
||||
"}",
|
||||
];
|
||||
} else {
|
||||
text = [
|
||||
'"new-repo-list": [',
|
||||
...fullNames.slice(0, -1).map((repo) => ` "${repo}",`),
|
||||
` "${fullNames[fullNames.length - 1]}"`,
|
||||
"]",
|
||||
];
|
||||
}
|
||||
const text = [
|
||||
"{",
|
||||
` "name": "new-repo-list",`,
|
||||
` "repositories": [`,
|
||||
...fullNames.slice(0, -1).map((repo) => ` "${repo}",`),
|
||||
` "${fullNames[fullNames.length - 1]}"`,
|
||||
` ]`,
|
||||
"}",
|
||||
];
|
||||
|
||||
await env.clipboard.writeText(text.join(EOL));
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
} from "vscode";
|
||||
import { CodeQLExtensionInterface } from "../../../../src/extension";
|
||||
import { extLogger } from "../../../../src/common";
|
||||
import * as config from "../../../../src/config";
|
||||
import {
|
||||
setRemoteControllerRepo,
|
||||
setRemoteRepositoryLists,
|
||||
@@ -915,137 +914,64 @@ describe("Variant Analysis Manager", () => {
|
||||
expect(writeTextStub).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
describe("variantAnalysisReposPanel true", () => {
|
||||
beforeEach(() => {
|
||||
jest
|
||||
.spyOn(config, "isVariantAnalysisReposPanelEnabled")
|
||||
.mockReturnValue(true);
|
||||
});
|
||||
it("should be valid JSON when put in object", async () => {
|
||||
await variantAnalysisManager.copyRepoListToClipboard(
|
||||
variantAnalysis.id,
|
||||
);
|
||||
|
||||
it("should be valid JSON when put in object", async () => {
|
||||
await variantAnalysisManager.copyRepoListToClipboard(
|
||||
variantAnalysis.id,
|
||||
);
|
||||
const text = writeTextStub.mock.calls[0][0];
|
||||
|
||||
const text = writeTextStub.mock.calls[0][0];
|
||||
const parsed = JSON.parse(`${text}`);
|
||||
|
||||
const parsed = JSON.parse(`${text}`);
|
||||
|
||||
expect(parsed).toEqual({
|
||||
name: "new-repo-list",
|
||||
repositories: [
|
||||
scannedRepos[4].repository.fullName,
|
||||
scannedRepos[2].repository.fullName,
|
||||
scannedRepos[0].repository.fullName,
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should use the sort key", async () => {
|
||||
await variantAnalysisManager.copyRepoListToClipboard(
|
||||
variantAnalysis.id,
|
||||
{
|
||||
...defaultFilterSortState,
|
||||
sortKey: SortKey.ResultsCount,
|
||||
},
|
||||
);
|
||||
|
||||
const text = writeTextStub.mock.calls[0][0];
|
||||
|
||||
const parsed = JSON.parse(`${text}`);
|
||||
|
||||
expect(parsed).toEqual({
|
||||
name: "new-repo-list",
|
||||
repositories: [
|
||||
scannedRepos[2].repository.fullName,
|
||||
scannedRepos[0].repository.fullName,
|
||||
scannedRepos[4].repository.fullName,
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should use the search value", async () => {
|
||||
await variantAnalysisManager.copyRepoListToClipboard(
|
||||
variantAnalysis.id,
|
||||
{
|
||||
...defaultFilterSortState,
|
||||
searchValue: "ban",
|
||||
},
|
||||
);
|
||||
|
||||
const text = writeTextStub.mock.calls[0][0];
|
||||
|
||||
const parsed = JSON.parse(`${text}`);
|
||||
|
||||
expect(parsed).toEqual({
|
||||
name: "new-repo-list",
|
||||
repositories: [scannedRepos[4].repository.fullName],
|
||||
});
|
||||
expect(parsed).toEqual({
|
||||
name: "new-repo-list",
|
||||
repositories: [
|
||||
scannedRepos[4].repository.fullName,
|
||||
scannedRepos[2].repository.fullName,
|
||||
scannedRepos[0].repository.fullName,
|
||||
],
|
||||
});
|
||||
});
|
||||
describe("variantAnalysisReposPanel false", () => {
|
||||
beforeEach(() => {
|
||||
jest
|
||||
.spyOn(config, "isVariantAnalysisReposPanelEnabled")
|
||||
.mockReturnValue(false);
|
||||
|
||||
it("should use the sort key", async () => {
|
||||
await variantAnalysisManager.copyRepoListToClipboard(
|
||||
variantAnalysis.id,
|
||||
{
|
||||
...defaultFilterSortState,
|
||||
sortKey: SortKey.ResultsCount,
|
||||
},
|
||||
);
|
||||
|
||||
const text = writeTextStub.mock.calls[0][0];
|
||||
|
||||
const parsed = JSON.parse(`${text}`);
|
||||
|
||||
expect(parsed).toEqual({
|
||||
name: "new-repo-list",
|
||||
repositories: [
|
||||
scannedRepos[2].repository.fullName,
|
||||
scannedRepos[0].repository.fullName,
|
||||
scannedRepos[4].repository.fullName,
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should be valid JSON when put in object", async () => {
|
||||
await variantAnalysisManager.copyRepoListToClipboard(
|
||||
variantAnalysis.id,
|
||||
);
|
||||
it("should use the search value", async () => {
|
||||
await variantAnalysisManager.copyRepoListToClipboard(
|
||||
variantAnalysis.id,
|
||||
{
|
||||
...defaultFilterSortState,
|
||||
searchValue: "ban",
|
||||
},
|
||||
);
|
||||
|
||||
const text = writeTextStub.mock.calls[0][0];
|
||||
const text = writeTextStub.mock.calls[0][0];
|
||||
|
||||
const parsed = JSON.parse(`{${text}}`);
|
||||
const parsed = JSON.parse(`${text}`);
|
||||
|
||||
expect(parsed).toEqual({
|
||||
"new-repo-list": [
|
||||
scannedRepos[4].repository.fullName,
|
||||
scannedRepos[2].repository.fullName,
|
||||
scannedRepos[0].repository.fullName,
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should use the sort key", async () => {
|
||||
await variantAnalysisManager.copyRepoListToClipboard(
|
||||
variantAnalysis.id,
|
||||
{
|
||||
...defaultFilterSortState,
|
||||
sortKey: SortKey.ResultsCount,
|
||||
},
|
||||
);
|
||||
|
||||
const text = writeTextStub.mock.calls[0][0];
|
||||
|
||||
const parsed = JSON.parse(`{${text}}`);
|
||||
|
||||
expect(parsed).toEqual({
|
||||
"new-repo-list": [
|
||||
scannedRepos[2].repository.fullName,
|
||||
scannedRepos[0].repository.fullName,
|
||||
scannedRepos[4].repository.fullName,
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should use the search value", async () => {
|
||||
await variantAnalysisManager.copyRepoListToClipboard(
|
||||
variantAnalysis.id,
|
||||
{
|
||||
...defaultFilterSortState,
|
||||
searchValue: "ban",
|
||||
},
|
||||
);
|
||||
|
||||
const text = writeTextStub.mock.calls[0][0];
|
||||
|
||||
const parsed = JSON.parse(`{${text}}`);
|
||||
|
||||
expect(parsed).toEqual({
|
||||
"new-repo-list": [scannedRepos[4].repository.fullName],
|
||||
});
|
||||
expect(parsed).toEqual({
|
||||
name: "new-repo-list",
|
||||
repositories: [scannedRepos[4].repository.fullName],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
import { QuickPickItem, window } from "vscode";
|
||||
import { join } from "path";
|
||||
import { DirectoryResult } from "tmp-promise";
|
||||
import * as tmp from "tmp-promise";
|
||||
import { ensureDir, writeFile, writeJson } from "fs-extra";
|
||||
import { UserCancellationException } from "../../../../src/commandRunner";
|
||||
|
||||
import * as config from "../../../../src/config";
|
||||
import { getRepositorySelection } from "../../../../src/variant-analysis/repository-selection";
|
||||
import { DbManager } from "../../../../src/databases/db-manager";
|
||||
import {
|
||||
@@ -15,450 +7,98 @@ import {
|
||||
} from "../../../../src/databases/db-item";
|
||||
|
||||
describe("repository selection", () => {
|
||||
describe("variantAnalysisReposPanel true", () => {
|
||||
beforeEach(() => {
|
||||
jest
|
||||
.spyOn(config, "isVariantAnalysisReposPanelEnabled")
|
||||
.mockReturnValue(true);
|
||||
});
|
||||
it("should throw error when no database item is selected", async () => {
|
||||
const dbManager = setUpDbManager(undefined);
|
||||
|
||||
it("should throw error when no database item is selected", async () => {
|
||||
const dbManager = setUpDbManager(undefined);
|
||||
|
||||
await expect(getRepositorySelection(dbManager)).rejects.toThrow(
|
||||
"Please select a remote database to run the query against.",
|
||||
);
|
||||
});
|
||||
|
||||
it("should log error when local database item is selected", async () => {
|
||||
const dbManager = setUpDbManager({
|
||||
kind: DbItemKind.LocalDatabase,
|
||||
} as DbItem);
|
||||
|
||||
await expect(getRepositorySelection(dbManager)).rejects.toThrow(
|
||||
"Local databases and lists are not supported yet.",
|
||||
);
|
||||
});
|
||||
|
||||
it("should log an error when an empty remote user defined list is selected", async () => {
|
||||
const dbManager = setUpDbManager({
|
||||
kind: DbItemKind.RemoteUserDefinedList,
|
||||
repos: [] as RemoteRepoDbItem[],
|
||||
} as DbItem);
|
||||
|
||||
await expect(getRepositorySelection(dbManager)).rejects.toThrow(
|
||||
"The selected repository list is empty. Please add repositories to it before running a variant analysis.",
|
||||
);
|
||||
});
|
||||
|
||||
it("should return correct selection when remote system defined list is selected", async () => {
|
||||
const dbManager = setUpDbManager({
|
||||
kind: DbItemKind.RemoteSystemDefinedList,
|
||||
listName: "top_10",
|
||||
} as DbItem);
|
||||
|
||||
const repoSelection = await getRepositorySelection(dbManager);
|
||||
|
||||
expect(repoSelection.repositoryLists).toEqual(["top_10"]);
|
||||
expect(repoSelection.owners).toBeUndefined();
|
||||
expect(repoSelection.repositories).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should return correct selection when remote user defined list is selected", async () => {
|
||||
const dbManager = setUpDbManager({
|
||||
kind: DbItemKind.RemoteUserDefinedList,
|
||||
repos: [
|
||||
{ repoFullName: "owner1/repo1" },
|
||||
{ repoFullName: "owner1/repo2" },
|
||||
],
|
||||
} as DbItem);
|
||||
|
||||
const repoSelection = await getRepositorySelection(dbManager);
|
||||
|
||||
expect(repoSelection.repositoryLists).toBeUndefined();
|
||||
expect(repoSelection.owners).toBeUndefined();
|
||||
expect(repoSelection.repositories).toEqual([
|
||||
"owner1/repo1",
|
||||
"owner1/repo2",
|
||||
]);
|
||||
});
|
||||
|
||||
it("should return correct selection when remote owner is selected", async () => {
|
||||
const dbManager = setUpDbManager({
|
||||
kind: DbItemKind.RemoteOwner,
|
||||
ownerName: "owner2",
|
||||
} as DbItem);
|
||||
|
||||
const repoSelection = await getRepositorySelection(dbManager);
|
||||
|
||||
expect(repoSelection.repositoryLists).toBeUndefined();
|
||||
expect(repoSelection.owners).toEqual(["owner2"]);
|
||||
expect(repoSelection.repositories).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should return correct selection when remote repo is selected", async () => {
|
||||
const dbManager = setUpDbManager({
|
||||
kind: DbItemKind.RemoteRepo,
|
||||
repoFullName: "owner1/repo2",
|
||||
} as DbItem);
|
||||
|
||||
const repoSelection = await getRepositorySelection(dbManager);
|
||||
|
||||
expect(repoSelection.repositoryLists).toBeUndefined();
|
||||
expect(repoSelection.owners).toBeUndefined();
|
||||
expect(repoSelection.repositories).toEqual(["owner1/repo2"]);
|
||||
});
|
||||
|
||||
function setUpDbManager(response: DbItem | undefined): DbManager {
|
||||
return {
|
||||
getSelectedDbItem: jest.fn(() => {
|
||||
return response;
|
||||
}),
|
||||
} as any as DbManager;
|
||||
}
|
||||
await expect(getRepositorySelection(dbManager)).rejects.toThrow(
|
||||
"Please select a remote database to run the query against.",
|
||||
);
|
||||
});
|
||||
|
||||
describe("variantAnalysisReposPanel false", () => {
|
||||
let quickPickSpy: jest.SpiedFunction<typeof window.showQuickPick>;
|
||||
let showInputBoxSpy: jest.SpiedFunction<typeof window.showInputBox>;
|
||||
it("should log error when local database item is selected", async () => {
|
||||
const dbManager = setUpDbManager({
|
||||
kind: DbItemKind.LocalDatabase,
|
||||
} as DbItem);
|
||||
|
||||
let getRemoteRepositoryListsSpy: jest.SpiedFunction<
|
||||
typeof config.getRemoteRepositoryLists
|
||||
>;
|
||||
let getRemoteRepositoryListsPathSpy: jest.SpiedFunction<
|
||||
typeof config.getRemoteRepositoryListsPath
|
||||
>;
|
||||
|
||||
beforeEach(() => {
|
||||
quickPickSpy = jest
|
||||
.spyOn(window, "showQuickPick")
|
||||
.mockResolvedValue(undefined);
|
||||
showInputBoxSpy = jest
|
||||
.spyOn(window, "showInputBox")
|
||||
.mockResolvedValue(undefined);
|
||||
|
||||
getRemoteRepositoryListsSpy = jest
|
||||
.spyOn(config, "getRemoteRepositoryLists")
|
||||
.mockReturnValue(undefined);
|
||||
getRemoteRepositoryListsPathSpy = jest
|
||||
.spyOn(config, "getRemoteRepositoryListsPath")
|
||||
.mockReturnValue(undefined);
|
||||
|
||||
jest
|
||||
.spyOn(config, "isVariantAnalysisReposPanelEnabled")
|
||||
.mockReturnValue(false);
|
||||
});
|
||||
|
||||
describe("repo lists from settings", () => {
|
||||
it("should allow selection from repo lists from your pre-defined config", async () => {
|
||||
// Fake return values
|
||||
quickPickSpy.mockResolvedValue({
|
||||
repositories: ["foo/bar", "foo/baz"],
|
||||
} as unknown as QuickPickItem);
|
||||
getRemoteRepositoryListsSpy.mockReturnValue({
|
||||
list1: ["foo/bar", "foo/baz"],
|
||||
list2: [],
|
||||
});
|
||||
|
||||
// Make the function call
|
||||
const repoSelection = await getRepositorySelection();
|
||||
|
||||
// Check that the return value is correct
|
||||
expect(repoSelection.repositoryLists).toBeUndefined();
|
||||
expect(repoSelection.owners).toBeUndefined();
|
||||
expect(repoSelection.repositories).toEqual(["foo/bar", "foo/baz"]);
|
||||
});
|
||||
|
||||
it("should return an error for an empty repository list", async () => {
|
||||
// Fake return values
|
||||
quickPickSpy.mockResolvedValue({
|
||||
repositories: [],
|
||||
} as unknown as QuickPickItem);
|
||||
getRemoteRepositoryListsSpy.mockReturnValue({
|
||||
list1: ["foo/bar", "foo/baz"],
|
||||
list2: [],
|
||||
});
|
||||
|
||||
await expect(getRepositorySelection()).rejects.toThrow(
|
||||
"No repositories selected",
|
||||
);
|
||||
await expect(getRepositorySelection()).rejects.toThrow(
|
||||
UserCancellationException,
|
||||
);
|
||||
await expect(getRepositorySelection()).rejects.toHaveProperty(
|
||||
"silent",
|
||||
false,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("system level repo lists", () => {
|
||||
it("should allow selection from repo lists defined at the system level", async () => {
|
||||
// Fake return values
|
||||
quickPickSpy.mockResolvedValue({
|
||||
repositoryList: "top_100",
|
||||
} as unknown as QuickPickItem);
|
||||
getRemoteRepositoryListsSpy.mockReturnValue({
|
||||
list1: ["foo/bar", "foo/baz"],
|
||||
list2: [],
|
||||
});
|
||||
|
||||
// Make the function call
|
||||
const repoSelection = await getRepositorySelection();
|
||||
|
||||
// Check that the return value is correct
|
||||
expect(repoSelection.repositories).toBeUndefined();
|
||||
expect(repoSelection.owners).toBeUndefined();
|
||||
expect(repoSelection.repositoryLists).toEqual(["top_100"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("custom owner", () => {
|
||||
// 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.mockResolvedValue({
|
||||
useAllReposOfOwner: true,
|
||||
} as unknown as QuickPickItem);
|
||||
getRemoteRepositoryListsSpy.mockReturnValue({}); // no pre-defined repo lists
|
||||
showInputBoxSpy.mockResolvedValue(owner);
|
||||
|
||||
// Make the function call
|
||||
const repoSelection = await getRepositorySelection();
|
||||
|
||||
// Check that the return value is correct
|
||||
expect(repoSelection.repositories).toBeUndefined();
|
||||
expect(repoSelection.repositoryLists).toBeUndefined();
|
||||
expect(repoSelection.owners).toEqual([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.mockResolvedValue({
|
||||
useAllReposOfOwner: true,
|
||||
} as unknown as QuickPickItem);
|
||||
getRemoteRepositoryListsSpy.mockReturnValue({}); // no pre-defined repo lists
|
||||
showInputBoxSpy.mockResolvedValue(owner);
|
||||
|
||||
// Function call should throw a UserCancellationException
|
||||
await expect(getRepositorySelection()).rejects.toThrow(
|
||||
`Invalid user or organization: ${owner}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("should be ok for the user to change their mind", async () => {
|
||||
quickPickSpy.mockResolvedValue({
|
||||
useAllReposOfOwner: true,
|
||||
} as unknown as QuickPickItem);
|
||||
getRemoteRepositoryListsSpy.mockReturnValue({});
|
||||
|
||||
// The user pressed escape to cancel the operation
|
||||
showInputBoxSpy.mockResolvedValue(undefined);
|
||||
|
||||
await expect(getRepositorySelection()).rejects.toThrow(
|
||||
"No repositories selected",
|
||||
);
|
||||
await expect(getRepositorySelection()).rejects.toThrow(
|
||||
UserCancellationException,
|
||||
);
|
||||
await expect(getRepositorySelection()).rejects.toHaveProperty(
|
||||
"silent",
|
||||
true,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("custom repo", () => {
|
||||
// Test the repo regex in various "good" cases
|
||||
const goodRepos = [
|
||||
"owner/repo",
|
||||
"owner_with.symbols-/repo.with-symbols_",
|
||||
"ownerWithNumbers58/repoWithNumbers37",
|
||||
];
|
||||
goodRepos.forEach((repo) => {
|
||||
it(`should run on a valid repo that you enter in the text box: ${repo}`, async () => {
|
||||
// Fake return values
|
||||
quickPickSpy.mockResolvedValue({
|
||||
useCustomRepo: true,
|
||||
} as unknown as QuickPickItem);
|
||||
getRemoteRepositoryListsSpy.mockReturnValue({}); // no pre-defined repo lists
|
||||
showInputBoxSpy.mockResolvedValue(repo);
|
||||
|
||||
// Make the function call
|
||||
const repoSelection = await getRepositorySelection();
|
||||
|
||||
// Check that the return value is correct
|
||||
expect(repoSelection.repositoryLists).toBeUndefined();
|
||||
expect(repoSelection.owners).toBeUndefined();
|
||||
expect(repoSelection.repositories).toEqual([repo]);
|
||||
});
|
||||
});
|
||||
|
||||
// Test the repo regex in various "bad" cases
|
||||
const badRepos = [
|
||||
"invalid*owner/repo",
|
||||
"owner/repo+some&invalid&stuff",
|
||||
"owner-with-no-repo/",
|
||||
"/repo-with-no-owner",
|
||||
];
|
||||
badRepos.forEach((repo) => {
|
||||
it(`should show an error message if you enter an invalid repo in the text box: ${repo}`, async () => {
|
||||
// Fake return values
|
||||
quickPickSpy.mockResolvedValue({
|
||||
useCustomRepo: true,
|
||||
} as unknown as QuickPickItem);
|
||||
getRemoteRepositoryListsSpy.mockReturnValue({}); // no pre-defined repo lists
|
||||
showInputBoxSpy.mockResolvedValue(repo);
|
||||
|
||||
// Function call should throw a UserCancellationException
|
||||
await expect(getRepositorySelection()).rejects.toThrow(
|
||||
"Invalid repository format",
|
||||
);
|
||||
await expect(getRepositorySelection()).rejects.toThrow(
|
||||
UserCancellationException,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("should be ok for the user to change their mind", async () => {
|
||||
quickPickSpy.mockResolvedValue({
|
||||
useCustomRepo: true,
|
||||
} as unknown as QuickPickItem);
|
||||
getRemoteRepositoryListsSpy.mockReturnValue({});
|
||||
|
||||
// The user pressed escape to cancel the operation
|
||||
showInputBoxSpy.mockResolvedValue(undefined);
|
||||
|
||||
await expect(getRepositorySelection()).rejects.toThrow(
|
||||
"No repositories selected",
|
||||
);
|
||||
await expect(getRepositorySelection()).rejects.toThrow(
|
||||
UserCancellationException,
|
||||
);
|
||||
await expect(getRepositorySelection()).rejects.toHaveProperty(
|
||||
"silent",
|
||||
true,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("external repository lists file", () => {
|
||||
let directory: DirectoryResult;
|
||||
|
||||
beforeEach(async () => {
|
||||
directory = await tmp.dir({
|
||||
unsafeCleanup: true,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await directory.cleanup();
|
||||
});
|
||||
|
||||
it("should fail if path does not exist", async () => {
|
||||
const nonExistingFile = join(directory.path, "non-existing-file.json");
|
||||
getRemoteRepositoryListsPathSpy.mockReturnValue(nonExistingFile);
|
||||
|
||||
await expect(getRepositorySelection()).rejects.toThrow(
|
||||
`External repository lists file does not exist at ${nonExistingFile}`,
|
||||
);
|
||||
});
|
||||
|
||||
it("should fail if path points to directory", async () => {
|
||||
const existingDirectory = join(directory.path, "directory");
|
||||
await ensureDir(existingDirectory);
|
||||
getRemoteRepositoryListsPathSpy.mockReturnValue(existingDirectory);
|
||||
|
||||
await expect(getRepositorySelection()).rejects.toThrow(
|
||||
"External repository lists path should not point to a directory",
|
||||
);
|
||||
});
|
||||
|
||||
it("should fail if file does not have valid JSON", async () => {
|
||||
const existingFile = join(directory.path, "repository-lists.json");
|
||||
await writeFile(existingFile, "not-json");
|
||||
getRemoteRepositoryListsPathSpy.mockReturnValue(existingFile);
|
||||
|
||||
await expect(getRepositorySelection()).rejects.toThrow(
|
||||
"Invalid repository lists file. It should contain valid JSON.",
|
||||
);
|
||||
});
|
||||
|
||||
it("should fail if file contains array", async () => {
|
||||
const existingFile = join(directory.path, "repository-lists.json");
|
||||
await writeJson(existingFile, []);
|
||||
getRemoteRepositoryListsPathSpy.mockReturnValue(existingFile);
|
||||
|
||||
await expect(getRepositorySelection()).rejects.toThrow(
|
||||
"Invalid repository lists file. It should be an object mapping names to a list of repositories.",
|
||||
);
|
||||
});
|
||||
|
||||
it("should fail if file does not contain repo lists in the right format", async () => {
|
||||
const existingFile = join(directory.path, "repository-lists.json");
|
||||
const repoLists = {
|
||||
list1: "owner1/repo1",
|
||||
};
|
||||
await writeJson(existingFile, repoLists);
|
||||
getRemoteRepositoryListsPathSpy.mockReturnValue(existingFile);
|
||||
|
||||
await expect(getRepositorySelection()).rejects.toThrow(
|
||||
"Invalid repository lists file. It should contain an array of repositories for each list.",
|
||||
);
|
||||
});
|
||||
|
||||
it("should get repo lists from file", async () => {
|
||||
const existingFile = join(directory.path, "repository-lists.json");
|
||||
const repoLists = {
|
||||
list1: ["owner1/repo1", "owner2/repo2"],
|
||||
list2: ["owner3/repo3"],
|
||||
};
|
||||
await writeJson(existingFile, repoLists);
|
||||
getRemoteRepositoryListsPathSpy.mockReturnValue(existingFile);
|
||||
getRemoteRepositoryListsSpy.mockReturnValue({
|
||||
list3: ["onwer4/repo4"],
|
||||
list4: [],
|
||||
});
|
||||
|
||||
quickPickSpy.mockResolvedValue({
|
||||
repositories: ["owner3/repo3"],
|
||||
} as unknown as QuickPickItem);
|
||||
|
||||
const repoSelection = await getRepositorySelection();
|
||||
|
||||
expect(repoSelection.repositoryLists).toBeUndefined();
|
||||
expect(repoSelection.owners).toBeUndefined();
|
||||
expect(repoSelection.repositories).toEqual(["owner3/repo3"]);
|
||||
});
|
||||
});
|
||||
|
||||
it("should allow the user to cancel", async () => {
|
||||
// Fake return values
|
||||
quickPickSpy.mockResolvedValue(undefined);
|
||||
|
||||
await expect(getRepositorySelection()).rejects.toThrow(
|
||||
"No repositories selected",
|
||||
);
|
||||
await expect(getRepositorySelection()).rejects.toThrow(
|
||||
UserCancellationException,
|
||||
);
|
||||
await expect(getRepositorySelection()).rejects.toHaveProperty(
|
||||
"silent",
|
||||
true,
|
||||
);
|
||||
});
|
||||
await expect(getRepositorySelection(dbManager)).rejects.toThrow(
|
||||
"Local databases and lists are not supported yet.",
|
||||
);
|
||||
});
|
||||
|
||||
it("should log an error when an empty remote user defined list is selected", async () => {
|
||||
const dbManager = setUpDbManager({
|
||||
kind: DbItemKind.RemoteUserDefinedList,
|
||||
repos: [] as RemoteRepoDbItem[],
|
||||
} as DbItem);
|
||||
|
||||
await expect(getRepositorySelection(dbManager)).rejects.toThrow(
|
||||
"The selected repository list is empty. Please add repositories to it before running a variant analysis.",
|
||||
);
|
||||
});
|
||||
|
||||
it("should return correct selection when remote system defined list is selected", async () => {
|
||||
const dbManager = setUpDbManager({
|
||||
kind: DbItemKind.RemoteSystemDefinedList,
|
||||
listName: "top_10",
|
||||
} as DbItem);
|
||||
|
||||
const repoSelection = await getRepositorySelection(dbManager);
|
||||
|
||||
expect(repoSelection.repositoryLists).toEqual(["top_10"]);
|
||||
expect(repoSelection.owners).toBeUndefined();
|
||||
expect(repoSelection.repositories).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should return correct selection when remote user defined list is selected", async () => {
|
||||
const dbManager = setUpDbManager({
|
||||
kind: DbItemKind.RemoteUserDefinedList,
|
||||
repos: [
|
||||
{ repoFullName: "owner1/repo1" },
|
||||
{ repoFullName: "owner1/repo2" },
|
||||
],
|
||||
} as DbItem);
|
||||
|
||||
const repoSelection = await getRepositorySelection(dbManager);
|
||||
|
||||
expect(repoSelection.repositoryLists).toBeUndefined();
|
||||
expect(repoSelection.owners).toBeUndefined();
|
||||
expect(repoSelection.repositories).toEqual([
|
||||
"owner1/repo1",
|
||||
"owner1/repo2",
|
||||
]);
|
||||
});
|
||||
|
||||
it("should return correct selection when remote owner is selected", async () => {
|
||||
const dbManager = setUpDbManager({
|
||||
kind: DbItemKind.RemoteOwner,
|
||||
ownerName: "owner2",
|
||||
} as DbItem);
|
||||
|
||||
const repoSelection = await getRepositorySelection(dbManager);
|
||||
|
||||
expect(repoSelection.repositoryLists).toBeUndefined();
|
||||
expect(repoSelection.owners).toEqual(["owner2"]);
|
||||
expect(repoSelection.repositories).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should return correct selection when remote repo is selected", async () => {
|
||||
const dbManager = setUpDbManager({
|
||||
kind: DbItemKind.RemoteRepo,
|
||||
repoFullName: "owner1/repo2",
|
||||
} as DbItem);
|
||||
|
||||
const repoSelection = await getRepositorySelection(dbManager);
|
||||
|
||||
expect(repoSelection.repositoryLists).toBeUndefined();
|
||||
expect(repoSelection.owners).toBeUndefined();
|
||||
expect(repoSelection.repositories).toEqual(["owner1/repo2"]);
|
||||
});
|
||||
|
||||
function setUpDbManager(response: DbItem | undefined): DbManager {
|
||||
return {
|
||||
getSelectedDbItem: jest.fn(() => {
|
||||
return response;
|
||||
}),
|
||||
} as any as DbManager;
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user