diff --git a/package.json b/package.json index 98954665..065330d2 100644 --- a/package.json +++ b/package.json @@ -416,6 +416,21 @@ "category": "KX", "command": "kdb.toggleParameterCache", "title": "KX: Toggle parameter cache" + }, + { + "category": "KX", + "command": "kdb.renameLabel", + "title": "Rename label" + }, + { + "category": "KX", + "command": "kdb.editLabelColor", + "title": "Edit label color" + }, + { + "category": "KX", + "command": "kdb.deleteLabel", + "title": "Delete label" } ], "keybindings": [ @@ -692,7 +707,7 @@ }, { "command": "kdb.editConnection", - "when": "view == kdb-servers", + "when": "view == kdb-servers && (viewItem in kdb.rootNodes || viewItem in kdb.insightsNodes)", "group": "connection@4" }, { @@ -754,6 +769,21 @@ "command": "kdb.deleteFile", "when": "(view == kdb-datasource-explorer || view == kdb-scratchpad-explorer) && viewItem == artifact", "group": "kdbWorkspace@2" + }, + { + "command": "kdb.renameLabel", + "when": "view == kdb-servers && viewItem == label", + "group": "label@1" + }, + { + "command": "kdb.editLabelColor", + "when": "view == kdb-servers && viewItem == label", + "group": "label@2" + }, + { + "command": "kdb.deleteLabel", + "when": "view == kdb-servers && viewItem == label", + "group": "label@3" } ], "editor/title/run": [ diff --git a/resources/dark/labels/label-blue.svg b/resources/dark/labels/label-blue.svg new file mode 100644 index 00000000..75d121a1 --- /dev/null +++ b/resources/dark/labels/label-blue.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/dark/labels/label-cyan.svg b/resources/dark/labels/label-cyan.svg new file mode 100644 index 00000000..4c007822 --- /dev/null +++ b/resources/dark/labels/label-cyan.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/dark/labels/label-green.svg b/resources/dark/labels/label-green.svg new file mode 100644 index 00000000..0871bbe6 --- /dev/null +++ b/resources/dark/labels/label-green.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/dark/labels/label-magenta.svg b/resources/dark/labels/label-magenta.svg new file mode 100644 index 00000000..8c351912 --- /dev/null +++ b/resources/dark/labels/label-magenta.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/dark/labels/label-red.svg b/resources/dark/labels/label-red.svg new file mode 100644 index 00000000..f1ea352c --- /dev/null +++ b/resources/dark/labels/label-red.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/dark/labels/label-white.svg b/resources/dark/labels/label-white.svg new file mode 100644 index 00000000..bc052168 --- /dev/null +++ b/resources/dark/labels/label-white.svg @@ -0,0 +1,4 @@ + + + + diff --git a/resources/dark/labels/label-yellow.svg b/resources/dark/labels/label-yellow.svg new file mode 100644 index 00000000..b3ba5b22 --- /dev/null +++ b/resources/dark/labels/label-yellow.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/light/labels/label-blue.svg b/resources/light/labels/label-blue.svg new file mode 100644 index 00000000..411c4ed1 --- /dev/null +++ b/resources/light/labels/label-blue.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/light/labels/label-cyan.svg b/resources/light/labels/label-cyan.svg new file mode 100644 index 00000000..f4630608 --- /dev/null +++ b/resources/light/labels/label-cyan.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/light/labels/label-green.svg b/resources/light/labels/label-green.svg new file mode 100644 index 00000000..a8da7eab --- /dev/null +++ b/resources/light/labels/label-green.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/light/labels/label-magenta.svg b/resources/light/labels/label-magenta.svg new file mode 100644 index 00000000..e0f47686 --- /dev/null +++ b/resources/light/labels/label-magenta.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/light/labels/label-red.svg b/resources/light/labels/label-red.svg new file mode 100644 index 00000000..f1ea352c --- /dev/null +++ b/resources/light/labels/label-red.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/light/labels/label-white.svg b/resources/light/labels/label-white.svg new file mode 100644 index 00000000..bc052168 --- /dev/null +++ b/resources/light/labels/label-white.svg @@ -0,0 +1,4 @@ + + + + diff --git a/resources/light/labels/label-yellow.svg b/resources/light/labels/label-yellow.svg new file mode 100644 index 00000000..c7b3ae6b --- /dev/null +++ b/resources/light/labels/label-yellow.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/commands/workspaceCommand.ts b/src/commands/workspaceCommand.ts index c182ba77..4b739f94 100644 --- a/src/commands/workspaceCommand.ts +++ b/src/commands/workspaceCommand.ts @@ -28,7 +28,7 @@ import { } from "vscode"; import { ext } from "../extensionVariables"; import { ConnectionManagementService } from "../services/connectionManagerService"; -import { InsightsNode, KdbNode } from "../services/kdbTreeProvider"; +import { InsightsNode, KdbNode, LabelNode } from "../services/kdbTreeProvider"; import { runQuery } from "./serverCommand"; import { ExecutionTypes } from "../models/execution"; import { importOldDsFiles, oldFilesExists } from "../utils/dataSource"; @@ -96,17 +96,37 @@ function getServers() { } /* istanbul ignore next */ -export async function getConnectionForServer(server: string) { +export async function getConnectionForServer( + server: string, +): Promise { if (server) { - const servers = await ext.serverProvider.getChildren(); - return servers.find((item) => { - if (item instanceof InsightsNode) { - return item.details.alias === server; - } else if (item instanceof KdbNode) { - return item.details.serverAlias === server; + const nodes = await ext.serverProvider.getChildren(); + const orphan = nodes.find((node) => { + if (node instanceof InsightsNode) { + return node.details.alias === server; + } else if (node instanceof KdbNode) { + return node.details.serverAlias === server; } return false; - }) as KdbNode | InsightsNode; + }) as InsightsNode | KdbNode; + if (orphan) { + return orphan; + } + const labels = nodes.filter((server) => server instanceof LabelNode); + for (const label of labels) { + const item = (label as LabelNode).children.find((node) => { + const name = + node instanceof InsightsNode + ? node.details.alias + : node instanceof KdbNode + ? node.details.serverAlias + : ""; + return name === server; + }) as InsightsNode | KdbNode; + if (item) { + return item; + } + } } } diff --git a/src/extension.ts b/src/extension.ts index 13e4e964..e0bcd150 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -106,8 +106,11 @@ import { QuickFixProvider } from "./services/quickFixProvider"; import { connectClientCommands } from "./commands/clientCommands"; import { createNewLabel, + deleteLabel, getWorkspaceLabels, getWorkspaceLabelsConnMap, + renameLabel, + setLabelColor, } from "./utils/connLabel"; let client: LanguageClient; @@ -506,6 +509,60 @@ export async function activate(context: ExtensionContext) { ext.serverProvider.reload(); } }), + commands.registerCommand("kdb.renameLabel", async (item) => { + if (item) { + const name = await window.showInputBox({ + prompt: "Enter label name", + value: item.label, + }); + if (name) { + renameLabel(item.label, name); + } + } + }), + commands.registerCommand("kdb.editLabelColor", async (item) => { + if (item) { + const colors = ext.labelColors.map((color) => ({ + label: color.name, + iconPath: { + light: Uri.file( + path.join( + __filename, + "..", + "..", + "resources", + "light", + "labels", + `label-${color.name.toLowerCase()}.svg`, + ), + ), + dark: Uri.file( + path.join( + __filename, + "..", + "..", + "resources", + "dark", + "labels", + `label-${color.name.toLowerCase()}.svg`, + ), + ), + }, + })); + const picked = await window.showQuickPick(colors, { + title: "Select label color", + placeHolder: item.source.color.name, + }); + if (picked) { + setLabelColor(item.label, picked.label); + } + } + }), + commands.registerCommand("kdb.deleteLabel", (item) => { + if (item) { + deleteLabel(item.label); + } + }), ); checkOldDatasourceFiles(); diff --git a/src/services/kdbTreeProvider.ts b/src/services/kdbTreeProvider.ts index cb74b1c4..2eb52bbd 100644 --- a/src/services/kdbTreeProvider.ts +++ b/src/services/kdbTreeProvider.ts @@ -41,6 +41,12 @@ import { } from "../utils/core"; import { ConnectionManagementService } from "./connectionManagerService"; import { InsightsConnection } from "../classes/insightsConnection"; +import { + getWorkspaceLabels, + getWorkspaceLabelsConnMap, + retrieveConnLabelsNames, +} from "../utils/connLabel"; +import { Labels } from "../models/labels"; export class KdbTreeProvider implements TreeDataProvider { private _onDidChangeTreeData: EventEmitter< @@ -92,15 +98,38 @@ export class KdbTreeProvider implements TreeDataProvider { } async getChildren(element?: TreeItem): Promise { - if (!this.serverList) { - return Promise.resolve([]); - } - if (!this.insightsList) { - return Promise.resolve([]); + if (!this.serverList || !this.insightsList) { + return []; } if (!element) { - return Promise.resolve(this.getMergedElements(element)); + getWorkspaceLabels(); + getWorkspaceLabelsConnMap(); + + const orphans: TreeItem[] = []; + const nodes = ext.connLabelList.map((label) => new LabelNode(label)); + const items = this.getMergedElements(element); + + let orphan, found; + for (const item of items) { + orphan = true; + if (item instanceof KdbNode || item instanceof InsightsNode) { + const labels = retrieveConnLabelsNames(item); + for (const label of labels) { + found = nodes.find((node) => label === node.source.name); + if (found) { + found.children.push(item); + orphan = false; + } + } + } + if (orphan) { + orphans.push(item); + } + } + return [...orphans, ...nodes]; + } else if (element instanceof LabelNode) { + return element.children; } else if ( element.contextValue !== undefined && ext.kdbrootNodes.indexOf(element.contextValue) !== -1 @@ -785,3 +814,33 @@ export class QServerNode extends TreeItem { }; contextValue = this.label; } + +export class LabelNode extends TreeItem { + readonly children: TreeItem[] = []; + + constructor(public readonly source: Labels) { + super(source.name, TreeItemCollapsibleState.Collapsed); + this.contextValue = "label"; + } + + iconPath = { + light: path.join( + __filename, + "..", + "..", + "resources", + "light", + "labels", + `label-${this.source.color.name.toLowerCase()}.svg`, + ), + dark: path.join( + __filename, + "..", + "..", + "resources", + "dark", + "labels", + `label-${this.source.color.name.toLowerCase()}.svg`, + ), + }; +} diff --git a/src/utils/connLabel.ts b/src/utils/connLabel.ts index 76b293bc..5907b1d2 100644 --- a/src/utils/connLabel.ts +++ b/src/utils/connLabel.ts @@ -124,3 +124,49 @@ export function retrieveConnLabelsNames( }); return labels; } + +export function renameLabel(name: string, newName: string) { + getWorkspaceLabels(); + const found = ext.connLabelList.find((item) => item.name === name); + if (found) { + found.name = newName; + } + getWorkspaceLabelsConnMap(); + const target = ext.labelConnMapList.find((item) => item.labelName === name); + if (target) { + target.labelName = newName; + } + workspace + .getConfiguration() + .update("kdb.labelsConnectionMap", ext.labelConnMapList, true) + .then(() => + workspace + .getConfiguration() + .update("kdb.connectionLabels", ext.connLabelList, true), + ); +} + +export function setLabelColor(name: string, color: string) { + getWorkspaceLabels(); + const found = ext.connLabelList.find((item) => item.name === name); + if (found) { + const target = ext.labelColors.find((value) => value.name === color); + if (target) { + found.color = target; + } + } + workspace + .getConfiguration() + .update("kdb.connectionLabels", ext.connLabelList, true); +} + +export function deleteLabel(name: string) { + getWorkspaceLabels(); + const found = ext.connLabelList.find((item) => item.name === name); + if (found) { + ext.connLabelList.splice(ext.connLabelList.indexOf(found), 1); + } + workspace + .getConfiguration() + .update("kdb.connectionLabels", ext.connLabelList, true); +} diff --git a/test/suite/services.test.ts b/test/suite/services.test.ts index 2be3b0c3..93816d5e 100644 --- a/test/suite/services.test.ts +++ b/test/suite/services.test.ts @@ -39,6 +39,7 @@ import { InsightsNode, KdbNode, KdbTreeProvider, + LabelNode, MetaObjectPayloadNode, QCategoryNode, QNamespaceNode, @@ -63,6 +64,7 @@ import * as utils from "../../src/utils/getUri"; import { MetaInfoType, MetaObject } from "../../src/models/meta"; import { CompletionProvider } from "../../src/services/completionProvider"; import { MetaContentProvider } from "../../src/services/metaContentProvider"; +import { ConnectionLabel, Labels } from "../../src/models/labels"; // eslint-disable-next-line @typescript-eslint/no-var-requires const codeFlow = require("../../src/services/kdbInsights/codeFlowLogin"); @@ -605,6 +607,18 @@ describe("kdbTreeProvider", () => { ); }); + it("Should return a new LabelNode", () => { + const labelNode = new LabelNode({ + name: "White", + color: { name: "White", colorHex: "#CCCCCC" }, + }); + assert.strictEqual( + labelNode.label, + "White", + "LabelNode node creation failed", + ); + }); + describe("InsightsMetaNode", () => { it("should initialize fields correctly", () => { const node = new InsightsMetaNode( @@ -709,6 +723,24 @@ describe("kdbTreeProvider", () => { const result = await kdbProvider.getChildren(metaNode); assert.notStrictEqual(result, undefined); }); + + it("should return label node", async () => { + const labels: Labels[] = [ + { name: "label1", color: { name: "red", colorHex: "#FF0000" } }, + ]; + const conns: ConnectionLabel[] = [ + { + labelName: "label1", + connections: ["testServerAlias", "testInsightsAlias"], + }, + ]; + sinon.stub(workspace, "getConfiguration").value(() => ({ + get: (v: string) => (v === "kdb.connectionLabels" ? labels : conns), + })); + const provider = new KdbTreeProvider(servers, insights); + const result = await provider.getChildren(); + assert.strictEqual(result.length, 1); + }); }); }); diff --git a/test/suite/utils.test.ts b/test/suite/utils.test.ts index aa41407b..0a57a533 100644 --- a/test/suite/utils.test.ts +++ b/test/suite/utils.test.ts @@ -1745,5 +1745,43 @@ describe("Utils", () => { assert.deepStrictEqual(labels, ["label1"]); }); + + it("should rename a label", () => { + const labels: Labels[] = [ + { name: "label1", color: { name: "red", colorHex: "#FF0000" } }, + ]; + getConfigurationStub.returns({ + get: sinon.stub().returns(labels), + update: sinon.stub().returns(Promise.resolve()), + }); + LabelsUtils.renameLabel("label1", "label2"); + assert.strictEqual(ext.connLabelList.length, 1); + assert.deepStrictEqual(ext.connLabelList[0].name, "label2"); + }); + + it("should set label color", () => { + const labels: Labels[] = [ + { name: "label1", color: { name: "red", colorHex: "#FF0000" } }, + ]; + getConfigurationStub.returns({ + get: sinon.stub().returns(labels), + update: sinon.stub().returns(Promise.resolve()), + }); + LabelsUtils.setLabelColor("label1", "Blue"); + assert.strictEqual(ext.connLabelList.length, 1); + assert.deepStrictEqual(ext.connLabelList[0].color.name, "Blue"); + }); + + it("should delete label", () => { + const labels: Labels[] = [ + { name: "label1", color: { name: "red", colorHex: "#FF0000" } }, + ]; + getConfigurationStub.returns({ + get: sinon.stub().returns(labels), + update: sinon.stub().returns(Promise.resolve()), + }); + LabelsUtils.deleteLabel("label1"); + assert.strictEqual(ext.connLabelList.length, 0); + }); }); });