diff --git a/extensions/ql-vscode/package.json b/extensions/ql-vscode/package.json index d4341fd23..256c59d49 100644 --- a/extensions/ql-vscode/package.json +++ b/extensions/ql-vscode/package.json @@ -62,6 +62,7 @@ "onCommand:codeQLDatabases.chooseDatabase", "onCommand:codeQLDatabases.setCurrentDatabase", "onCommand:codeQLDatabasesExperimental.openConfigFile", + "onCommand:codeQLDatabasesExperimental.addNewList", "onCommand:codeQLDatabasesExperimental.setSelectedItem", "onCommand:codeQL.quickQuery", "onCommand:codeQL.restartQueryServer", @@ -361,10 +362,12 @@ { "command": "codeQLDatabasesExperimental.openConfigFile", "title": "Open Database Configuration File", - "icon": { - "light": "media/light/edit.svg", - "dark": "media/dark/edit.svg" - } + "icon": "$(edit)" + }, + { + "command": "codeQLDatabasesExperimental.addNewList", + "title": "Add new list", + "icon": "$(new-folder)" }, { "command": "codeQLDatabasesExperimental.setSelectedItem", @@ -774,6 +777,11 @@ "command": "codeQLDatabasesExperimental.openConfigFile", "when": "view == codeQLDatabasesExperimental", "group": "navigation" + }, + { + "command": "codeQLDatabasesExperimental.addNewList", + "when": "view == codeQLDatabasesExperimental", + "group": "navigation" } ], "view/item/context": [ @@ -997,6 +1005,10 @@ "command": "codeQLDatabasesExperimental.openConfigFile", "when": "false" }, + { + "command": "codeQLDatabasesExperimental.addNewList", + "when": "false" + }, { "command": "codeQLDatabasesExperimental.setSelectedItem", "when": "false" diff --git a/extensions/ql-vscode/src/databases/config/db-config-store.ts b/extensions/ql-vscode/src/databases/config/db-config-store.ts index 6e272830b..cc912e2d0 100644 --- a/extensions/ql-vscode/src/databases/config/db-config-store.ts +++ b/extensions/ql-vscode/src/databases/config/db-config-store.ts @@ -90,6 +90,21 @@ export class DbConfigStore extends DisposableObject { await this.writeConfig(config); } + public async addRemoteList(listName: string): Promise { + if (!this.config) { + throw Error("Cannot add remote list if config is not loaded"); + } + + const config: DbConfig = cloneDbConfig(this.config); + config.databases.remote.repositoryLists.push({ + name: listName, + repositories: [], + }); + + // TODO: validate that the name doesn't already exist + await this.writeConfig(config); + } + private async writeConfig(config: DbConfig): Promise { await writeJSON(this.configPath, config, { spaces: 2, diff --git a/extensions/ql-vscode/src/databases/db-manager.ts b/extensions/ql-vscode/src/databases/db-manager.ts index 6675970a9..961029b25 100644 --- a/extensions/ql-vscode/src/databases/db-manager.ts +++ b/extensions/ql-vscode/src/databases/db-manager.ts @@ -73,4 +73,8 @@ export class DbManager { await this.dbConfigStore.updateExpandedState(newExpandedItems); } + + public async addNewRemoteList(listName: string): Promise { + await this.dbConfigStore.addRemoteList(listName); + } } diff --git a/extensions/ql-vscode/src/databases/ui/db-panel.ts b/extensions/ql-vscode/src/databases/ui/db-panel.ts index 64f8fbec1..77d6bdc59 100644 --- a/extensions/ql-vscode/src/databases/ui/db-panel.ts +++ b/extensions/ql-vscode/src/databases/ui/db-panel.ts @@ -38,6 +38,11 @@ export class DbPanel extends DisposableObject { this.openConfigFile(), ), ); + this.push( + commandRunner("codeQLDatabasesExperimental.addNewList", () => + this.addNewRemoteList(), + ), + ); this.push( commandRunner( "codeQLDatabasesExperimental.setSelectedItem", @@ -52,6 +57,18 @@ export class DbPanel extends DisposableObject { await window.showTextDocument(document); } + private async addNewRemoteList(): Promise { + // TODO: check that config exists *before* showing the input box + const listName = await window.showInputBox({ + prompt: "Enter a name for the new list", + placeHolder: "example-list", + }); + if (listName === undefined) { + return; + } + await this.dbManager.addNewRemoteList(listName); + } + private async setSelectedItem(treeViewItem: DbTreeViewItem): Promise { if (treeViewItem.dbItem === undefined) { throw new Error( diff --git a/extensions/ql-vscode/src/vscode-tests/minimal-workspace/databases/db-panel.test.ts b/extensions/ql-vscode/src/vscode-tests/minimal-workspace/databases/db-panel.test.ts index 854b77ce2..22390bc18 100644 --- a/extensions/ql-vscode/src/vscode-tests/minimal-workspace/databases/db-panel.test.ts +++ b/extensions/ql-vscode/src/vscode-tests/minimal-workspace/databases/db-panel.test.ts @@ -1,6 +1,6 @@ import { TreeItemCollapsibleState, ThemeIcon } from "vscode"; import { join } from "path"; -import { ensureDir, remove, writeJson } from "fs-extra"; +import { ensureDir, readJSON, remove, writeJson } from "fs-extra"; import { DbConfig, SelectedDbItemKind, @@ -531,6 +531,66 @@ describe("db panel", () => { } }); + it("should add a new list to the remote db list", async () => { + const dbConfig: DbConfig = { + databases: { + remote: { + repositoryLists: [ + { + name: "my-list-1", + repositories: ["owner1/repo1", "owner1/repo2"], + }, + ], + owners: [], + repositories: [], + }, + local: { + lists: [], + databases: [], + }, + }, + expanded: [], + selected: { + kind: SelectedDbItemKind.RemoteUserDefinedList, + listName: "my-list-1", + }, + }; + + await saveDbConfig(dbConfig); + + const dbTreeItems = await dbTreeDataProvider.getChildren(); + + expect(dbTreeItems).toBeTruthy(); + const items = dbTreeItems!; + + const remoteRootNode = items[0]; + const remoteUserDefinedLists = remoteRootNode.children.filter( + (c) => c.dbItem?.kind === DbItemKind.RemoteUserDefinedList, + ); + const list1 = remoteRootNode.children.find( + (c) => + c.dbItem?.kind === DbItemKind.RemoteUserDefinedList && + c.dbItem?.listName === "my-list-1", + ); + + expect(remoteUserDefinedLists.length).toBe(1); + expect(remoteUserDefinedLists[0]).toBe(list1); + + await dbManager.addNewRemoteList("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); + expect(dbConfigFileContents.databases.remote.repositoryLists.length).toBe( + 2, + ); + expect(dbConfigFileContents.databases.remote.repositoryLists[1]).toEqual({ + name: "my-list-2", + repositories: [], + }); + }); + async function saveDbConfig(dbConfig: DbConfig): Promise { await writeJson(dbConfigFilePath, dbConfig);