diff --git a/src/commands/serverCommand.ts b/src/commands/serverCommand.ts index d6b82c05..897f2823 100644 --- a/src/commands/serverCommand.ts +++ b/src/commands/serverCommand.ts @@ -68,6 +68,7 @@ import { InsightsConnection } from "../classes/insightsConnection"; import { MetaContentProvider } from "../services/metaContentProvider"; import { handleLabelsConnMap, removeConnFromLabels } from "../utils/connLabel"; import { + ExportedConnections, InsightDetails, Insights, Server, @@ -376,7 +377,7 @@ export async function addKdbConnection( tls: kdbData.tls, }, }; - if (servers[0].managed) { + if (servers.key.managed) { await addLocalConnectionContexts(getServerName(servers[0])); } } else { @@ -539,6 +540,95 @@ export async function editKdbConnection( } } +// test fs readFileSync unit tests are flaky, no correct way to test them +/* istanbul ignore next */ +export async function importConnections() { + const options = { + canSelectMany: false, + openLabel: "Select JSON File", + filters: { + "JSON Files": ["json"], + "All Files": ["*"], + }, + }; + + const fileUri = await window.showOpenDialog(options); + if (!fileUri || fileUri.length === 0) { + kdbOutputLog("[IMPORT CONNECTION]No file selected", "ERROR"); + return; + } + const filePath = fileUri[0].fsPath; + const fileContent = fs.readFileSync(filePath, "utf8"); + + let importedConnections: ExportedConnections; + try { + importedConnections = JSON.parse(fileContent); + } catch (e) { + kdbOutputLog("[IMPORT CONNECTION]Invalid JSON format", "ERROR"); + return; + } + + if (!isValidExportedConnections(importedConnections)) { + kdbOutputLog( + "[IMPORT CONNECTION]JSON does not match the required format", + "ERROR", + ); + return; + } + if ( + importedConnections.connections.KDB.length === 0 && + importedConnections.connections.Insights.length === 0 + ) { + kdbOutputLog( + "[IMPORT CONNECTION]There is no KDB or Insights connections to import in this JSON file", + "ERROR", + ); + return; + } + await addImportedConnections(importedConnections); +} + +export async function addImportedConnections( + importedConnections: ExportedConnections, +) { + const connMangService = new ConnectionManagementService(); + const existingAliases = connMangService.retrieveListOfConnectionsNames(); + const localAlreadyExists = existingAliases.has("local"); + let counter = 1; + for (const connection of importedConnections.connections.Insights) { + let alias = connMangService.checkConnAlias(connection.alias, true); + + while (existingAliases.has(alias)) { + alias = `${connection.alias}-${counter}`; + counter++; + } + connection.alias = alias; + await addInsightsConnection(connection); + existingAliases.add(alias); + counter = 1; + } + + for (const connection of importedConnections.connections.KDB) { + let alias = connMangService.checkConnAlias( + connection.serverAlias, + false, + localAlreadyExists, + ); + while (existingAliases.has(alias)) { + alias = `${connection.serverAlias}-${counter}`; + counter++; + } + const isManaged = alias === "local"; + connection.serverAlias = alias; + await addKdbConnection(connection, isManaged); + existingAliases.add(alias); + counter = 1; + } + + kdbOutputLog("[IMPORT CONNECTION]Connections imported successfully", "INFO"); + window.showInformationMessage("Connections imported successfully"); +} + export async function removeConnection(viewItem: KdbNode | InsightsNode) { const connMngService = new ConnectionManagementService(); removeConnFromLabels( @@ -1058,3 +1148,12 @@ export function writeScratchpadResult( } } } + +function isValidExportedConnections(data: any): data is ExportedConnections { + return ( + data && + data.connections && + Array.isArray(data.connections.Insights) && + Array.isArray(data.connections.KDB) + ); +} diff --git a/src/extension.ts b/src/extension.ts index ceaa0315..93946a62 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -52,6 +52,7 @@ import { editKdbConnection, enableTLS, exportConnections, + importConnections, openMeta, refreshGetMeta, removeConnection, @@ -281,8 +282,8 @@ export async function activate(context: ExtensionContext) { exportConnections(viewItem.label); }, ), - commands.registerCommand("kdb.connections.import", () => { - window.showInformationMessage("Import Connections command executed"); + commands.registerCommand("kdb.connections.import", async () => { + await importConnections(); }), commands.registerCommand( "kdb.open.meta", diff --git a/src/services/connectionManagerService.ts b/src/services/connectionManagerService.ts index 1e054d21..15938648 100644 --- a/src/services/connectionManagerService.ts +++ b/src/services/connectionManagerService.ts @@ -69,6 +69,30 @@ export class ConnectionManagementService { ); } + public retrieveListOfConnectionsNames(): Set { + return new Set( + ext.connectionsList.map((conn) => { + if (conn instanceof KdbNode) { + return conn.details.serverAlias; + } else { + return conn.details.alias; + } + }), + ); + } + + public checkConnAlias( + alias: string, + isInsights: boolean, + localAlreadyExists?: boolean, + ): string { + if (isInsights) { + return alias !== "local" ? alias : "localInsights"; + } else { + return localAlreadyExists && alias === "local" ? alias + "KDB" : alias; + } + } + public isKdbConnection(connection: KdbNode | InsightsNode): boolean { return connection instanceof KdbNode; } diff --git a/test/suite/commands.test.ts b/test/suite/commands.test.ts index b3187b4b..85499472 100644 --- a/test/suite/commands.test.ts +++ b/test/suite/commands.test.ts @@ -11,11 +11,11 @@ * specific language governing permissions and limitations under the License. */ -import * as fs from "fs"; import assert from "assert"; import mock from "mock-fs"; import * as sinon from "sinon"; import * as vscode from "vscode"; +import * as fs from "fs"; import * as dataSourceCommand from "../../src/commands/dataSourceCommand"; import * as installTools from "../../src/commands/installTools"; import * as serverCommand from "../../src/commands/serverCommand"; @@ -53,6 +53,7 @@ import { GetDataError } from "../../src/models/data"; import * as clientCommand from "../../src/commands/clientCommands"; import { LanguageClient } from "vscode-languageclient/node"; import { + ExportedConnections, InsightDetails, ServerDetails, ServerType, @@ -1112,6 +1113,179 @@ describe("serverCommand", () => { }); }); + describe("importConnections", () => { + let showOpenDialogStub: sinon.SinonStub; + let kdbOutputLogStub: sinon.SinonStub; + let addImportedConnectionsStub: sinon.SinonStub; + + beforeEach(() => { + showOpenDialogStub = sinon.stub(vscode.window, "showOpenDialog"); + kdbOutputLogStub = sinon.stub(coreUtils, "kdbOutputLog"); + addImportedConnectionsStub = sinon.stub( + serverCommand, + "addImportedConnections", + ); + }); + + afterEach(() => { + sinon.restore(); + mock.restore(); + }); + + it("should log an error if no file is selected", async () => { + showOpenDialogStub.resolves(undefined); + + await serverCommand.importConnections(); + + assert( + kdbOutputLogStub.calledWith( + "[IMPORT CONNECTION]No file selected", + "ERROR", + ), + ); + }); + }); + + describe("addImportedConnections", async () => { + let addInsightsConnectionStub: sinon.SinonStub; + let addKdbConnectionStub: sinon.SinonStub; + let kdbOutputLogStub: sinon.SinonStub; + let showInformationMessageStub: sinon.SinonStub; + let getInsightsStub: sinon.SinonStub; + let getServersStub: sinon.SinonStub; + const kdbNodeImport1: KdbNode = { + label: "local", + details: { + serverName: "testKdb", + serverAlias: "local", + serverPort: "1818", + auth: false, + managed: false, + tls: false, + }, + collapsibleState: vscode.TreeItemCollapsibleState.None, + contextValue: "kdbNode", + children: [], + getTooltip: function (): vscode.MarkdownString { + throw new Error("Function not implemented."); + }, + getDescription: function (): string { + throw new Error("Function not implemented."); + }, + iconPath: undefined, + }; + const insightsNodeImport1: InsightsNode = { + label: "testInsight", + details: { + server: "testInsight", + alias: "testInsight", + auth: false, + }, + collapsibleState: vscode.TreeItemCollapsibleState.None, + contextValue: "insightsNode", + children: [], + getTooltip: function (): vscode.MarkdownString { + throw new Error("Function not implemented."); + }, + getDescription: function (): string { + throw new Error("Function not implemented."); + }, + iconPath: undefined, + }; + + beforeEach(() => { + addInsightsConnectionStub = sinon.stub( + serverCommand, + "addInsightsConnection", + ); + addKdbConnectionStub = sinon.stub(serverCommand, "addKdbConnection"); + kdbOutputLogStub = sinon.stub(coreUtils, "kdbOutputLog"); + getInsightsStub = sinon.stub(coreUtils, "getInsights").returns(undefined); + getServersStub = sinon.stub(coreUtils, "getServers").returns(undefined); + showInformationMessageStub = sinon.stub( + vscode.window, + "showInformationMessage", + ); + ext.connectionsList.length = 0; + }); + + afterEach(() => { + sinon.restore(); + ext.connectionsList.length = 0; + }); + + it("should add insights connections with unique aliases", async () => { + ext.connectionsList.push(insightsNodeImport1, kdbNodeImport1); + const importedConnections: ExportedConnections = { + connections: { + Insights: [ + { + alias: "testImportInsights1", + server: "testInsight", + auth: false, + }, + { + alias: "testImportInsights1", + server: "testInsight2", + auth: false, + }, + ], + KDB: [], + }, + }; + + await serverCommand.addImportedConnections(importedConnections); + + sinon.assert.notCalled(addKdbConnectionStub); + }); + + it("should log success message and show information message", async () => { + const importedConnections: ExportedConnections = { + connections: { + Insights: [], + KDB: [], + }, + }; + + await serverCommand.addImportedConnections(importedConnections); + + assert( + kdbOutputLogStub.calledWith( + "[IMPORT CONNECTION]Connections imported successfully", + "INFO", + ), + ); + assert( + showInformationMessageStub.calledWith( + "Connections imported successfully", + ), + ); + }); + + it("should add kdb connections", () => { + ext.connectionsList.push(insightsNodeImport1, kdbNodeImport1); + const importedConnections: ExportedConnections = { + connections: { + Insights: [], + KDB: [ + { + serverName: "testKdb", + serverAlias: "testKdb", + serverPort: "1818", + auth: false, + managed: false, + tls: false, + }, + ], + }, + }; + + serverCommand.addImportedConnections(importedConnections); + + sinon.assert.notCalled(addInsightsConnectionStub); + }); + }); + describe("writeQueryResultsToView", () => { it("should call executeCommand with correct arguments", () => { const result = { data: [1, 2, 3] }; diff --git a/test/suite/services.test.ts b/test/suite/services.test.ts index bf7e6299..3bb524d8 100644 --- a/test/suite/services.test.ts +++ b/test/suite/services.test.ts @@ -16,7 +16,6 @@ import assert from "node:assert"; import sinon from "sinon"; import { ExtensionContext, - MarkdownString, TreeItemCollapsibleState, Uri, WebviewPanel, @@ -67,7 +66,6 @@ import { ConnectionLabel, Labels } from "../../src/models/labels"; import { Insights, Server, - ServerDetails, ServerType, } from "../../src/models/connectionsModels"; import AuthSettings from "../../src/utils/secretStorage"; @@ -1036,6 +1034,44 @@ describe("connectionManagerService", () => { }); }); + describe("retrieveListOfConnectionsNames", () => { + it("Should return the list of connection names", () => { + ext.connectionsList.push(kdbNode, insightNode); + const result = connectionManagerService.retrieveListOfConnectionsNames(); + assert.strictEqual(result.size, 2); + }); + }); + + describe("checkConnAlias", () => { + it("Should return localInsights when connection is insights and alias equals local", () => { + const result = connectionManagerService.checkConnAlias("local", true); + assert.strictEqual(result, "localInsights"); + }); + + it("Should note return localInsights when connection is insights and alias not equals local", () => { + const result = connectionManagerService.checkConnAlias("notLocal", true); + assert.strictEqual(result, "notLocal"); + }); + + it("Should return local when connection is kdb and alias equals local and local conn not exist already", () => { + const result = connectionManagerService.checkConnAlias( + "local", + false, + false, + ); + assert.strictEqual(result, "local"); + }); + + it("Should return localKDB when connection is kdb and alias equals local and local conn exist already", () => { + const result = connectionManagerService.checkConnAlias( + "local", + false, + true, + ); + assert.strictEqual(result, "localKDB"); + }); + }); + describe("removeConnectionFromContextString", () => { it("Should remove the connection from context string", () => { ext.connectedContextStrings.push("testLabel");