Merge pull request #1925 from github/nora/add-remove-context-action

Add remove context menu action
This commit is contained in:
Nora
2023-01-10 09:09:51 +01:00
committed by GitHub
6 changed files with 310 additions and 3 deletions

View File

@@ -66,6 +66,7 @@
"onCommand:codeQLDatabasesExperimental.setSelectedItemContextMenu",
"onCommand:codeQLDatabasesExperimental.renameItemContextMenu",
"onCommand:codeQLDatabasesExperimental.openOnGitHubContextMenu",
"onCommand:codeQLDatabasesExperimental.removeItemContextMenu",
"onCommand:codeQL.quickQuery",
"onCommand:codeQL.restartQueryServer",
"onWebviewPanel:resultsView",
@@ -392,6 +393,10 @@
"command": "codeQLDatabasesExperimental.openOnGitHubContextMenu",
"title": "Open on GitHub"
},
{
"command": "codeQLDatabasesExperimental.removeItemContextMenu",
"title": "Remove"
},
{
"command": "codeQLDatabases.chooseDatabaseFolder",
"title": "Choose Database from Folder",
@@ -791,6 +796,10 @@
}
],
"view/item/context": [
{
"command": "codeQLDatabasesExperimental.removeItemContextMenu",
"when": "view == codeQLDatabasesExperimental && viewItem =~ /canBeRemoved/"
},
{
"command": "codeQLDatabasesExperimental.setSelectedItemContextMenu",
"when": "view == codeQLDatabasesExperimental && viewItem =~ /canBeSelected/"
@@ -1043,6 +1052,10 @@
"command": "codeQLDatabasesExperimental.openOnGitHubContextMenu",
"when": "false"
},
{
"command": "codeQLDatabasesExperimental.removeItemContextMenu",
"when": "false"
},
{
"command": "codeQLDatabases.setCurrentDatabase",
"when": "false"

View File

@@ -7,6 +7,7 @@ import {
renameLocalList,
renameRemoteList,
SelectedDbItem,
SelectedDbItemKind,
} from "./db-config";
import * as chokidar from "chokidar";
import { DisposableObject, DisposeHandler } from "../../pure/disposable-object";
@@ -22,7 +23,13 @@ import {
LocalDatabaseDbItem,
LocalListDbItem,
RemoteUserDefinedListDbItem,
DbItem,
DbItemKind,
} from "../db-item";
import {
compareSelectedKindIsEqual,
mapDbItemToSelectedDbItem,
} from "../db-item-selection";
export class DbConfigStore extends DisposableObject {
public readonly onDidChangeConfig: AppEvent<void>;
@@ -88,6 +95,96 @@ export class DbConfigStore extends DisposableObject {
await this.writeConfig(config);
}
public async removeDbItem(dbItem: DbItem): Promise<void> {
if (!this.config) {
throw Error("Cannot remove item if config is not loaded");
}
const config = cloneDbConfig(this.config);
const selectedItem: SelectedDbItem | undefined = config.selected;
// Remove item from databases
switch (dbItem.kind) {
case DbItemKind.LocalList:
config.databases.local.lists = config.databases.local.lists.filter(
(list) => list.name !== dbItem.listName,
);
break;
case DbItemKind.RemoteUserDefinedList:
config.databases.remote.repositoryLists =
config.databases.remote.repositoryLists.filter(
(list) => list.name !== dbItem.listName,
);
break;
case DbItemKind.LocalDatabase:
// When we start using local databases these need to be removed from disk as well.
if (dbItem.parentListName) {
const parent = config.databases.local.lists.find(
(list) => list.name === dbItem.parentListName,
);
if (!parent) {
throw Error(`Cannot find parent list '${dbItem.parentListName}'`);
} else {
parent.databases = parent.databases.filter(
(db) => db.name !== dbItem.databaseName,
);
}
}
config.databases.local.databases =
config.databases.local.databases.filter(
(db) => db.name !== dbItem.databaseName,
);
break;
case DbItemKind.RemoteRepo:
if (dbItem.parentListName) {
const parent = config.databases.remote.repositoryLists.find(
(list) => list.name === dbItem.parentListName,
);
if (!parent) {
throw Error(`Cannot find parent list '${dbItem.parentListName}'`);
} else {
parent.repositories = parent.repositories.filter(
(repo) => repo !== dbItem.repoFullName,
);
}
}
config.databases.remote.repositories =
config.databases.remote.repositories.filter(
(repo) => repo !== dbItem.repoFullName,
);
break;
case DbItemKind.RemoteOwner:
config.databases.remote.owners = config.databases.remote.owners.filter(
(owner) => owner !== dbItem.ownerName,
);
break;
default:
throw Error(`Type '${dbItem.kind}' cannot be removed`);
}
// Remove item from selected
const removedItem = mapDbItemToSelectedDbItem(dbItem);
if (selectedItem && removedItem) {
// if removedItem has a parentList, check if parentList is selectedItem
if (
removedItem.kind === SelectedDbItemKind.LocalUserDefinedList ||
removedItem.kind === SelectedDbItemKind.RemoteUserDefinedList
) {
if (
(selectedItem.kind === SelectedDbItemKind.LocalDatabase ||
selectedItem.kind === SelectedDbItemKind.RemoteRepository) &&
removedItem.listName === selectedItem.listName
) {
config.selected = undefined;
}
}
if (compareSelectedKindIsEqual(removedItem, selectedItem)) {
config.selected = undefined;
}
}
await this.writeConfig(config);
}
public async addRemoteRepo(
repoNwo: string,
parentList?: string,

View File

@@ -1,5 +1,12 @@
import { DbItem, DbItemKind, LocalDbItem, RemoteDbItem } from "./db-item";
import { SelectedDbItem, SelectedDbItemKind } from "./config/db-config";
import {
SelectedDbItem,
SelectedDbItemKind,
SelectedLocalDatabase,
SelectedLocalUserDefinedList,
SelectedRemoteOwner,
SelectedRemoteRepository,
} from "./config/db-config";
export function getSelectedDbItem(dbItems: DbItem[]): DbItem | undefined {
for (const dbItem of dbItems) {
@@ -80,15 +87,51 @@ export function mapDbItemToSelectedDbItem(
case DbItemKind.LocalDatabase:
return {
kind: SelectedDbItemKind.LocalDatabase,
listName: dbItem?.parentListName,
databaseName: dbItem.databaseName,
listName: dbItem?.parentListName,
};
case DbItemKind.RemoteRepo:
return {
kind: SelectedDbItemKind.RemoteRepository,
listName: dbItem?.parentListName,
repositoryName: dbItem.repoFullName,
listName: dbItem?.parentListName,
};
}
}
export function compareSelectedKindIsEqual(
item1: SelectedDbItem,
item2: SelectedDbItem,
): boolean {
if (item1.kind === item2.kind) {
switch (item1.kind) {
case SelectedDbItemKind.LocalUserDefinedList:
case SelectedDbItemKind.RemoteUserDefinedList:
case SelectedDbItemKind.RemoteSystemDefinedList:
return (
item1.listName === (item2 as SelectedLocalUserDefinedList).listName
);
case SelectedDbItemKind.RemoteOwner:
return item1.ownerName === (item2 as SelectedRemoteOwner).ownerName;
case SelectedDbItemKind.LocalDatabase: {
const selectedItem = item2 as SelectedLocalDatabase;
return (
item1.databaseName === selectedItem.databaseName &&
item1.listName === selectedItem.listName
);
}
case SelectedDbItemKind.RemoteRepository: {
const selectedItem = item2 as SelectedRemoteRepository;
return (
item1.repositoryName === selectedItem.repositoryName &&
item1.listName === selectedItem.listName
);
}
default:
return false;
}
} else {
return false;
}
}

View File

@@ -75,6 +75,14 @@ export class DbManager {
}
}
public async removeDbItem(dbItem: DbItem): Promise<void> {
await this.dbConfigStore.removeDbItem(dbItem);
// Updating the expanded items takes care of cleaning up
// any non-existent items.
await this.updateExpandedItems(this.getExpandedItems());
}
public async updateDbItemExpandedState(
dbItem: DbItem,
itemExpanded: boolean,

View File

@@ -107,6 +107,12 @@ export class DbPanel extends DisposableObject {
(treeViewItem: DbTreeViewItem) => this.renameItem(treeViewItem),
),
);
this.push(
commandRunner(
"codeQLDatabasesExperimental.removeItemContextMenu",
(treeViewItem: DbTreeViewItem) => this.removeItem(treeViewItem),
),
);
}
private async openConfigFile(): Promise<void> {
@@ -363,6 +369,15 @@ export class DbPanel extends DisposableObject {
await this.dbManager.renameList(dbItem, newName);
}
private async removeItem(treeViewItem: DbTreeViewItem): Promise<void> {
if (treeViewItem.dbItem === undefined) {
throw new Error(
"Not a removable database item. Please select a valid item.",
);
}
await this.dbManager.removeDbItem(treeViewItem.dbItem);
}
private async onDidCollapseElement(
event: TreeViewExpansionEvent<DbTreeViewItem>,
): Promise<void> {

View File

@@ -13,6 +13,8 @@ import {
import {
createLocalDatabaseDbItem,
createLocalListDbItem,
createRemoteOwnerDbItem,
createRemoteRepoDbItem,
createRemoteUserDefinedListDbItem,
} from "../../../factories/db-item-factories";
import { createMockApp } from "../../../__mocks__/appMock";
@@ -365,4 +367,133 @@ describe("db config store", () => {
configStore.dispose();
});
});
describe("db and list deletion", () => {
let app: App;
let configPath: string;
beforeEach(async () => {
app = createMockApp({
extensionPath,
workspaceStoragePath: tempWorkspaceStoragePath,
});
configPath = join(tempWorkspaceStoragePath, "workspace-databases.json");
});
it("should remove a single db item", async () => {
// Initial set up
const dbConfig = createDbConfig({
remoteOwners: ["owner1", "owner2"],
selected: {
kind: SelectedDbItemKind.RemoteOwner,
ownerName: "owner1",
},
});
await writeJSON(configPath, dbConfig);
const configStore = new DbConfigStore(app);
await configStore.initialize();
// Remove
const currentDbItem = createRemoteOwnerDbItem({
ownerName: "owner1",
});
await configStore.removeDbItem(currentDbItem);
// Read the config file
const updatedDbConfig = (await readJSON(configPath)) as DbConfig;
// Check that the config file has been updated
const updatedRemoteDbs = updatedDbConfig.databases.remote;
expect(updatedRemoteDbs.owners).toHaveLength(1);
expect(updatedRemoteDbs.owners[0]).toEqual("owner2");
expect(updatedDbConfig.selected).toEqual(undefined);
configStore.dispose();
});
it("should remove a list db item", async () => {
// Initial set up
const dbConfig = createDbConfig({
remoteLists: [
{
name: "list1",
repositories: ["owner/repo1", "owner/repo2"],
},
],
selected: {
kind: SelectedDbItemKind.RemoteUserDefinedList,
listName: "list1",
},
});
await writeJSON(configPath, dbConfig);
const configStore = new DbConfigStore(app);
await configStore.initialize();
// Remove
const currentDbItem = createRemoteUserDefinedListDbItem({
listName: "list1",
});
await configStore.removeDbItem(currentDbItem);
// Read the config file
const updatedDbConfig = (await readJSON(configPath)) as DbConfig;
// Check that the config file has been updated
const updatedRemoteDbs = updatedDbConfig.databases.remote;
expect(updatedRemoteDbs.repositoryLists).toHaveLength(0);
expect(updatedDbConfig.selected).toEqual(undefined);
configStore.dispose();
});
it("should remove a db item in a list", async () => {
// Initial set up
const dbConfig = createDbConfig({
remoteLists: [
{
name: "list1",
repositories: ["owner/repo1", "owner/repo2"],
},
],
selected: {
kind: SelectedDbItemKind.RemoteRepository,
repositoryName: "owner/repo1",
listName: "list1",
},
});
await writeJSON(configPath, dbConfig);
const configStore = new DbConfigStore(app);
await configStore.initialize();
// Remove
const currentDbItem = createRemoteRepoDbItem({
repoFullName: "owner/repo1",
parentListName: "list1",
});
await configStore.removeDbItem(currentDbItem);
// Read the config file
const updatedDbConfig = (await readJSON(configPath)) as DbConfig;
// Check that the config file has been updated
const updatedRemoteDbs = updatedDbConfig.databases.remote;
expect(updatedRemoteDbs.repositoryLists[0].repositories).toHaveLength(1);
expect(updatedRemoteDbs.repositoryLists[0].repositories[0]).toEqual(
"owner/repo2",
);
expect(updatedDbConfig.selected).toEqual(undefined);
configStore.dispose();
});
});
});