Add support for adding local lists (#1907)

This commit is contained in:
Charis Kyriakou
2022-12-23 11:11:48 +00:00
committed by GitHub
parent 31b80efc56
commit 24a7348fc2
5 changed files with 136 additions and 25 deletions

View File

@@ -129,6 +129,28 @@ export class DbConfigStore extends DisposableObject {
await this.writeConfig(config);
}
public async addLocalList(listName: string): Promise<void> {
if (!this.config) {
throw Error("Cannot add local list if config is not loaded");
}
if (listName === "") {
throw Error("List name cannot be empty");
}
if (this.doesLocalListExist(listName)) {
throw Error(`A local list with the name '${listName}' already exists`);
}
const config: DbConfig = cloneDbConfig(this.config);
config.databases.local.lists.push({
name: listName,
databases: [],
});
await this.writeConfig(config);
}
public async addRemoteList(listName: string): Promise<void> {
if (!this.config) {
throw Error("Cannot add remote list if config is not loaded");

View File

@@ -104,8 +104,8 @@ export class DbManager {
): Promise<void> {
switch (listKind) {
case DbListKind.Local:
// Adding a local list is not supported yet.
throw Error("Cannot add a local list");
await this.dbConfigStore.addLocalList(listName);
break;
case DbListKind.Remote:
await this.dbConfigStore.addRemoteList(listName);
break;

View File

@@ -23,6 +23,10 @@ export interface RemoteDatabaseQuickPickItem extends QuickPickItem {
kind: string;
}
export interface AddListQuickPickItem extends QuickPickItem {
kind: DbListKind;
}
export class DbPanel extends DisposableObject {
private readonly dataProvider: DbTreeDataProvider;
private readonly treeView: TreeView<DbTreeViewItem>;
@@ -179,6 +183,8 @@ export class DbPanel extends DisposableObject {
}
private async addNewList(): Promise<void> {
const listKind = await this.getAddNewListKind();
const listName = await window.showInputBox({
prompt: "Enter a name for the new list",
placeHolder: "example-list",
@@ -187,17 +193,6 @@ export class DbPanel extends DisposableObject {
return;
}
const highlightedItem = await this.getHighlightedDbItem();
// For now: we only support adding remote lists, so if no item is highlighted,
// we default to the "RootRemote" kind.
// In future: if the highlighted item is undefined, we'll show a quick pick where
// a user can select whether to add a remote or local list.
const highlightedItemKind = highlightedItem?.kind || DbItemKind.RootRemote;
const listKind = remoteDbKinds.includes(highlightedItemKind)
? DbListKind.Remote
: DbListKind.Local;
if (this.dbManager.doesListExist(listKind, listName)) {
void showAndLogErrorMessage(`The list '${listName}' already exists`);
return;
@@ -206,6 +201,47 @@ export class DbPanel extends DisposableObject {
await this.dbManager.addNewList(listKind, listName);
}
private async getAddNewListKind(): Promise<DbListKind> {
const highlightedItem = await this.getHighlightedDbItem();
if (highlightedItem) {
return remoteDbKinds.includes(highlightedItem.kind)
? DbListKind.Remote
: DbListKind.Local;
} else {
const quickPickItems = [
{
label: "$(cloud) Remote",
detail: "Add a remote database from GitHub",
alwaysShow: true,
kind: DbListKind.Remote,
},
{
label: "$(database) Local",
detail: "Import a database from the cloud or a local file",
alwaysShow: true,
kind: DbListKind.Local,
},
];
const selectedOption = await window.showQuickPick<AddListQuickPickItem>(
quickPickItems,
{
title: "Add a new database",
ignoreFocusOut: true,
},
);
if (!selectedOption) {
// 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 database list kind selected",
true,
);
}
return selectedOption.kind;
}
}
private async setSelectedItem(treeViewItem: DbTreeViewItem): Promise<void> {
if (treeViewItem.dbItem === undefined) {
throw new Error(

View File

@@ -4,7 +4,11 @@ import { CodeQLExtensionInterface } from "../../../extension";
import { readJson } from "fs-extra";
import * as path from "path";
import { DbConfig } from "../../../databases/config/db-config";
import { RemoteDatabaseQuickPickItem } from "../../../databases/ui/db-panel";
import {
AddListQuickPickItem,
RemoteDatabaseQuickPickItem,
} from "../../../databases/ui/db-panel";
import { DbListKind } from "../../../databases/db-item";
jest.setTimeout(60_000);
@@ -25,6 +29,9 @@ describe("Db panel UI commands", () => {
it("should add new remote db list", async () => {
// Add db list
jest.spyOn(window, "showQuickPick").mockResolvedValue({
kind: DbListKind.Remote,
} as AddListQuickPickItem);
jest.spyOn(window, "showInputBox").mockResolvedValue("my-list-1");
await commands.executeCommand("codeQLDatabasesExperimental.addNewList");
@@ -35,6 +42,21 @@ describe("Db panel UI commands", () => {
expect(dbConfig.databases.remote.repositoryLists[0].name).toBe("my-list-1");
});
it("should add new local db list", async () => {
// Add db list
jest.spyOn(window, "showQuickPick").mockResolvedValue({
kind: DbListKind.Local,
} as AddListQuickPickItem);
jest.spyOn(window, "showInputBox").mockResolvedValue("my-list-1");
await commands.executeCommand("codeQLDatabasesExperimental.addNewList");
// Check db config
const dbConfigFilePath = path.join(storagePath, "workspace-databases.json");
const dbConfig: DbConfig = await readJson(dbConfigFilePath);
expect(dbConfig.databases.local.lists).toHaveLength(1);
expect(dbConfig.databases.local.lists[0].name).toBe("my-list-1");
});
it("should add new remote repository", async () => {
// Add db
jest.spyOn(window, "showQuickPick").mockResolvedValue({

View File

@@ -478,10 +478,7 @@ describe("db panel", () => {
await dbManager.addNewRemoteRepo("owner2/repo2");
// Read the workspace databases JSON file directly to check that the new repo has been added.
// We can't use the dbConfigStore's `read` function here because it depends on the file watcher
// picking up changes, and we don't control the timing of that.
const dbConfigFileContents = await readJSON(dbConfigFilePath);
const dbConfigFileContents = await readDbConfigDirectly();
expect(dbConfigFileContents.databases.remote.repositories.length).toBe(2);
expect(dbConfigFileContents.databases.remote.repositories[1]).toEqual(
"owner2/repo2",
@@ -572,10 +569,7 @@ describe("db panel", () => {
await dbManager.addNewList(DbListKind.Remote, "my-list-2");
// Read the workspace databases JSON file directly to check that the new list has been added.
// We can't use the dbConfigStore's `read` function here because it depends on the file watcher
// picking up changes, and we don't control the timing of that.
const dbConfigFileContents = await readJSON(dbConfigFilePath);
const dbConfigFileContents = await readDbConfigDirectly();
expect(dbConfigFileContents.databases.remote.repositoryLists.length).toBe(
2,
);
@@ -586,12 +580,42 @@ describe("db panel", () => {
});
it("should throw error when adding a new list to a local node", async () => {
const dbConfig: DbConfig = createDbConfig();
const dbConfig: DbConfig = createDbConfig({
localLists: [
{
name: "my-list-1",
databases: [],
},
],
});
await saveDbConfig(dbConfig);
await expect(dbManager.addNewList(DbListKind.Local, "")).rejects.toThrow(
new Error("Cannot add a local list"),
const dbTreeItems = await dbTreeDataProvider.getChildren();
expect(dbTreeItems).toBeTruthy();
const items = dbTreeItems!;
const localRootNode = items[1];
const localUserDefinedLists = localRootNode.children.filter(
(c) => c.dbItem?.kind === DbItemKind.LocalList,
);
const list1 = localRootNode.children.find(
(c) =>
c.dbItem?.kind === DbItemKind.LocalList &&
c.dbItem?.listName === "my-list-1",
);
expect(localUserDefinedLists.length).toBe(1);
expect(localUserDefinedLists[0]).toBe(list1);
await dbManager.addNewList(DbListKind.Local, "my-list-2");
const dbConfigFileContents = await readDbConfigDirectly();
expect(dbConfigFileContents.databases.local.lists.length).toBe(2);
expect(dbConfigFileContents.databases.local.lists[1]).toEqual({
name: "my-list-2",
databases: [],
});
});
});
@@ -838,4 +862,11 @@ describe("db panel", () => {
SELECTED_DB_ITEM_RESOURCE_URI && treeViewItem.contextValue === undefined
);
}
async function readDbConfigDirectly(): Promise<DbConfig> {
// Read the workspace databases JSON file directly to check that the new list has been added.
// We can't use the dbConfigStore's `read` function here because it depends on the file watcher
// picking up changes, and we don't control the timing of that.
return (await readJSON(dbConfigFilePath)) as DbConfig;
}
});