From 1c705da4449feef45b360f3f98ec1d2514371f6c Mon Sep 17 00:00:00 2001 From: Charis Kyriakou Date: Thu, 5 Jan 2023 13:29:10 +0000 Subject: [PATCH] Add 'rename' context menu action for dbs/lists (#1928) --- extensions/ql-vscode/package.json | 13 +++ .../src/databases/db-item-expansion.ts | 18 ++++ .../ql-vscode/src/databases/db-item-naming.ts | 19 ++++ .../ql-vscode/src/databases/db-manager.ts | 50 +++++++++- .../ql-vscode/src/databases/ui/db-panel.ts | 99 ++++++++++++++++++- .../databases/db-item-expansion.test.ts | 56 +++++++++++ .../databases/db-item-naming.test.ts | 79 +++++++++++++++ 7 files changed, 331 insertions(+), 3 deletions(-) create mode 100644 extensions/ql-vscode/src/databases/db-item-naming.ts create mode 100644 extensions/ql-vscode/test/unit-tests/databases/db-item-naming.test.ts diff --git a/extensions/ql-vscode/package.json b/extensions/ql-vscode/package.json index 25b1ecc20..860850c9a 100644 --- a/extensions/ql-vscode/package.json +++ b/extensions/ql-vscode/package.json @@ -64,6 +64,7 @@ "onCommand:codeQLDatabasesExperimental.addNewList", "onCommand:codeQLDatabasesExperimental.setSelectedItem", "onCommand:codeQLDatabasesExperimental.setSelectedItemContextMenu", + "onCommand:codeQLDatabasesExperimental.renameItemContextMenu", "onCommand:codeQLDatabasesExperimental.openOnGitHubContextMenu", "onCommand:codeQL.quickQuery", "onCommand:codeQL.restartQueryServer", @@ -383,6 +384,10 @@ "command": "codeQLDatabasesExperimental.setSelectedItemContextMenu", "title": "Select" }, + { + "command": "codeQLDatabasesExperimental.renameItemContextMenu", + "title": "Rename" + }, { "command": "codeQLDatabasesExperimental.openOnGitHubContextMenu", "title": "Open on GitHub" @@ -790,6 +795,10 @@ "command": "codeQLDatabasesExperimental.setSelectedItemContextMenu", "when": "view == codeQLDatabasesExperimental && viewItem =~ /canBeSelected/" }, + { + "command": "codeQLDatabasesExperimental.renameItemContextMenu", + "when": "view == codeQLDatabasesExperimental && viewItem =~ /canBeRenamed/" + }, { "command": "codeQLDatabasesExperimental.openOnGitHubContextMenu", "when": "view == codeQLDatabasesExperimental && viewItem =~ /canBeOpenedOnGitHub/" @@ -1026,6 +1035,10 @@ "command": "codeQLDatabasesExperimental.setSelectedItemContextMenu", "when": "false" }, + { + "command": "codeQLDatabasesExperimental.renameItemContextMenu", + "when": "false" + }, { "command": "codeQLDatabasesExperimental.openOnGitHubContextMenu", "when": "false" diff --git a/extensions/ql-vscode/src/databases/db-item-expansion.ts b/extensions/ql-vscode/src/databases/db-item-expansion.ts index 13760c132..e83759239 100644 --- a/extensions/ql-vscode/src/databases/db-item-expansion.ts +++ b/extensions/ql-vscode/src/databases/db-item-expansion.ts @@ -50,6 +50,24 @@ export function updateItemInExpandedState( } } +export function replaceItemInExpandedState( + currentExpandedItems: ExpandedDbItem[], + currentDbItem: DbItem, + newDbItem: DbItem, +): ExpandedDbItem[] { + const newExpandedItems: ExpandedDbItem[] = []; + + for (const item of currentExpandedItems) { + if (isDbItemEqualToExpandedDbItem(currentDbItem, item)) { + newExpandedItems.push(mapDbItemToExpandedDbItem(newDbItem)); + } else { + newExpandedItems.push(item); + } + } + + return newExpandedItems; +} + function mapDbItemToExpandedDbItem(dbItem: DbItem): ExpandedDbItem { switch (dbItem.kind) { case DbItemKind.RootLocal: diff --git a/extensions/ql-vscode/src/databases/db-item-naming.ts b/extensions/ql-vscode/src/databases/db-item-naming.ts new file mode 100644 index 000000000..bfe62373c --- /dev/null +++ b/extensions/ql-vscode/src/databases/db-item-naming.ts @@ -0,0 +1,19 @@ +import { DbItem, DbItemKind } from "./db-item"; + +export function getDbItemName(dbItem: DbItem): string | undefined { + switch (dbItem.kind) { + case DbItemKind.RootLocal: + case DbItemKind.RootRemote: + return undefined; + case DbItemKind.LocalList: + case DbItemKind.RemoteUserDefinedList: + case DbItemKind.RemoteSystemDefinedList: + return dbItem.listName; + case DbItemKind.RemoteOwner: + return dbItem.ownerName; + case DbItemKind.LocalDatabase: + return dbItem.databaseName; + case DbItemKind.RemoteRepo: + return dbItem.repoFullName; + } +} diff --git a/extensions/ql-vscode/src/databases/db-manager.ts b/extensions/ql-vscode/src/databases/db-manager.ts index 9a07fce0a..c86af8eb6 100644 --- a/extensions/ql-vscode/src/databases/db-manager.ts +++ b/extensions/ql-vscode/src/databases/db-manager.ts @@ -2,8 +2,19 @@ import { App } from "../common/app"; import { AppEvent, AppEventEmitter } from "../common/events"; import { ValueResult } from "../common/value-result"; import { DbConfigStore } from "./config/db-config-store"; -import { DbItem, DbListKind } from "./db-item"; -import { updateItemInExpandedState, ExpandedDbItem } from "./db-item-expansion"; +import { + DbItem, + DbItemKind, + DbListKind, + LocalDatabaseDbItem, + LocalListDbItem, + RemoteUserDefinedListDbItem, +} from "./db-item"; +import { + updateItemInExpandedState, + replaceItemInExpandedState, + ExpandedDbItem, +} from "./db-item-expansion"; import { getSelectedDbItem, mapDbItemToSelectedDbItem, @@ -105,6 +116,37 @@ export class DbManager { } } + public async renameList( + currentDbItem: LocalListDbItem | RemoteUserDefinedListDbItem, + newName: string, + ): Promise { + if (currentDbItem.kind === DbItemKind.LocalList) { + await this.dbConfigStore.renameLocalList(currentDbItem, newName); + } else if (currentDbItem.kind === DbItemKind.RemoteUserDefinedList) { + await this.dbConfigStore.renameRemoteList(currentDbItem, newName); + } + + const newDbItem = { ...currentDbItem, listName: newName }; + const newExpandedItems = replaceItemInExpandedState( + this.getExpandedItems(), + currentDbItem, + newDbItem, + ); + + await this.setExpandedItems(newExpandedItems); + } + + public async renameLocalDb( + currentDbItem: LocalDatabaseDbItem, + newName: string, + ): Promise { + await this.dbConfigStore.renameLocalDb( + currentDbItem, + newName, + currentDbItem.parentListName, + ); + } + public doesListExist(listKind: DbListKind, listName: string): boolean { switch (listKind) { case DbListKind.Local: @@ -124,6 +166,10 @@ export class DbManager { return this.dbConfigStore.doesRemoteDbExist(nwo, listName); } + public doesLocalDbExist(dbName: string, listName?: string): boolean { + return this.dbConfigStore.doesLocalDbExist(dbName, listName); + } + private getExpandedItems(): ExpandedDbItem[] { const items = this.app.workspaceState.get( DbManager.DB_EXPANDED_STATE_KEY, diff --git a/extensions/ql-vscode/src/databases/ui/db-panel.ts b/extensions/ql-vscode/src/databases/ui/db-panel.ts index 3d1613604..e18828d9a 100644 --- a/extensions/ql-vscode/src/databases/ui/db-panel.ts +++ b/extensions/ql-vscode/src/databases/ui/db-panel.ts @@ -16,7 +16,16 @@ import { } from "../../common/github-url-identifier-helper"; import { showAndLogErrorMessage } from "../../helpers"; import { DisposableObject } from "../../pure/disposable-object"; -import { DbItem, DbItemKind, DbListKind, remoteDbKinds } from "../db-item"; +import { + DbItem, + DbItemKind, + DbListKind, + LocalDatabaseDbItem, + LocalListDbItem, + remoteDbKinds, + RemoteUserDefinedListDbItem, +} from "../db-item"; +import { getDbItemName } from "../db-item-naming"; import { DbManager } from "../db-manager"; import { DbTreeDataProvider } from "./db-tree-data-provider"; import { DbTreeViewItem } from "./db-tree-view-item"; @@ -92,6 +101,12 @@ export class DbPanel extends DisposableObject { (treeViewItem: DbTreeViewItem) => this.openOnGitHub(treeViewItem), ), ); + this.push( + commandRunner( + "codeQLDatabasesExperimental.renameItemContextMenu", + (treeViewItem: DbTreeViewItem) => this.renameItem(treeViewItem), + ), + ); } private async openConfigFile(): Promise { @@ -266,6 +281,88 @@ export class DbPanel extends DisposableObject { await this.dbManager.setSelectedDbItem(treeViewItem.dbItem); } + private async renameItem(treeViewItem: DbTreeViewItem): Promise { + const dbItem = treeViewItem.dbItem; + if (dbItem === undefined) { + throw new Error( + "Not a database item that can be renamed. Please select a valid item.", + ); + } + + const oldName = getDbItemName(dbItem); + + const newName = await window.showInputBox({ + prompt: "Enter the new name", + value: oldName, + }); + + if (newName === undefined || newName === "") { + return; + } + + switch (dbItem.kind) { + case DbItemKind.LocalList: + await this.renameLocalListItem(dbItem, newName); + break; + case DbItemKind.LocalDatabase: + await this.renameLocalDatabaseItem(dbItem, newName); + break; + case DbItemKind.RemoteUserDefinedList: + await this.renameRemoteUserDefinedListItem(dbItem, newName); + break; + default: + throw Error(`Action not allowed for the '${dbItem.kind}' db item kind`); + } + } + + private async renameLocalListItem( + dbItem: LocalListDbItem, + newName: string, + ): Promise { + if (dbItem.listName === newName) { + return; + } + + if (this.dbManager.doesListExist(DbListKind.Local, newName)) { + void showAndLogErrorMessage(`The list '${newName}' already exists`); + return; + } + + await this.dbManager.renameList(dbItem, newName); + } + + private async renameLocalDatabaseItem( + dbItem: LocalDatabaseDbItem, + newName: string, + ): Promise { + if (dbItem.databaseName === newName) { + return; + } + + if (this.dbManager.doesLocalDbExist(newName, dbItem.parentListName)) { + void showAndLogErrorMessage(`The database '${newName}' already exists`); + return; + } + + await this.dbManager.renameLocalDb(dbItem, newName); + } + + private async renameRemoteUserDefinedListItem( + dbItem: RemoteUserDefinedListDbItem, + newName: string, + ): Promise { + if (dbItem.listName === newName) { + return; + } + + if (this.dbManager.doesListExist(DbListKind.Remote, newName)) { + void showAndLogErrorMessage(`The list '${newName}' already exists`); + return; + } + + await this.dbManager.renameList(dbItem, newName); + } + private async onDidCollapseElement( event: TreeViewExpansionEvent, ): Promise { diff --git a/extensions/ql-vscode/test/unit-tests/databases/db-item-expansion.test.ts b/extensions/ql-vscode/test/unit-tests/databases/db-item-expansion.test.ts index 5dc96d4a8..ce6df26d0 100644 --- a/extensions/ql-vscode/test/unit-tests/databases/db-item-expansion.test.ts +++ b/extensions/ql-vscode/test/unit-tests/databases/db-item-expansion.test.ts @@ -6,6 +6,7 @@ import { updateItemInExpandedState, ExpandedDbItem, ExpandedDbItemKind, + replaceItemInExpandedState, } from "../../../src/databases/db-item-expansion"; import { createRemoteUserDefinedListDbItem, @@ -108,4 +109,59 @@ describe("db item expansion", () => { expect(newExpandedItems).toEqual([]); }); }); + + describe("replaceItemInExpandedState", () => { + it("should replace the db item", () => { + const currentExpandedItems: ExpandedDbItem[] = [ + { + kind: ExpandedDbItemKind.RootRemote, + }, + { + kind: ExpandedDbItemKind.RemoteUserDefinedList, + listName: "list1", + }, + { + kind: ExpandedDbItemKind.RemoteUserDefinedList, + listName: "list2", + }, + { + kind: ExpandedDbItemKind.LocalUserDefinedList, + listName: "list1", + }, + ]; + + const currentDbItem = createRemoteUserDefinedListDbItem({ + listName: "list1", + }); + + const newDbItem: RemoteUserDefinedListDbItem = { + ...currentDbItem, + listName: "list1 (renamed)", + }; + + const newExpandedItems = replaceItemInExpandedState( + currentExpandedItems, + currentDbItem, + newDbItem, + ); + + expect(newExpandedItems).toEqual([ + { + kind: ExpandedDbItemKind.RootRemote, + }, + { + kind: ExpandedDbItemKind.RemoteUserDefinedList, + listName: "list1 (renamed)", + }, + { + kind: ExpandedDbItemKind.RemoteUserDefinedList, + listName: "list2", + }, + { + kind: ExpandedDbItemKind.LocalUserDefinedList, + listName: "list1", + }, + ]); + }); + }); }); diff --git a/extensions/ql-vscode/test/unit-tests/databases/db-item-naming.test.ts b/extensions/ql-vscode/test/unit-tests/databases/db-item-naming.test.ts new file mode 100644 index 000000000..1ba56af56 --- /dev/null +++ b/extensions/ql-vscode/test/unit-tests/databases/db-item-naming.test.ts @@ -0,0 +1,79 @@ +import { getDbItemName } from "../../../src/databases/db-item-naming"; +import { + createLocalDatabaseDbItem, + createLocalListDbItem, + createRemoteOwnerDbItem, + createRemoteRepoDbItem, + createRemoteSystemDefinedListDbItem, + createRemoteUserDefinedListDbItem, + createRootLocalDbItem, + createRootRemoteDbItem, +} from "../../factories/db-item-factories"; + +describe("db item naming", () => { + describe("getDbItemName", () => { + it("return undefined for root local db item", () => { + const dbItem = createRootLocalDbItem(); + + const name = getDbItemName(dbItem); + + expect(name).toBeUndefined(); + }); + + it("return undefined for root remote db item", () => { + const dbItem = createRootRemoteDbItem(); + + const name = getDbItemName(dbItem); + + expect(name).toBeUndefined(); + }); + + it("return list name for local list db item", () => { + const dbItem = createLocalListDbItem(); + + const name = getDbItemName(dbItem); + + expect(name).toEqual(dbItem.listName); + }); + + it("return list name for remote user defined list db item", () => { + const dbItem = createRemoteUserDefinedListDbItem(); + + const name = getDbItemName(dbItem); + + expect(name).toEqual(dbItem.listName); + }); + + it("return list name for remote system defined list db item", () => { + const dbItem = createRemoteSystemDefinedListDbItem(); + + const name = getDbItemName(dbItem); + + expect(name).toEqual(dbItem.listName); + }); + + it("return owner name for owner db item", () => { + const dbItem = createRemoteOwnerDbItem(); + + const name = getDbItemName(dbItem); + + expect(name).toEqual(dbItem.ownerName); + }); + + it("return database name for local db item", () => { + const dbItem = createLocalDatabaseDbItem(); + + const name = getDbItemName(dbItem); + + expect(name).toEqual(dbItem.databaseName); + }); + + it("return repo name for remote repo db item", () => { + const dbItem = createRemoteRepoDbItem(); + + const name = getDbItemName(dbItem); + + expect(name).toEqual(dbItem.repoFullName); + }); + }); +});