diff --git a/.github/workflows/app-sec-template.yml b/.github/workflows/app-sec-template.yml index d3bf38e5..84148b07 100644 --- a/.github/workflows/app-sec-template.yml +++ b/.github/workflows/app-sec-template.yml @@ -12,12 +12,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout source code - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Install Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: 20.x @@ -29,7 +29,7 @@ jobs: uses: martinbeentjes/npm-get-version-action@v1.3.1 - name: Download lcov result from test job - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: lcov diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f5ced72b..bc76f879 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -18,7 +18,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Node.js uses: actions/setup-node@v4 with: @@ -47,7 +47,7 @@ jobs: - name: Upload lcov result for app-sec job if: runner.os == 'Linux' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: lcov path: coverage-reports/lcov.info diff --git a/.github/workflows/prod_release.yml b/.github/workflows/prod_release.yml index dd175e27..fbc2b73a 100644 --- a/.github/workflows/prod_release.yml +++ b/.github/workflows/prod_release.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Install Node.js @@ -29,13 +29,13 @@ jobs: - name: Packaging run: npm run package - name: Upload artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: KDB-VSCode-Extension path: ./kdb-*vsix retention-days: 1 - name: Upload lcov result for app-sec job - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: lcov path: coverage-reports/lcov.info @@ -58,11 +58,11 @@ jobs: VERSION=${{ github.ref_name }} echo "run_tag=$(echo ${VERSION:1})" >> $GITHUB_OUTPUT - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Download VSIX file from build job - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: KDB-VSCode-Extension - name: Get Body @@ -102,7 +102,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Install Node.js diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a538d27f..17625d59 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,6 +5,7 @@ on: branches: - dev - main + - feature/* env: NODE_ENV: production @@ -30,13 +31,13 @@ jobs: - name: Packaging run: npm run package - name: Upload artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: KDB-VSCode-Extension path: ./kdb-*vsix retention-days: 1 - name: Upload lcov result for app-sec job - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: lcov path: coverage-reports/lcov.info @@ -54,7 +55,7 @@ jobs: needs: app-sec steps: - name: Download VSIX file from build job - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: KDB-VSCode-Extension - name: Release to Portal diff --git a/CHANGELOG.md b/CHANGELOG.md index 97f466f4..6e895557 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,23 @@ All notable changes to the **kdb VS Code extension** are documented in this file. +# v1.7.0 + +### Enhancements + +- Now it's possible to edit existing connections +- Dialog offering to reconnect to connection that was edited (if the connection was connected) +- Added labels for connections +- Ability to connect Insights servers with self signed SSL certificate + +### Fixes + +- https is prefixed for unschemed Insights server urls +- Use the custom editor to open datasource when renaming or deleting +- Removed unnecessary buttons in walkthrough +- Fixed toggle parameter cache doesn't work in workbooks +- Fixed files can't be executed from entity tree + # v1.6.1 ### Fixes diff --git a/README.md b/README.md index 024b1f65..51bd1d64 100644 --- a/README.md +++ b/README.md @@ -141,6 +141,7 @@ When you select **Bundled q** as the connection type and set the following prope | Server Name | The name is already set as **local**. | | The connection address | This is already be set as `127.0.0.1` which corresponds to your **localhost**. | | Port | Set the port for the kdb server. Ensure the port used doesn't conflict with any other running q process; e.g. 5002. [Read here for more about setting a q port](https://code.kx.com/q/basics/ipc/) | +| Label Name | Select the label you want to assign the connection to | 1. Click **Create Connection** and the connection appears under **CONNECTIONS** in the primary sidebar.. @@ -166,8 +167,9 @@ Set the following properties: | Username | If authentication is needed, fill in the username otherwise, leave **blank** | | Password | If authentication is needed, fill in the password otherwise, leave **blank** | | Enable TLS Encryption | Check the box is TLS is enabled. Learn more [about TLS encryption](https://code.kx.com/q/kb/ssl/). | +| Label Name | Select the label you want to assign the connection to | -![setendpoint](https://github.com/KxSystems/kx-vscode/blob/main/img/myq.png?raw=true). +![setendpoint](https://github.com/KxSystems/kx-vscode/blob/main/img/myq.png?raw=true) 1. Click **Create Connection** and the connection appears under **CONNECTIONS** in the primary sidebar. @@ -185,6 +187,7 @@ Set the following properties: | ---------------------- | -------------------------------------------------------------------------------------------------------------------------- | | Server Name | The server name / alias. This can be any name, aside from `local`, which is used by [Bundled q connection](#bundled-q) | | The connection address | This is the remote address of your **kdb Insights Enterprise** deployment: e.g. `https://mykdbinsights.cloudapp.azure.com` | +| Label Name | Select the label you want to assign the connection to | ![connecttoinsights](https://github.com/KxSystems/kx-vscode/blob/main/img/insightsconnection.png?raw=true) @@ -226,6 +229,118 @@ The 'meta' node contains a child node for each of the child sections in the json You can refresh the meta data view at any time by choosing **Refresh meta data** from the right-click menu of an Insights connection. +## Edit Connections + +To edit an existing connection, simply right-click the connection you wish to edit and select the **Edit connection** option. + +![Edit connection option](https://github.com/KxSystems/kx-vscode/blob/main/img/select-edit-connection.png?raw=true) + +> NOTE: Editing an **active connection** may require you to **restart** the connection. If so, you will be prompted to reconnect after saving your changes. + +![Edit connected connection dialog](https://github.com/KxSystems/kx-vscode/blob/main/img/edit-connected-connection-dialog.png?raw=true) + +### Edit Bundle q connection + +When you select to edit an **Bundled q** connection, you can edit the following properties: + +| Property | Description | +| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Server Name | The name is already set as **local** and **cannot be edited**. | +| The connection address | This is already be set as `127.0.0.1` which corresponds to your **localhost** and **cannot be edited**. | +| Port | Set the port for the kdb server. Ensure the port used doesn't conflict with any other running q process; e.g. 5002. [Read here for more about setting a q port](https://code.kx.com/q/basics/ipc/) | +| Label Name | Select the label you want to assign the connection to | + +![Edit Bundle q connection](https://github.com/KxSystems/kx-vscode/blob/main/img/edit-bundle-q-conn-form.png?raw=true) + +### Edit My q connection + +When you select to edit an **My q** connection, you can edit the following properties: + +| Property | Description | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Server Name | The server name / alias. The server name selected cannot be **local** or **insights**, as these are reserved for use by [Bundled q connections](#bundled-q) and [Insights connections](#insights-connection), respectively; e.g. dev | +| The connection address | Set to the IP address of the kdb server; e.g. **localhost**. | +| Port | Enter the port used by the kdb server; e.g. 5001. Learn more about [setting a q port](https://code.kx.com/q/basics/ipc/) . | +| Edit Auth options | Check the box if you wish to change **Auth options**. If you want to **remove the Auth** for this connection, select this checkbox and leave the **Username** and **Password** fields in **blank** | +| Username | If authentication is needed, fill in the username otherwise, leave **blank** | +| Password | If authentication is needed, fill in the password otherwise, leave **blank** | +| Enable TLS Encryption | Check the box is TLS is enabled. Learn more [about TLS encryption](https://code.kx.com/q/kb/ssl/). | +| Label Name | Select the label you want to assign the connection to | + +![Edit My q connection](https://github.com/KxSystems/kx-vscode/blob/main/img/edit-my-q-conn-form.png?raw=true) + +### Edit Insights connection + +When you select to edit an **Insights** connection, you can edit the following properties: + +| Property | Description | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Server Name | The server name / alias. This can be any name, aside from `local`, which is used by [Bundled q connection](#bundled-q) | +| The connection address | This is the remote address of your **kdb Insights Enterprise** deployment: e.g. `https://mykdbinsights.cloudapp.azure.com` | +| Define Realm | Specify the Keycloak realm for authentication. Usually the realm is set to `insights`, which is the default value used by the extension. You only need to change this field if a different realm has been configured on your server. | +| Label Name | Select the label you want to assign the connection to | + +![Edit Insights connection](https://github.com/KxSystems/kx-vscode/blob/main/img/edit-insights-conn-form.png?raw=true) + +## Connection Labels + +Connection Labels allow you to categorize and organize your connections by assigning them distinct names and colors, making it easier to manage and locate specific connections within the application. + +![Connection Tree With Labels](https://github.com/KxSystems/kx-vscode/blob/main/img/conn-labels-tree.png?raw=true) + +### Create New Label + +To create a Label, start by **editing** or **creating** a connection. At the **bottom of the form**, you'll see a **Create New Label** button. + +![Create New Label Button](https://github.com/KxSystems/kx-vscode/blob/main/img/create-new-label-btn.png?raw=true) + +Clicking this button will open a **dialog** where you can enter a **Label name** and choose a **Label color** for it. You can then create the Label by clicking 'Create' or cancel the process by clicking 'Cancel.' + +| Property | Description | +| ----------- | --------------------------------------------------------- | +| Label Name | Enter a name for the label. | +| Label color | Select the color in the list of colors for the new label. | + +![Create New Label](https://github.com/KxSystems/kx-vscode/blob/main/img/create-new-label-dialog.png?raw=true) + +### Add Label to a connection + +To add a Label to a connection, start by **editing** or **creating** a connection. At the **bottom of the form**, you'll see a **Label Name** dropdown to select a Label, select the Label and click in **Edit or Create Connection**. + +| Property | Description | +| ---------- | ------------------------------------ | +| Label Name | Select Label from the list of Labels | + +![Select Label](https://github.com/KxSystems/kx-vscode/blob/main/img/conn-labels.png?raw=true) + +### Rename Label + +Right-click the label at Connection Tree and select **Rename label**. + +![Rename Label Opt](https://github.com/KxSystems/kx-vscode/blob/main/img/labels-rename-opt.png?raw=true) + +A prompt will be displayed at the top of the screen with the field to edit the name of the Label. + +![Rename Label](https://github.com/KxSystems/kx-vscode/blob/main/img/labels-rename.png?raw=true) + +### Edit Label Color + +Right-click the label at Connection Tree and select **Edit label color**. + +![Edit Label Color Opt](https://github.com/KxSystems/kx-vscode/blob/main/img/labels-edit-color-opt.png?raw=true) + +A prompt will be displayed at the top of the screen with the field to edit the color of the Label. + +![Edit Label Color](https://github.com/KxSystems/kx-vscode/blob/main/img/labels-edit-color.png?raw=true) + +### Delete Label + +Right-click the label at Connection Tree and select **Delete label**. + +![Delete Label Opt](https://github.com/KxSystems/kx-vscode/blob/main/img/labels-delete-opt.png?raw=true) + +> The connections assign to the Label will **not be deleted**. + ## kdb language server A kdb language server is bundled with the kdb VS Code extension. It offers various common features to aid in the development of kdb code, including: diff --git a/img/bundleqform.png b/img/bundleqform.png index 20764089..7c3e6d37 100644 Binary files a/img/bundleqform.png and b/img/bundleqform.png differ diff --git a/img/conn-labels-tree.png b/img/conn-labels-tree.png new file mode 100644 index 00000000..4d2d0b4c Binary files /dev/null and b/img/conn-labels-tree.png differ diff --git a/img/conn-labels.png b/img/conn-labels.png new file mode 100644 index 00000000..1a354126 Binary files /dev/null and b/img/conn-labels.png differ diff --git a/img/create-new-label-btn.png b/img/create-new-label-btn.png new file mode 100644 index 00000000..7495c1ad Binary files /dev/null and b/img/create-new-label-btn.png differ diff --git a/img/create-new-label-dialog.png b/img/create-new-label-dialog.png new file mode 100644 index 00000000..4286a40c Binary files /dev/null and b/img/create-new-label-dialog.png differ diff --git a/img/edit-bundle-q-conn-form.png b/img/edit-bundle-q-conn-form.png new file mode 100644 index 00000000..69071fd0 Binary files /dev/null and b/img/edit-bundle-q-conn-form.png differ diff --git a/img/edit-connected-connection-dialog.png b/img/edit-connected-connection-dialog.png new file mode 100644 index 00000000..867d72d4 Binary files /dev/null and b/img/edit-connected-connection-dialog.png differ diff --git a/img/edit-insights-conn-form.png b/img/edit-insights-conn-form.png new file mode 100644 index 00000000..f29da1b3 Binary files /dev/null and b/img/edit-insights-conn-form.png differ diff --git a/img/edit-my-q-conn-form.png b/img/edit-my-q-conn-form.png new file mode 100644 index 00000000..ebd528af Binary files /dev/null and b/img/edit-my-q-conn-form.png differ diff --git a/img/insightsconnection.png b/img/insightsconnection.png index 233c4f30..f1051074 100644 Binary files a/img/insightsconnection.png and b/img/insightsconnection.png differ diff --git a/img/labels-delete-opt.png b/img/labels-delete-opt.png new file mode 100644 index 00000000..2269f85d Binary files /dev/null and b/img/labels-delete-opt.png differ diff --git a/img/labels-edit-color-opt.png b/img/labels-edit-color-opt.png new file mode 100644 index 00000000..4b911e6f Binary files /dev/null and b/img/labels-edit-color-opt.png differ diff --git a/img/labels-edit-color.png b/img/labels-edit-color.png new file mode 100644 index 00000000..274bc780 Binary files /dev/null and b/img/labels-edit-color.png differ diff --git a/img/labels-rename-opt.png b/img/labels-rename-opt.png new file mode 100644 index 00000000..df92f33a Binary files /dev/null and b/img/labels-rename-opt.png differ diff --git a/img/labels-rename.png b/img/labels-rename.png new file mode 100644 index 00000000..b76642b3 Binary files /dev/null and b/img/labels-rename.png differ diff --git a/img/myq.png b/img/myq.png index 2d32a085..a1e70e53 100644 Binary files a/img/myq.png and b/img/myq.png differ diff --git a/img/select-edit-connection.png b/img/select-edit-connection.png new file mode 100644 index 00000000..4725c151 Binary files /dev/null and b/img/select-edit-connection.png differ diff --git a/package-lock.json b/package-lock.json index 487580ac..cefd8212 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,19 @@ { "name": "kdb", - "version": "1.6.1", + "version": "1.7.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "kdb", - "version": "1.6.1", + "version": "1.7.0", "license": "MIT", "dependencies": { "@types/graceful-fs": "^4.1.9", "@vscode/webview-ui-toolkit": "^1.4.0", "@windozer/node-q": "^2.6.0", "ag-grid-community": "^32.0.1", - "axios": "^1.7.2", + "axios": "^1.7.4", "chevrotain": "^10.5.0", "csv-parser": "^3.0.0", "extract-zip": "^2.0.1", @@ -1680,9 +1680,10 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/axios": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", - "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", + "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", + "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -1745,12 +1746,13 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, + "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -2422,10 +2424,11 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -2977,6 +2980,7 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -4485,6 +4489,7 @@ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, diff --git a/package.json b/package.json index 90592dd8..d065e960 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { "name": "kdb", "displayName": "kdb", - "description": "IDE support for kdb product suite", + "description": "IDE support for kdb product suite including the q programming language", "publisher": "KX", - "version": "1.6.1", + "version": "1.7.0", "engines": { "vscode": "^1.86.0" }, @@ -35,6 +35,10 @@ "onCommand:kdb.newConnection.createNewInsightConnection", "onCommand:kdb.newConnection.createNewConnection", "onCommand:kdb.newConnection.createNewBundledConnection", + "onCommand:kdb.newConnection.editInsightsConnection", + "onCommand:kdb.newConnection.editMyQConnection", + "onCommand:kdb.newConnection.editBundledConnection", + "onCommand:kdb.labels.create", "onView:kdb-datasources-explorer", "onTerminalProfile:kdb.q-terminal", "onLanguage:python" @@ -50,7 +54,7 @@ { "id": "install", "title": "Register, acquire license and download the q runtime", - "description": " \n[Install runtime](command:kdb.installTools)\n[Ignore/hide this help](command:kdb.hideWalkthrough)", + "description": " \n[Install runtime](command:kdb.installTools)", "media": { "image": "resources/kx_install.png", "altText": "register" @@ -63,7 +67,7 @@ { "id": "view", "title": "q runtime installed", - "description": " \n[Ignore/hide this help](command:kdb.hideWalkthrough)", + "description": " \n", "media": { "markdown": "out/qinstall.md" }, @@ -176,6 +180,18 @@ "description": "Connection map for workspace files", "default": {}, "scope": "resource" + }, + "kdb.connectionLabels": { + "type": "array", + "description": "List of label names and colorset", + "default": [], + "scope": "resource" + }, + "kdb.labelsConnectionMap": { + "type": "array", + "description": "Labels connection map", + "default": [], + "scope": "resource" } } }, @@ -253,6 +269,11 @@ "title": "New connection...", "icon": "$(add)" }, + { + "category": "KX", + "command": "kdb.editConnection", + "title": "Edit connection" + }, { "category": "KX", "command": "kdb.removeConnection", @@ -395,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": [ @@ -431,13 +467,13 @@ "command": "kdb.execute.block", "key": "ctrl+shift+e", "mac": "cmd+shift+e", - "when": "editorLangId == q && !(resourceFilename =~ /.kdb.q/)" + "when": "editorLangId == q" }, { "command": "kdb.toggleParameterCache", "key": "ctrl+shift+y", "mac": "cmd+shift+y", - "when": "editorLangId == q && !(resourceFilename =~ /.kdb.q/)" + "when": "editorLangId == q" } ], "snippets": [ @@ -459,8 +495,8 @@ ".quke" ], "icon": { - "dark": "./resources/scratchpad.svg", - "light": "./resources/scratchpad.svg" + "dark": "./resources/dark/scratchpad.svg", + "light": "./resources/light/scratchpad.svg" }, "configuration": "./language-configuration.json" }, @@ -561,6 +597,10 @@ "command": "kdb.addConnection", "when": "false" }, + { + "command": "kdb.editConnection", + "when": "false" + }, { "command": "kdb.removeConnection", "when": "false" @@ -665,6 +705,11 @@ "when": "view == kdb-servers && viewItem not in kdb.connected && (viewItem in kdb.rootNodes || viewItem in kdb.insightsNodes)", "group": "connection@1" }, + { + "command": "kdb.editConnection", + "when": "view == kdb-servers && (viewItem in kdb.rootNodes || viewItem in kdb.insightsNodes)", + "group": "connection@4" + }, { "command": "kdb.active.connection", "when": "view == kdb-servers && viewItem in kdb.connected && (viewItem in kdb.rootNodes || viewItem in kdb.insightsNodes) && viewItem not in kdb.connected.active", @@ -724,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": [ @@ -789,6 +849,18 @@ "group": "q@1", "when": "editorLangId == python && (resourceFilename =~ /.kdb.py/ || kdb.insightsConnected)" } + ], + "explorer/context": [ + { + "command": "kdb.execute.fileQuery", + "group": "q", + "when": "resourceExtname == .q" + }, + { + "command": "kdb.execute.pythonFileScratchpadQuery", + "group": "q", + "when": "resourceExtname == .py" + } ] }, "customEditors": [ @@ -875,7 +947,7 @@ "@vscode/webview-ui-toolkit": "^1.4.0", "@windozer/node-q": "^2.6.0", "ag-grid-community": "^32.0.1", - "axios": "^1.7.2", + "axios": "^1.7.4", "chevrotain": "^10.5.0", "csv-parser": "^3.0.0", "extract-zip": "^2.0.1", diff --git a/resources/metaIcons/aggicon.svg b/resources/dark/aggicon.svg similarity index 100% rename from resources/metaIcons/aggicon.svg rename to resources/dark/aggicon.svg diff --git a/resources/metaIcons/apiicon.svg b/resources/dark/apiicon.svg similarity index 100% rename from resources/metaIcons/apiicon.svg rename to resources/dark/apiicon.svg diff --git a/resources/metaIcons/dapicon.svg b/resources/dark/dapicon.svg similarity index 100% rename from resources/metaIcons/dapicon.svg rename to resources/dark/dapicon.svg diff --git a/resources/datasource-active.svg b/resources/dark/datasource-active.svg similarity index 99% rename from resources/datasource-active.svg rename to resources/dark/datasource-active.svg index 8f0e696d..e72579b8 100644 --- a/resources/datasource-active.svg +++ b/resources/dark/datasource-active.svg @@ -1,6 +1,6 @@ + fill="#0FBC7A" /> \ No newline at end of file diff --git a/resources/datasource-connected.svg b/resources/dark/datasource-connected.svg similarity index 99% rename from resources/datasource-connected.svg rename to resources/dark/datasource-connected.svg index 8e535452..7a23bdd2 100644 --- a/resources/datasource-connected.svg +++ b/resources/dark/datasource-connected.svg @@ -1,6 +1,6 @@ + fill="#CD3131" /> \ No newline at end of file diff --git a/resources/datasource.svg b/resources/dark/datasource.svg similarity index 99% rename from resources/datasource.svg rename to resources/dark/datasource.svg index e8b46bf6..778a908b 100644 --- a/resources/datasource.svg +++ b/resources/dark/datasource.svg @@ -1,6 +1,6 @@ + fill="#CCCCCC" /> \ No newline at end of file diff --git a/resources/dark/dictionaries.svg b/resources/dark/dictionaries.svg new file mode 100644 index 00000000..52edcfe5 --- /dev/null +++ b/resources/dark/dictionaries.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/resources/dark/functions.svg b/resources/dark/functions.svg new file mode 100644 index 00000000..713fc615 --- /dev/null +++ b/resources/dark/functions.svg @@ -0,0 +1,8 @@ + + + + + + + + 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/metaIcons/metaicon.svg b/resources/dark/metaicon.svg similarity index 100% rename from resources/metaIcons/metaicon.svg rename to resources/dark/metaicon.svg diff --git a/resources/dark/namespaces.svg b/resources/dark/namespaces.svg new file mode 100644 index 00000000..5e830bbc --- /dev/null +++ b/resources/dark/namespaces.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/resources/dark/p-insights-active.svg b/resources/dark/p-insights-active.svg index 45804af7..0c2b46d5 100644 --- a/resources/dark/p-insights-active.svg +++ b/resources/dark/p-insights-active.svg @@ -1,9 +1,12 @@ - - + + - + - + \ No newline at end of file diff --git a/resources/dark/p-insights-connected.svg b/resources/dark/p-insights-connected.svg index 3f1d2311..748c61d1 100644 --- a/resources/dark/p-insights-connected.svg +++ b/resources/dark/p-insights-connected.svg @@ -1,9 +1,12 @@ - - + + - + - + \ No newline at end of file diff --git a/resources/dark/p-insights.svg b/resources/dark/p-insights.svg index e1605554..7aaeed6f 100644 --- a/resources/dark/p-insights.svg +++ b/resources/dark/p-insights.svg @@ -1,4 +1,4 @@ - - + + \ No newline at end of file diff --git a/resources/p-q-connection-active.svg b/resources/dark/p-q-connection-active.svg similarity index 98% rename from resources/p-q-connection-active.svg rename to resources/dark/p-q-connection-active.svg index 2e2b05dd..c9d2dc57 100644 --- a/resources/p-q-connection-active.svg +++ b/resources/dark/p-q-connection-active.svg @@ -6,7 +6,7 @@ + fill="#0FBC7A" /> \ No newline at end of file diff --git a/resources/p-q-connection-connected.svg b/resources/dark/p-q-connection-connected.svg similarity index 98% rename from resources/p-q-connection-connected.svg rename to resources/dark/p-q-connection-connected.svg index 0e2aebe0..52e7b21c 100644 --- a/resources/p-q-connection-connected.svg +++ b/resources/dark/p-q-connection-connected.svg @@ -6,7 +6,7 @@ + fill="#CD3131" /> \ No newline at end of file diff --git a/resources/p-q-connection.svg b/resources/dark/p-q-connection.svg similarity index 98% rename from resources/p-q-connection.svg rename to resources/dark/p-q-connection.svg index dfb3e84c..28ec8d60 100644 --- a/resources/p-q-connection.svg +++ b/resources/dark/p-q-connection.svg @@ -6,7 +6,7 @@ + fill="#CCCCCC" /> \ No newline at end of file diff --git a/resources/metaIcons/packageicon.svg b/resources/dark/packageicon.svg similarity index 100% rename from resources/metaIcons/packageicon.svg rename to resources/dark/packageicon.svg diff --git a/resources/python-active.svg b/resources/dark/python-active.svg similarity index 93% rename from resources/python-active.svg rename to resources/dark/python-active.svg index f2c4ff07..90274c5d 100644 --- a/resources/python-active.svg +++ b/resources/dark/python-active.svg @@ -1,6 +1,6 @@ - - \ No newline at end of file diff --git a/resources/python-connected.svg b/resources/dark/python-connected.svg similarity index 93% rename from resources/python-connected.svg rename to resources/dark/python-connected.svg index 79122228..def2a249 100644 --- a/resources/python-connected.svg +++ b/resources/dark/python-connected.svg @@ -1,6 +1,6 @@ - - \ No newline at end of file diff --git a/resources/python.svg b/resources/dark/python.svg similarity index 93% rename from resources/python.svg rename to resources/dark/python.svg index 703b1e9d..bd02a72b 100644 --- a/resources/python.svg +++ b/resources/dark/python.svg @@ -1,6 +1,6 @@ - - \ No newline at end of file diff --git a/resources/metaIcons/rcicon.svg b/resources/dark/rcicon.svg similarity index 100% rename from resources/metaIcons/rcicon.svg rename to resources/dark/rcicon.svg diff --git a/resources/metaIcons/schemaicon.svg b/resources/dark/schemaicon.svg similarity index 100% rename from resources/metaIcons/schemaicon.svg rename to resources/dark/schemaicon.svg diff --git a/resources/scratchpad-active.svg b/resources/dark/scratchpad-active.svg similarity index 97% rename from resources/scratchpad-active.svg rename to resources/dark/scratchpad-active.svg index 764e49ae..840679a1 100644 --- a/resources/scratchpad-active.svg +++ b/resources/dark/scratchpad-active.svg @@ -3,12 +3,12 @@ xmlns="http://www.w3.org/2000/svg"> diff --git a/resources/scratchpad-connected.svg b/resources/dark/scratchpad-connected.svg similarity index 97% rename from resources/scratchpad-connected.svg rename to resources/dark/scratchpad-connected.svg index bfd203d1..54d62f00 100644 --- a/resources/scratchpad-connected.svg +++ b/resources/dark/scratchpad-connected.svg @@ -3,12 +3,12 @@ xmlns="http://www.w3.org/2000/svg"> \ No newline at end of file diff --git a/resources/scratchpad.svg b/resources/dark/scratchpad.svg similarity index 97% rename from resources/scratchpad.svg rename to resources/dark/scratchpad.svg index 5e5553ad..8ec6d658 100644 --- a/resources/scratchpad.svg +++ b/resources/dark/scratchpad.svg @@ -3,12 +3,12 @@ xmlns="http://www.w3.org/2000/svg"> \ No newline at end of file diff --git a/resources/dark/tables.svg b/resources/dark/tables.svg new file mode 100644 index 00000000..c931182b --- /dev/null +++ b/resources/dark/tables.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/resources/dark/variables.svg b/resources/dark/variables.svg new file mode 100644 index 00000000..f63d3082 --- /dev/null +++ b/resources/dark/variables.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/resources/dark/views.svg b/resources/dark/views.svg new file mode 100644 index 00000000..f0cc49b6 --- /dev/null +++ b/resources/dark/views.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/resources/light/aggicon.svg b/resources/light/aggicon.svg new file mode 100644 index 00000000..81d31a44 --- /dev/null +++ b/resources/light/aggicon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/resources/light/apiicon.svg b/resources/light/apiicon.svg new file mode 100644 index 00000000..ae9919c1 --- /dev/null +++ b/resources/light/apiicon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/resources/light/dapicon.svg b/resources/light/dapicon.svg new file mode 100644 index 00000000..f2c6e9a6 --- /dev/null +++ b/resources/light/dapicon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/resources/light/datasource-active.svg b/resources/light/datasource-active.svg new file mode 100644 index 00000000..231af6ab --- /dev/null +++ b/resources/light/datasource-active.svg @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/resources/light/datasource-connected.svg b/resources/light/datasource-connected.svg new file mode 100644 index 00000000..7a23bdd2 --- /dev/null +++ b/resources/light/datasource-connected.svg @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/resources/light/datasource.svg b/resources/light/datasource.svg new file mode 100644 index 00000000..f3844308 --- /dev/null +++ b/resources/light/datasource.svg @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/resources/light/dictionaries.svg b/resources/light/dictionaries.svg new file mode 100644 index 00000000..ee540e3e --- /dev/null +++ b/resources/light/dictionaries.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/resources/light/functions.svg b/resources/light/functions.svg new file mode 100644 index 00000000..2e0ed2c8 --- /dev/null +++ b/resources/light/functions.svg @@ -0,0 +1,8 @@ + + + + + + + + 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/resources/light/metaicon.svg b/resources/light/metaicon.svg new file mode 100644 index 00000000..aa8a41f4 --- /dev/null +++ b/resources/light/metaicon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/resources/light/namespaces.svg b/resources/light/namespaces.svg new file mode 100644 index 00000000..485287ce --- /dev/null +++ b/resources/light/namespaces.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/resources/light/p-insights-active.svg b/resources/light/p-insights-active.svg index 45804af7..62ca295c 100644 --- a/resources/light/p-insights-active.svg +++ b/resources/light/p-insights-active.svg @@ -1,9 +1,12 @@ - - + + - + - + \ No newline at end of file diff --git a/resources/light/p-insights-connected.svg b/resources/light/p-insights-connected.svg index 3f1d2311..748c61d1 100644 --- a/resources/light/p-insights-connected.svg +++ b/resources/light/p-insights-connected.svg @@ -1,9 +1,12 @@ - - + + - + - + \ No newline at end of file diff --git a/resources/light/p-insights.svg b/resources/light/p-insights.svg index 556b758d..31e0d520 100644 --- a/resources/light/p-insights.svg +++ b/resources/light/p-insights.svg @@ -1,4 +1,4 @@ - - + + \ No newline at end of file diff --git a/resources/light/p-q-connection-active.svg b/resources/light/p-q-connection-active.svg new file mode 100644 index 00000000..eb21e6e7 --- /dev/null +++ b/resources/light/p-q-connection-active.svg @@ -0,0 +1,12 @@ + + + + + + + + + \ No newline at end of file diff --git a/resources/light/p-q-connection-connected.svg b/resources/light/p-q-connection-connected.svg new file mode 100644 index 00000000..52e7b21c --- /dev/null +++ b/resources/light/p-q-connection-connected.svg @@ -0,0 +1,12 @@ + + + + + + + + + \ No newline at end of file diff --git a/resources/light/p-q-connection.svg b/resources/light/p-q-connection.svg new file mode 100644 index 00000000..c5dbd845 --- /dev/null +++ b/resources/light/p-q-connection.svg @@ -0,0 +1,12 @@ + + + + + + + + + \ No newline at end of file diff --git a/resources/light/packageicon.svg b/resources/light/packageicon.svg new file mode 100644 index 00000000..bf745c96 --- /dev/null +++ b/resources/light/packageicon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/resources/light/python-active.svg b/resources/light/python-active.svg new file mode 100644 index 00000000..03750185 --- /dev/null +++ b/resources/light/python-active.svg @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/resources/light/python-connected.svg b/resources/light/python-connected.svg new file mode 100644 index 00000000..def2a249 --- /dev/null +++ b/resources/light/python-connected.svg @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/resources/light/python.svg b/resources/light/python.svg new file mode 100644 index 00000000..2d300d2a --- /dev/null +++ b/resources/light/python.svg @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/resources/light/rcicon.svg b/resources/light/rcicon.svg new file mode 100644 index 00000000..157a4f4a --- /dev/null +++ b/resources/light/rcicon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/resources/light/schemaicon.svg b/resources/light/schemaicon.svg new file mode 100644 index 00000000..edb7a75f --- /dev/null +++ b/resources/light/schemaicon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/resources/light/scratchpad-active.svg b/resources/light/scratchpad-active.svg new file mode 100644 index 00000000..5b4f57df --- /dev/null +++ b/resources/light/scratchpad-active.svg @@ -0,0 +1,15 @@ + + + + + \ No newline at end of file diff --git a/resources/light/scratchpad-connected.svg b/resources/light/scratchpad-connected.svg new file mode 100644 index 00000000..54d62f00 --- /dev/null +++ b/resources/light/scratchpad-connected.svg @@ -0,0 +1,14 @@ + + + + \ No newline at end of file diff --git a/resources/light/scratchpad.svg b/resources/light/scratchpad.svg new file mode 100644 index 00000000..579c32cc --- /dev/null +++ b/resources/light/scratchpad.svg @@ -0,0 +1,14 @@ + + + + \ No newline at end of file diff --git a/resources/light/tables.svg b/resources/light/tables.svg new file mode 100644 index 00000000..b959f149 --- /dev/null +++ b/resources/light/tables.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/resources/light/variables.svg b/resources/light/variables.svg new file mode 100644 index 00000000..1523ff80 --- /dev/null +++ b/resources/light/variables.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/resources/light/views.svg b/resources/light/views.svg new file mode 100644 index 00000000..3470188c --- /dev/null +++ b/resources/light/views.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/resources/p-insights-active.svg b/resources/p-insights-active.svg deleted file mode 100644 index 7fbcd97f..00000000 --- a/resources/p-insights-active.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/resources/p-insights-connected.svg b/resources/p-insights-connected.svg deleted file mode 100644 index 9e90659d..00000000 --- a/resources/p-insights-connected.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/resources/p-insights.svg b/resources/p-insights.svg deleted file mode 100644 index efcbf65e..00000000 --- a/resources/p-insights.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/src/classes/insightsConnection.ts b/src/classes/insightsConnection.ts index bf60ef30..c927d9e0 100644 --- a/src/classes/insightsConnection.ts +++ b/src/classes/insightsConnection.ts @@ -16,7 +16,10 @@ import axios, { AxiosRequestConfig } from "axios"; import { ProgressLocation, window } from "vscode"; import * as url from "url"; import { MetaInfoType, MetaObject, MetaObjectPayload } from "../models/meta"; -import { getCurrentToken } from "../services/kdbInsights/codeFlowLogin"; +import { + getCurrentToken, + getHttpsAgent, +} from "../services/kdbInsights/codeFlowLogin"; import { InsightsNode } from "../services/kdbTreeProvider"; import { GetDataObjectPayload } from "../models/data"; import { isCompressed, uncompress } from "../ipc/c"; @@ -54,6 +57,7 @@ export class InsightsConnection { this.node.details.server, this.node.details.alias, this.node.details.realm || "insights", + !!this.node.details.insecure, ).then(async (token) => { this.connected = token ? true : false; if (token) { @@ -74,6 +78,27 @@ export class InsightsConnection { //will be added the feature to retrieve server objects from insights } + private async getOptions() { + const token = await getCurrentToken( + this.node.details.server, + this.node.details.alias, + this.node.details.realm || "insights", + !!this.node.details.insecure, + ); + + if (token === undefined) { + tokenUndefinedError(this.connLabel); + return undefined; + } + + const options: AxiosRequestConfig = { + headers: { Authorization: `Bearer ${token.accessToken}` }, + httpsAgent: getHttpsAgent(this.node.details.insecure), + }; + + return options; + } + public async getMeta(): Promise { if (this.connected) { const metaUrl = new url.URL( @@ -81,21 +106,12 @@ export class InsightsConnection { this.node.details.server, ); - const token = await getCurrentToken( - this.node.details.server, - this.node.details.alias, - this.node.details.realm || "insights", - ); + const options = await this.getOptions(); - if (token === undefined) { - tokenUndefinedError(this.connLabel); + if (options === undefined) { return undefined; } - const options = { - headers: { Authorization: `Bearer ${token.accessToken}` }, - }; - const metaResponse = await axios.post(metaUrl.toString(), {}, options); const meta: MetaObject = metaResponse.data; this.meta = meta; @@ -110,21 +126,13 @@ export class InsightsConnection { ext.insightsAuthUrls.configURL, this.node.details.server, ); - const token = await getCurrentToken( - this.node.details.server, - this.node.details.alias, - this.node.details.realm || "insights", - ); - if (token === undefined) { - tokenUndefinedError(this.connLabel); + const options = await this.getOptions(); + + if (options === undefined) { return undefined; } - const options = { - headers: { Authorization: `Bearer ${token.accessToken}` }, - }; - const configResponse = await axios.get(configUrl.toString(), options); this.config = configResponse.data; this.getInsightsVersion(); @@ -209,6 +217,7 @@ export class InsightsConnection { this.node.details.server, this.node.details.alias, this.node.details.realm || "insights", + !!this.node.details.insecure, ); if (token === undefined) { tokenUndefinedError(this.connLabel); @@ -226,6 +235,7 @@ export class InsightsConnection { data: body, headers: headers, responseType: "arraybuffer", + httpsAgent: getHttpsAgent(this.node.details.insecure), }; const results = await window.withProgress( { @@ -315,6 +325,7 @@ export class InsightsConnection { this.node.details.server, this.node.details.alias, this.node.details.realm || "insights", + !!this.node.details.insecure, ); if (token === undefined) { @@ -326,12 +337,13 @@ export class InsightsConnection { if (username === undefined || username.preferred_username === "") { invalidUsernameJWT(this.connLabel); } - const headers = { + const headers: AxiosRequestConfig = { headers: { Authorization: `Bearer ${token.accessToken}`, username: username.preferred_username!, json: true, }, + httpsAgent: getHttpsAgent(this.node.details.insecure), }; const body = { output: variableName, @@ -399,6 +411,7 @@ export class InsightsConnection { this.node.details.server, this.node.details.alias, this.node.details.realm || "insights", + !!this.node.details.insecure, ); if (token === undefined) { tokenUndefinedError(this.connLabel); @@ -434,7 +447,10 @@ export class InsightsConnection { progress.report({ message: "Query is executing..." }); const spRes = await axios - .post(scratchpadURL.toString(), body, { headers }) + .post(scratchpadURL.toString(), body, { + headers, + httpsAgent: getHttpsAgent(this.node.details.insecure), + }) .then((response: any) => { kdbOutputLog(`[SCRATCHPAD] Status: ${response.status}`, "INFO"); if (isTableView && !response.data.error) { @@ -470,6 +486,7 @@ export class InsightsConnection { this.node.details.server, this.node.details.alias, this.node.details.realm || "insights", + !!this.node.details.insecure, ); if (token === undefined) { @@ -482,12 +499,13 @@ export class InsightsConnection { invalidUsernameJWT(this.connLabel); return false; } - const headers = { + const headers: AxiosRequestConfig = { headers: { Authorization: `Bearer ${token.accessToken}`, username: username.preferred_username!, json: true, }, + httpsAgent: getHttpsAgent(this.node.details.insecure), }; return await window.withProgress( { diff --git a/src/commands/serverCommand.ts b/src/commands/serverCommand.ts index 19ead07e..0837fcca 100644 --- a/src/commands/serverCommand.ts +++ b/src/commands/serverCommand.ts @@ -46,6 +46,7 @@ import { getServerName, getServers, kdbOutputLog, + offerReconnectionAfterEdit, updateInsights, updateServers, } from "../utils/core"; @@ -67,12 +68,27 @@ import { Telemetry } from "../utils/telemetryClient"; import { ConnectionManagementService } from "../services/connectionManagerService"; import { InsightsConnection } from "../classes/insightsConnection"; import { MetaContentProvider } from "../services/metaContentProvider"; +import { handleLabelsConnMap, removeConnFromLabels } from "../utils/connLabel"; export async function addNewConnection(): Promise { + NewConnectionPannel.close(); NewConnectionPannel.render(ext.context.extensionUri); } -export async function addInsightsConnection(insightsData: InsightDetails) { +export async function editConnection(viewItem: KdbNode | InsightsNode) { + NewConnectionPannel.close(); + NewConnectionPannel.render(ext.context.extensionUri, viewItem); +} + +export function isConnected(connLabel: string): boolean { + const connMngService = new ConnectionManagementService(); + return connMngService.isConnected(connLabel); +} + +export async function addInsightsConnection( + insightsData: InsightDetails, + labels?: string[], +) { const aliasValidation = validateServerAlias(insightsData.alias, false); if (aliasValidation) { window.showErrorMessage(aliasValidation); @@ -94,37 +110,145 @@ export async function addInsightsConnection(insightsData: InsightDetails) { return; } else { const key = insightsData.alias; + let server = insightsData.server || ""; + if (!/^https?:\/\//i.exec(server)) { + server = "https://" + server; + } if (insights === undefined) { insights = { key: { auth: true, alias: insightsData.alias, - server: insightsData.server!, + server, realm: insightsData.realm, + insecure: insightsData.insecure, }, }; } else { insights[key] = { auth: true, alias: insightsData.alias, - server: insightsData.server!, + server, realm: insightsData.realm, + insecure: insightsData.insecure, }; } await updateInsights(insights); const newInsights = getInsights(); if (newInsights != undefined) { + ext.latestLblsChanged.length = 0; + if (labels && labels.length > 0) { + ext.latestLblsChanged.push(...labels); + await handleLabelsConnMap(labels, insightsData.alias); + } ext.serverProvider.refreshInsights(newInsights); Telemetry.sendEvent("Connection.Created.Insights"); } window.showInformationMessage( `Added Insights connection: ${insightsData.alias}`, ); + NewConnectionPannel.close(); } } +/* istanbul ignore next */ +export async function editInsightsConnection( + insightsData: InsightDetails, + oldAlias: string, + labels?: string[], +) { + const aliasValidation = + oldAlias === insightsData.alias + ? undefined + : validateServerAlias(insightsData.alias, false); + if (aliasValidation) { + window.showErrorMessage(aliasValidation); + return; + } + const isConnectedConn = isConnected(oldAlias); + await disconnect(oldAlias); + if (insightsData.alias === undefined || insightsData.alias === "") { + const host = new url.URL(insightsData.server); + insightsData.alias = host.host; + } + const insights: Insights | undefined = getInsights(); + if (insights) { + const oldInsights = insights[getKeyForServerName(oldAlias)]; + const newAliasExists = + oldAlias !== insightsData.alias + ? insights[getKeyForServerName(insightsData.alias)] + : undefined; + if (newAliasExists) { + await window.showErrorMessage( + `Insights instance named ${insightsData.alias} already exists.`, + ); + return; + } else { + if (!oldInsights) { + await window.showErrorMessage( + `Insights instance named ${oldAlias} does not exist.`, + ); + return; + } else { + const oldKey = getKeyForServerName(oldAlias); + const newKey = insightsData.alias; + removeConnFromLabels(oldAlias); + if (insights[oldKey] && oldAlias !== insightsData.alias) { + const uInsights = Object.keys(insights).filter((insight) => { + return insight !== oldKey; + }); + const updatedInsights: Insights = {}; + uInsights.forEach((insight) => { + updatedInsights[insight] = insights[insight]; + }); + + updatedInsights[newKey] = { + auth: true, + alias: insightsData.alias, + server: insightsData.server, + realm: insightsData.realm, + insecure: insightsData.insecure, + }; + + await updateInsights(updatedInsights); + } else { + insights[oldKey] = { + auth: true, + alias: insightsData.alias, + server: insightsData.server, + realm: insightsData.realm, + insecure: insightsData.insecure, + }; + await updateInsights(insights); + } + + const newInsights = getInsights(); + if (newInsights != undefined) { + ext.latestLblsChanged.length = 0; + if (labels && labels.length > 0) { + ext.latestLblsChanged.push(...labels); + await handleLabelsConnMap(labels, insightsData.alias); + } else { + removeConnFromLabels(insightsData.alias); + } + ext.serverProvider.refreshInsights(newInsights); + Telemetry.sendEvent("Connection.Edited.Insights"); + if (isConnectedConn) { + offerReconnectionAfterEdit(insightsData.alias); + } + } + window.showInformationMessage( + `Edited Insights connection: ${insightsData.alias}`, + ); + + NewConnectionPannel.close(); + } + } + } +} + // Not possible to test secrets /* istanbul ignore next */ export async function addAuthConnection( @@ -161,6 +285,16 @@ export async function addAuthConnection( } } +// Not possible to test secrets +/* istanbul ignore next */ +function removeAuthConnection(serverKey: string) { + if (ext.secretSettings.storeAuthData.hasOwnProperty(serverKey)) { + delete (ext.secretSettings.storeAuthData as { [key: string]: any })[ + serverKey + ]; + } +} + export async function enableTLS(serverKey: string): Promise { const servers: Server | undefined = getServers(); @@ -197,6 +331,7 @@ export async function enableTLS(serverKey: string): Promise { export async function addKdbConnection( kdbData: ServerDetails, isLocal?: boolean, + labels?: string[], ): Promise { const aliasValidation = validateServerAlias(kdbData.serverAlias, isLocal!); const hostnameValidation = validateServerName(kdbData.serverName); @@ -231,7 +366,7 @@ export async function addKdbConnection( serverName: kdbData.serverName, serverPort: kdbData.serverPort, serverAlias: kdbData.serverAlias, - managed: kdbData.serverAlias === "local" ? true : false, + managed: kdbData.serverAlias === "local", tls: kdbData.tls, }, }; @@ -244,7 +379,7 @@ export async function addKdbConnection( serverName: kdbData.serverName, serverPort: kdbData.serverPort, serverAlias: kdbData.serverAlias, - managed: kdbData.serverAlias === "local" ? true : false, + managed: kdbData.serverAlias === "local", tls: kdbData.tls, }; if (servers[key].managed) { @@ -255,6 +390,11 @@ export async function addKdbConnection( await updateServers(servers); const newServers = getServers(); if (newServers != undefined) { + ext.latestLblsChanged.length = 0; + if (labels && labels.length > 0) { + ext.latestLblsChanged.push(...labels); + await handleLabelsConnMap(labels, kdbData.serverAlias); + } Telemetry.sendEvent("Connection.Created.QProcess"); ext.serverProvider.refresh(newServers); } @@ -264,12 +404,142 @@ export async function addKdbConnection( window.showInformationMessage( `Added kdb connection: ${kdbData.serverAlias}`, ); + NewConnectionPannel.close(); } } +/* istanbul ignore next */ +export async function editKdbConnection( + kdbData: ServerDetails, + oldAlias: string, + isLocal?: boolean, + editAuth?: boolean, + labels?: string[], +) { + const aliasValidation = + oldAlias === kdbData.serverAlias + ? undefined + : validateServerAlias(kdbData.serverAlias, isLocal!); + const hostnameValidation = validateServerName(kdbData.serverName); + const portValidation = validateServerPort(kdbData.serverPort); + if (aliasValidation) { + window.showErrorMessage(aliasValidation); + return; + } + if (hostnameValidation) { + window.showErrorMessage(hostnameValidation); + return; + } + if (portValidation) { + window.showErrorMessage(portValidation); + return; + } + const isConnectedConn = isConnected(oldAlias); + await disconnect(oldAlias); + const servers: Server | undefined = getServers(); + + if (servers) { + const oldServer = servers[getKeyForServerName(oldAlias)]; + const newAliasExists = + oldAlias !== kdbData.serverAlias + ? servers[getKeyForServerName(kdbData.serverAlias)] + : undefined; + if (newAliasExists) { + await window.showErrorMessage( + `KDB instance named ${kdbData.serverAlias} already exists.`, + ); + return; + } else { + if (!oldServer) { + await window.showErrorMessage( + `KDB instance named ${oldAlias} does not exist.`, + ); + return; + } else { + const oldKey = getKeyForServerName(oldAlias); + removeConnFromLabels(oldKey); + const newKey = kdbData.serverAlias; + const removedAuth = + editAuth && (kdbData.username === "" || kdbData.password === ""); + if (servers[oldKey] && oldAlias !== kdbData.serverAlias) { + const uServers = Object.keys(servers).filter((server) => { + return server !== oldKey; + }); + const updatedServers: Server = {}; + uServers.forEach((server) => { + updatedServers[server] = servers[server]; + }); + + updatedServers[newKey] = { + auth: removedAuth ? false : kdbData.auth, + serverName: kdbData.serverName, + serverPort: kdbData.serverPort, + serverAlias: kdbData.serverAlias, + managed: kdbData.serverAlias === "local", + tls: kdbData.tls, + }; + + await updateServers(updatedServers); + } else { + servers[oldKey] = { + auth: removedAuth ? false : kdbData.auth, + serverName: kdbData.serverName, + serverPort: kdbData.serverPort, + serverAlias: kdbData.serverAlias, + managed: kdbData.serverAlias === "local", + tls: kdbData.tls, + }; + + await updateServers(servers); + } + const newServers = getServers(); + if (newServers != undefined) { + ext.latestLblsChanged.length = 0; + if (labels && labels.length > 0) { + ext.latestLblsChanged.push(...labels); + await handleLabelsConnMap(labels, kdbData.serverAlias); + } else { + removeConnFromLabels(kdbData.serverAlias); + } + ext.serverProvider.refresh(newServers); + Telemetry.sendEvent("Connection.Edited.KDB"); + const connLabelToReconn = `${kdbData.serverName}:${kdbData.serverPort} [${kdbData.serverAlias}]`; + if (isConnectedConn) { + offerReconnectionAfterEdit(connLabelToReconn); + } + } + window.showInformationMessage( + `Edited KDB connection: ${kdbData.serverAlias}`, + ); + if (oldKey !== newKey) { + removeConnFromLabels(oldKey); + removeAuthConnection(oldKey); + if (kdbData.auth) { + addAuthConnection(newKey, kdbData.username!, kdbData.password!); + } + } else { + if (editAuth && !removedAuth) { + addAuthConnection(newKey, kdbData.username!, kdbData.password!); + } + if (editAuth && removedAuth) { + removeAuthConnection(newKey); + } + } + + NewConnectionPannel.close(); + } + } + } +} + export async function removeConnection(viewItem: KdbNode | InsightsNode) { const connMngService = new ConnectionManagementService(); + removeConnFromLabels( + viewItem instanceof KdbNode + ? viewItem.details.serverAlias + : viewItem.details.alias, + ); await connMngService.removeConnection(viewItem); } 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 f375b6b2..0a1688a2 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -47,6 +47,9 @@ import { addNewConnection, connect, disconnect, + editConnection, + editInsightsConnection, + editKdbConnection, enableTLS, openMeta, refreshGetMeta, @@ -74,6 +77,7 @@ import { KdbResultsViewProvider } from "./services/resultsPanelProvider"; import { checkLocalInstall, checkOpenSslInstalled, + fixUnnamedAlias, getInsights, getServers, initializeLocalServers, @@ -101,6 +105,15 @@ import { connectBuildTools, lintCommand } from "./commands/buildToolsCommand"; import { CompletionProvider } from "./services/completionProvider"; import { QuickFixProvider } from "./services/quickFixProvider"; import { connectClientCommands } from "./commands/clientCommands"; +import { + createNewLabel, + deleteLabel, + getWorkspaceLabels, + getWorkspaceLabelsConnMap, + renameLabel, + setLabelColor, +} from "./utils/connLabel"; +import { activateTextDocument } from "./utils/workspace"; let client: LanguageClient; @@ -109,6 +122,10 @@ export async function activate(context: ExtensionContext) { ext.outputChannel = window.createOutputChannel("kdb"); ext.openSslVersion = await checkOpenSslInstalled(); ext.isBundleQCreated = false; + + getWorkspaceLabelsConnMap(); + getWorkspaceLabels(); + // clear necessary contexts commands.executeCommand("setContext", "kdb.connected.active", false); commands.executeCommand("setContext", "kdb.insightsConnected", false); @@ -131,6 +148,8 @@ export async function activate(context: ExtensionContext) { "datasource", ); + fixUnnamedAlias(); + commands.executeCommand("setContext", "kdb.QHOME", env.QHOME); window.registerTreeDataProvider("kdb-servers", ext.serverProvider); @@ -255,22 +274,61 @@ export async function activate(context: ExtensionContext) { commands.registerCommand("kdb.addConnection", async () => { await addNewConnection(); }), + commands.registerCommand( + "kdb.editConnection", + async (viewItem: KdbNode | InsightsNode) => { + await editConnection(viewItem); + }, + ), commands.registerCommand( "kdb.newConnection.createNewInsightConnection", - async (insightsData: InsightDetails) => { - await addInsightsConnection(insightsData); + async (insightsData: InsightDetails, labels: string[]) => { + await addInsightsConnection(insightsData, labels); }, ), commands.registerCommand( "kdb.newConnection.createNewConnection", - async (kdbData: ServerDetails) => { - await addKdbConnection(kdbData, false); + async (kdbData: ServerDetails, labels: string[]) => { + await addKdbConnection(kdbData, false, labels); }, ), commands.registerCommand( "kdb.newConnection.createNewBundledConnection", - async (kdbData: ServerDetails) => { - await addKdbConnection(kdbData, true); + async (kdbData: ServerDetails, labels: string[]) => { + await addKdbConnection(kdbData, true, labels); + }, + ), + commands.registerCommand( + "kdb.newConnection.editInsightsConnection", + async ( + insightsData: InsightDetails, + oldAlias: string, + labels: string[], + ) => { + await editInsightsConnection(insightsData, oldAlias, labels); + }, + ), + commands.registerCommand( + "kdb.newConnection.editMyQConnection", + async ( + kdbData: ServerDetails, + oldAlias: string, + editAuth: boolean, + labels: string[], + ) => { + await editKdbConnection(kdbData, oldAlias, false, editAuth, labels); + }, + ), + commands.registerCommand( + "kdb.newConnection.editBundledConnection", + async (kdbData: ServerDetails, oldAlias: string, labels: string[]) => { + await editKdbConnection(kdbData, oldAlias, true, false, labels); + }, + ), + commands.registerCommand( + "kdb.labels.create", + async (name: string, colorName: string) => { + await createNewLabel(name, colorName); }, ), commands.registerCommand( @@ -336,7 +394,10 @@ export async function activate(context: ExtensionContext) { commands.registerCommand("kdb.execute.selectedQuery", async () => { await runActiveEditor(ExecutionTypes.QuerySelection); }), - commands.registerCommand("kdb.execute.fileQuery", async () => { + commands.registerCommand("kdb.execute.fileQuery", async (item) => { + if (item instanceof Uri) { + await activateTextDocument(item); + } await runActiveEditor(ExecutionTypes.QueryFile); }), commands.registerCommand("kdb.execute.pythonScratchpadQuery", async () => { @@ -348,7 +409,10 @@ export async function activate(context: ExtensionContext) { // }), commands.registerCommand( "kdb.execute.pythonFileScratchpadQuery", - async () => { + async (item) => { + if (item instanceof Uri) { + await activateTextDocument(item); + } await runActiveEditor(ExecutionTypes.PythonQueryFile); }, ), @@ -411,16 +475,32 @@ export async function activate(context: ExtensionContext) { }), commands.registerCommand("kdb.renameFile", async (item: FileTreeItem) => { if (item && item.resourceUri) { - const document = await workspace.openTextDocument(item.resourceUri); - await window.showTextDocument(document); + if (item.resourceUri.path.endsWith(".kdb.json")) { + await commands.executeCommand( + "vscode.openWith", + item.resourceUri, + DataSourceEditorProvider.viewType, + ); + } else { + const document = await workspace.openTextDocument(item.resourceUri); + await window.showTextDocument(document); + } await commands.executeCommand("revealInExplorer"); await commands.executeCommand("renameFile"); } }), commands.registerCommand("kdb.deleteFile", async (item: FileTreeItem) => { if (item && item.resourceUri) { - const document = await workspace.openTextDocument(item.resourceUri); - await window.showTextDocument(document); + if (item.resourceUri.path.endsWith(".kdb.json")) { + await commands.executeCommand( + "vscode.openWith", + item.resourceUri, + DataSourceEditorProvider.viewType, + ); + } else { + const document = await workspace.openTextDocument(item.resourceUri); + await window.showTextDocument(document); + } await commands.executeCommand("revealInExplorer"); await commands.executeCommand("deleteFile"); } @@ -448,6 +528,66 @@ export async function activate(context: ExtensionContext) { ext.dataSourceTreeProvider.reload(); ext.scratchpadTreeProvider.reload(); } + if (event.affectsConfiguration("kdb.connectionLabelsMap")) { + ext.serverProvider.reload(); + } + if (event.affectsConfiguration("kdb.connectionLabels")) { + 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); + } }), ); diff --git a/src/extensionVariables.ts b/src/extensionVariables.ts index 735c5ab2..bac7d95e 100644 --- a/src/extensionVariables.ts +++ b/src/extensionVariables.ts @@ -37,6 +37,7 @@ import { ScratchpadFile } from "./models/scratchpad"; import { LocalConnection } from "./classes/localConnection"; import { InsightsConnection } from "./classes/insightsConnection"; import { DataSourceFiles } from "./models/dataSource"; +import { ConnectionLabel, LabelColors, Labels } from "./models/labels"; // eslint-disable-next-line @typescript-eslint/no-namespace export namespace ext { @@ -84,6 +85,9 @@ export namespace ext { export const kdbNodesWithoutAuth: string[] = []; export const kdbNodesWithoutTls: string[] = []; export const kdbConnectionAliasList: string[] = []; + export const connLabelList: Labels[] = []; + export const labelConnMapList: ConnectionLabel[] = []; + export const latestLblsChanged: string[] = []; export const maxRetryCount = 5; export let secretSettings: AuthSettings; @@ -306,4 +310,35 @@ export namespace ext { export const diagnosticCollection = languages.createDiagnosticCollection("kdb"); + + export const labelColors: LabelColors[] = [ + { + name: "White", + colorHex: "#FFFFFF", + }, + { + name: "Red", + colorHex: "#CD3131", + }, + { + name: "Green", + colorHex: "#10BC7A", + }, + { + name: "Yellow", + colorHex: "#E5E50E", + }, + { + name: "Blue", + colorHex: "#2371C8", + }, + { + name: "Magenta", + colorHex: "#BC3FBC", + }, + { + name: "Cyan", + colorHex: "#15A7CD", + }, + ]; } diff --git a/src/models/insights.ts b/src/models/insights.ts index bccf6667..b9446e4d 100644 --- a/src/models/insights.ts +++ b/src/models/insights.ts @@ -16,6 +16,7 @@ export interface InsightDetails { server: string; auth: boolean; realm?: string; + insecure?: boolean; } export interface Insights { diff --git a/src/models/labels.ts b/src/models/labels.ts new file mode 100644 index 00000000..dc9797a6 --- /dev/null +++ b/src/models/labels.ts @@ -0,0 +1,27 @@ +/* + * Copyright (c) 1998-2023 Kx Systems Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +export type LabelColors = { + name: string; + colorHex: string; +}; + +export type Labels = { + name: string; + color: LabelColors; +}; + +export type ConnectionLabel = { + labelName: string; + connections: string[]; +}; diff --git a/src/models/messages.ts b/src/models/messages.ts index 70190d1f..8b51000a 100644 --- a/src/models/messages.ts +++ b/src/models/messages.ts @@ -42,3 +42,20 @@ export interface DataSourceMessage2 { insightsMeta: MetaObjectPayload; dataSourceFile: DataSourceFiles; } + +export const enum ConnectionType { + BundledQ, + Kdb, + Insights, +} + +export interface EditConnectionMessage { + connType: ConnectionType; + serverName: string; + serverAddress: string; + port?: string; + realm?: string; + auth?: boolean; + tls?: boolean; + insecure?: boolean; +} diff --git a/src/models/server.ts b/src/models/server.ts index 4059514e..af400b55 100644 --- a/src/models/server.ts +++ b/src/models/server.ts @@ -21,7 +21,7 @@ export interface ServerDetails { serverName: string; serverPort: string; auth: boolean; - serverAlias: string | undefined; + serverAlias: string; managed: boolean; tls: boolean; username?: string; diff --git a/src/panels/newConnection.ts b/src/panels/newConnection.ts index 550a6ded..b20fb989 100644 --- a/src/panels/newConnection.ts +++ b/src/panels/newConnection.ts @@ -15,20 +15,80 @@ import * as vscode from "vscode"; import { getUri } from "../utils/getUri"; import { getNonce } from "../utils/getNonce"; import { ext } from "../extensionVariables"; +import { InsightsNode, KdbNode } from "../services/kdbTreeProvider"; +import { ConnectionType, EditConnectionMessage } from "../models/messages"; +import { retrieveConnLabelsNames } from "../utils/connLabel"; export class NewConnectionPannel { public static currentPanel: NewConnectionPannel | undefined; - private uri; + private readonly _extensionUri: vscode.Uri; public readonly _panel: vscode.WebviewPanel; private _disposables: vscode.Disposable[] = []; + public static render(extensionUri: vscode.Uri, conn?: any) { + if (NewConnectionPannel.currentPanel) { + NewConnectionPannel.currentPanel._panel.dispose(); + return; + } + + const panel = vscode.window.createWebviewPanel( + "kdbNewConnection", + conn ? "Edit Connection" : "New Connection", + vscode.ViewColumn.One, + { + enableScripts: true, + retainContextWhenHidden: true, + localResourceRoots: [vscode.Uri.joinPath(extensionUri, "out")], + }, + ); + + NewConnectionPannel.currentPanel = new NewConnectionPannel( + panel, + extensionUri, + ); + + panel.webview.postMessage({ + command: "refreshLabels", + data: ext.connLabelList, + colors: ext.labelColors, + }); + + if (conn) { + const labels = retrieveConnLabelsNames(conn); + const connType = this.getConnectionType(conn); + const editConnData = this.createEditConnectionMessage(conn, connType); + panel.webview.postMessage({ + command: "editConnection", + data: editConnData, + labels, + }); + } + } + + public static close() { + if (NewConnectionPannel.currentPanel) { + NewConnectionPannel.currentPanel._panel.dispose(); + return; + } + } + + public static refreshLabels() { + if (NewConnectionPannel.currentPanel) { + NewConnectionPannel.currentPanel._panel.webview.postMessage({ + command: "refreshLabels", + data: ext.connLabelList, + colors: ext.labelColors, + }); + } + } + private constructor(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) { - this.uri = extensionUri; + this._extensionUri = extensionUri; this._panel = panel; this._panel.onDidDispose(() => this.dispose(), null, this._disposables); this._panel.webview.html = this._getWebviewContent( this._panel.webview, - extensionUri, + this._extensionUri, ); /* istanbul ignore next */ this._panel.webview.onDidReceiveMessage((message) => { @@ -41,6 +101,7 @@ export class NewConnectionPannel { vscode.commands.executeCommand( "kdb.newConnection.createNewBundledConnection", message.data, + message.labels, ); } } @@ -48,47 +109,58 @@ export class NewConnectionPannel { vscode.commands.executeCommand( "kdb.newConnection.createNewInsightConnection", message.data, + message.labels, ); } if (message.command === "kdb.newConnection.createNewConnection") { vscode.commands.executeCommand( "kdb.newConnection.createNewConnection", message.data, + message.labels, ); } + if (message.command === "kdb.newConnection.editInsightsConnection") { + vscode.commands.executeCommand( + "kdb.newConnection.editInsightsConnection", + message.data, + message.oldAlias, + message.labels, + ); + } + if (message.command === "kdb.newConnection.editMyQConnection") { + vscode.commands.executeCommand( + "kdb.newConnection.editMyQConnection", + message.data, + message.oldAlias, + message.editAuth, + message.labels, + ); + } + if (message.command === "kdb.newConnection.editBundledConnection") { + vscode.commands.executeCommand( + "kdb.newConnection.editBundledConnection", + message.data, + message.oldAlias, + message.labels, + ); + } + if (message.command === "kdb.labels.create") { + vscode.commands.executeCommand( + "kdb.labels.create", + message.data.name, + message.data.colorName, + ); + setTimeout(() => { + this._panel.webview.postMessage({ + command: "refreshLabels", + data: ext.connLabelList, + colors: ext.labelColors, + }); + }, 500); + } }); } - public static render(extensionUri: vscode.Uri) { - if (NewConnectionPannel.currentPanel) { - NewConnectionPannel.currentPanel._panel.dispose(); - return; - } - - const panel = vscode.window.createWebviewPanel( - "kdbNewConnection", - "New Connection", - vscode.ViewColumn.One, - { - enableScripts: true, - retainContextWhenHidden: true, - localResourceRoots: [vscode.Uri.joinPath(extensionUri, "out")], - }, - ); - - NewConnectionPannel.currentPanel = new NewConnectionPannel( - panel, - extensionUri, - ); - } - - public static close() { - if (NewConnectionPannel.currentPanel) { - NewConnectionPannel.currentPanel._panel.dispose(); - return; - } - } - public dispose() { NewConnectionPannel.currentPanel = undefined; this._panel.dispose(); @@ -114,12 +186,47 @@ export class NewConnectionPannel { New Connection + - `; } + + private static getConnectionType( + conn: KdbNode | InsightsNode, + ): ConnectionType { + if (conn instanceof InsightsNode) { + return ConnectionType.Insights; + } else { + return conn.details.managed + ? ConnectionType.BundledQ + : ConnectionType.Kdb; + } + } + + private static createEditConnectionMessage( + conn: KdbNode | InsightsNode, + connType: ConnectionType, + ): EditConnectionMessage { + return { + connType, + serverName: + conn instanceof InsightsNode + ? conn.details.alias + : conn.details.serverAlias, + serverAddress: + conn instanceof InsightsNode + ? conn.details.server + : conn.details.serverName, + realm: conn instanceof InsightsNode ? conn.details.realm : undefined, + port: conn instanceof KdbNode ? conn.details.serverPort : undefined, + auth: conn.details.auth, + tls: conn instanceof KdbNode ? conn.details.tls : undefined, + insecure: + conn instanceof InsightsNode ? conn.details.insecure : undefined, + }; + } } diff --git a/src/services/connectionManagerService.ts b/src/services/connectionManagerService.ts index d7c166a3..1df1ab20 100644 --- a/src/services/connectionManagerService.ts +++ b/src/services/connectionManagerService.ts @@ -46,8 +46,21 @@ export class ConnectionManagementService { connLabel: string, ): LocalConnection | InsightsConnection | undefined { return ext.connectedConnectionList.find( - (connection: LocalConnection | InsightsConnection) => - connLabel === connection.connLabel, + (connection: LocalConnection | InsightsConnection) => { + if (!connLabel) { + return false; + } + const escapedConnLabel = connLabel.replace( + /[-[\]{}()*+?.,\\^$|#\s]/g, + "\\$&", + ); + const regex = new RegExp( + `\\d+\\.\\d+\\.\\d+\\.\\d+:\\d+ \\[${escapedConnLabel}\\]`, + ); + return ( + connLabel === connection.connLabel || regex.test(connection.connLabel) + ); + }, ); } @@ -56,7 +69,17 @@ export class ConnectionManagementService { } public isConnected(connLabel: string): boolean { - return ext.connectedContextStrings.includes(connLabel); + const escapedConnLabel = connLabel.replace( + /[-[\]{}()*+?.,\\^$|#\s]/g, + "\\$&", + ); + const regex = new RegExp( + `\\d+\\.\\d+\\.\\d+\\.\\d+:\\d+ \\[${escapedConnLabel}\\]`, + ); + return ( + ext.connectedContextStrings.includes(connLabel) || + ext.connectedContextStrings.some((context) => regex.test(context)) + ); } public retrieveLocalConnectionString(connection: KdbNode): string { @@ -153,7 +176,7 @@ export class ConnectionManagementService { public disconnect(connLabel: string): void { const connection = this.retrieveConnectedConnection(connLabel); - const connectionNode = this.retrieveConnection(connLabel); + const connectionNode = this.retrieveConnection(connection?.connLabel ?? ""); if (!connection || !connectionNode) { return; } diff --git a/src/services/kdbInsights/codeFlowLogin.ts b/src/services/kdbInsights/codeFlowLogin.ts index 33c344b1..faef4ca7 100644 --- a/src/services/kdbInsights/codeFlowLogin.ts +++ b/src/services/kdbInsights/codeFlowLogin.ts @@ -11,7 +11,7 @@ * specific language governing permissions and limitations under the License. */ -import axios from "axios"; +import axios, { AxiosRequestConfig } from "axios"; import * as crypto from "crypto"; import * as fs from "fs-extra"; import * as http from "http"; @@ -21,6 +21,7 @@ import * as url from "url"; import { ext } from "../../extensionVariables"; import { Uri, env } from "vscode"; import { pickPort } from "pick-port"; +import https from "https"; interface IDeferred { resolve: (result: T | Promise) => void; @@ -55,6 +56,10 @@ export interface IToken { refreshToken: string; } +export function getHttpsAgent(insecure: boolean | undefined) { + return new https.Agent({ rejectUnauthorized: !insecure }); +} + const defaultTimeout = 3 * 60 * 1000; // 3 min const closeTimeout = 10 * 1000; // 10 sec @@ -62,7 +67,11 @@ const commonRequestParams = { client_id: "insights-app", }; -export async function signIn(insightsUrl: string, realm: string) { +export async function signIn( + insightsUrl: string, + realm: string, + insecure: boolean, +) { const { server, codePromise } = createServer(); try { @@ -82,7 +91,7 @@ export async function signIn(insightsUrl: string, realm: string) { await env.openExternal(Uri.parse(authorizationUrl.toString())); const code = await codePromise; - return await getToken(insightsUrl, realm, code); + return await getToken(insightsUrl, realm, insecure, code); } finally { setImmediate(() => server.close()); } @@ -91,6 +100,7 @@ export async function signIn(insightsUrl: string, realm: string) { export async function signOut( insightsUrl: string, realm: string, + insecure: boolean, token: string, ): Promise { const queryParams = queryString({ @@ -100,8 +110,9 @@ export async function signOut( const body = { body: queryParams, }; - const headers = { + const headers: AxiosRequestConfig = { headers: { "Content-Type": "application/x-www-form-urlencoded" }, + httpsAgent: getHttpsAgent(insecure), }; const requestUrl = getRevokeUrl(insightsUrl, realm); @@ -113,9 +124,10 @@ export async function signOut( export async function refreshToken( insightsUrl: string, realm: string, + insecure: boolean, token: string, ): Promise { - return await tokenRequest(insightsUrl, realm, { + return await tokenRequest(insightsUrl, realm, insecure, { grant_type: ext.insightsGrantType.refreshToken, refresh_token: token, }); @@ -126,6 +138,7 @@ export async function getCurrentToken( serverName: string, serverAlias: string, realm: string, + insecure: boolean, ): Promise { if (serverName === "" || serverAlias === "") { return undefined; @@ -137,9 +150,14 @@ export async function getCurrentToken( if (existingToken !== undefined) { const storedToken: IToken = JSON.parse(existingToken); if (new Date(storedToken.accessTokenExpirationDate) < new Date()) { - token = await refreshToken(serverName, realm, storedToken.refreshToken); + token = await refreshToken( + serverName, + realm, + insecure, + storedToken.refreshToken, + ); if (token === undefined) { - token = await signIn(serverName, realm); + token = await signIn(serverName, realm, insecure); ext.context.secrets.store(serverAlias, JSON.stringify(token)); } ext.context.secrets.store(serverAlias, JSON.stringify(token)); @@ -148,7 +166,7 @@ export async function getCurrentToken( return storedToken; } } else { - token = await signIn(serverName, realm); + token = await signIn(serverName, realm, insecure); ext.context.secrets.store(serverAlias, JSON.stringify(token)); } return token; @@ -158,9 +176,10 @@ export async function getCurrentToken( async function getToken( insightsUrl: string, realm: string, + insecure: boolean, code: string, ): Promise { - return await tokenRequest(insightsUrl, realm, { + return await tokenRequest(insightsUrl, realm, insecure, { code, grant_type: ext.insightsGrantType.authorizationCode, }); @@ -169,15 +188,17 @@ async function getToken( async function tokenRequest( insightsUrl: string, realm: string, + insecure: boolean, params: any, ): Promise { const queryParams = queryString(params); - const headers = { + const headers: AxiosRequestConfig = { headers: { "Content-Type": "application/x-www-form-urlencoded", }, timeout: closeTimeout, signal: AbortSignal.timeout(closeTimeout), + httpsAgent: getHttpsAgent(insecure), }; const requestUrl = getTokenUrl(insightsUrl, realm); diff --git a/src/services/kdbTreeProvider.ts b/src/services/kdbTreeProvider.ts index cb74b1c4..55b20d2a 100644 --- a/src/services/kdbTreeProvider.ts +++ b/src/services/kdbTreeProvider.ts @@ -41,6 +41,14 @@ import { } from "../utils/core"; import { ConnectionManagementService } from "./connectionManagerService"; import { InsightsConnection } from "../classes/insightsConnection"; +import { + getWorkspaceLabels, + getWorkspaceLabelsConnMap, + isLabelContentChanged, + isLabelEmpty, + retrieveConnLabelsNames, +} from "../utils/connLabel"; +import { Labels } from "../models/labels"; export class KdbTreeProvider implements TreeDataProvider { private _onDidChangeTreeData: EventEmitter< @@ -92,15 +100,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 @@ -527,22 +558,7 @@ export class KdbNode extends TreeItem { : ""; } - iconPath = { - light: path.join( - __filename, - "..", - "..", - "resources", - "p-q-connection" + getServerIconState(this.label) + ".svg", - ), - dark: path.join( - __filename, - "..", - "..", - "resources", - "p-q-connection" + getServerIconState(this.label) + ".svg", - ), - }; + iconPath = getNamedIconPath("p-q-connection", this.label); contextValue = this.label; // "root"; } @@ -590,22 +606,7 @@ export class InsightsNode extends TreeItem { : ""; } - iconPath = { - light: path.join( - __filename, - "..", - "..", - "resources", - "p-insights" + getServerIconState(this.label) + ".svg", - ), - dark: path.join( - __filename, - "..", - "..", - "resources", - "p-insights" + getServerIconState(this.label) + ".svg", - ), - }; + iconPath = getNamedIconPath("p-insights", this.label); contextValue = this.label; // "root"; } @@ -632,7 +633,7 @@ export class InsightsMetaNode extends TreeItem { "..", "..", "resources", - "metaIcons", + "light", "metaicon.svg", ), dark: path.join( @@ -640,7 +641,7 @@ export class InsightsMetaNode extends TreeItem { "..", "..", "resources", - "metaIcons", + "dark", "metaicon.svg", ), }; @@ -671,9 +672,16 @@ export class QNamespaceNode extends TreeItem { "..", "resources", "light", - "p-file.svg", + "namespaces.svg", + ), + dark: path.join( + __filename, + "..", + "..", + "resources", + "dark", + "namespaces.svg", ), - dark: path.join(__filename, "..", "..", "resources", "dark", "p-file.svg"), }; contextValue = "ns"; } @@ -702,7 +710,7 @@ export class QCategoryNode extends TreeItem { "..", "resources", "light", - "p-folder.svg", + `${this.label.toLowerCase()}.svg`, ), dark: path.join( __filename, @@ -710,7 +718,7 @@ export class QCategoryNode extends TreeItem { "..", "resources", "dark", - "p-folder.svg", + `${this.label.toLowerCase()}.svg`, ), }; contextValue = this.ns; // "category"; @@ -734,7 +742,7 @@ export class MetaObjectPayloadNode extends TreeItem { "..", "..", "resources", - "metaIcons", + "light", `${this.coreIcon}.svg`, ), dark: path.join( @@ -742,7 +750,7 @@ export class MetaObjectPayloadNode extends TreeItem { "..", "..", "resources", - "metaIcons", + "dark", `${this.coreIcon}.svg`, ), }; @@ -785,3 +793,67 @@ export class QServerNode extends TreeItem { }; contextValue = this.label; } + +export class LabelNode extends TreeItem { + readonly children: TreeItem[] = []; + static id = 0; + + constructor(public readonly source: Labels) { + super(source.name); + this.id = "LabelNode" + LabelNode.id++; + this.collapsibleState = this.getCollapsibleState(source.name); + 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`, + ), + }; + + getCollapsibleState(labelName: string): TreeItemCollapsibleState { + if (isLabelEmpty(labelName)) { + return TreeItemCollapsibleState.None; + } + if (isLabelContentChanged(labelName)) { + return TreeItemCollapsibleState.Expanded; + } + return TreeItemCollapsibleState.Collapsed; + } +} + +function getNamedIconPath(name: string, label: string) { + return { + light: path.join( + __filename, + "..", + "..", + "resources", + "light", + name + getServerIconState(label) + ".svg", + ), + dark: path.join( + __filename, + "..", + "..", + "resources", + "dark", + name + getServerIconState(label) + ".svg", + ), + }; +} diff --git a/src/services/workspaceTreeProvider.ts b/src/services/workspaceTreeProvider.ts index 208e97cf..c89e0359 100644 --- a/src/services/workspaceTreeProvider.ts +++ b/src/services/workspaceTreeProvider.ts @@ -98,12 +98,25 @@ export class FileTreeItem extends TreeItem { state = getWorkspaceIconsState(connection.label); } } - this.iconPath = Path.join( - __filename, - "../".repeat(2), - "resources", - this.baseIcon + state + ".svg", - ); + + this.iconPath = { + light: Path.join( + __filename, + "..", + "..", + "resources", + "light", + this.baseIcon + state + ".svg", + ), + dark: Path.join( + __filename, + "..", + "..", + "resources", + "dark", + this.baseIcon + state + ".svg", + ), + }; } private getFileIconType(fileName: string) { diff --git a/src/utils/connLabel.ts b/src/utils/connLabel.ts new file mode 100644 index 00000000..f2c9f4c9 --- /dev/null +++ b/src/utils/connLabel.ts @@ -0,0 +1,193 @@ +/* + * Copyright (c) 1998-2023 Kx Systems Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +import { ConnectionLabel, Labels } from "../models/labels"; +import { workspace } from "vscode"; +import { ext } from "../extensionVariables"; +import { kdbOutputLog } from "./core"; +import { InsightsNode, KdbNode } from "../services/kdbTreeProvider"; +import { NewConnectionPannel } from "../panels/newConnection"; + +export function getWorkspaceLabels() { + const existingConnLbls = workspace + .getConfiguration() + .get("kdb.connectionLabels"); + ext.connLabelList.length = 0; + if (existingConnLbls && existingConnLbls.length > 0) { + existingConnLbls.forEach((label: Labels) => { + ext.connLabelList.push(label); + }); + } +} + +export function createNewLabel(name: string, colorName: string) { + getWorkspaceLabels(); + const color = ext.labelColors.find( + (color) => color.name.toLowerCase() === colorName.toLowerCase(), + ); + if (name === "") { + kdbOutputLog("Label name can't be empty", "ERROR"); + } + if (color && name !== "") { + const newLbl: Labels = { + name: name, + color: color, + }; + ext.connLabelList.push(newLbl); + workspace + .getConfiguration() + .update("kdb.connectionLabels", ext.connLabelList, true); + } else { + kdbOutputLog("No Color selected for the label", "ERROR"); + } +} + +export function getWorkspaceLabelsConnMap() { + const existingLabelConnMaps = workspace + .getConfiguration() + .get("kdb.labelsConnectionMap"); + ext.labelConnMapList.length = 0; + if (existingLabelConnMaps && existingLabelConnMaps.length > 0) { + existingLabelConnMaps.forEach((labelConnMap: ConnectionLabel) => { + ext.labelConnMapList.push(labelConnMap); + }); + } +} + +export function addConnToLabel(labelName: string, connName: string) { + const label = ext.connLabelList.find( + (lbl) => lbl.name.toLowerCase() === labelName.toLowerCase(), + ); + if (label) { + if (ext.labelConnMapList.length > 0) { + const labelConnMap = ext.labelConnMapList.find( + (lbl) => lbl.labelName === labelName, + ); + if (labelConnMap) { + if (!labelConnMap.connections.includes(connName)) { + labelConnMap.connections.push(connName); + } + } else { + ext.labelConnMapList.push({ + labelName: labelName, + connections: [connName], + }); + } + } else { + ext.labelConnMapList.push({ + labelName: labelName, + connections: [connName], + }); + } + } +} + +export function removeConnFromLabels(connName: string) { + ext.labelConnMapList.forEach((labelConnMap) => { + if (labelConnMap.connections.includes(connName)) { + labelConnMap.connections = labelConnMap.connections.filter( + (conn: string) => conn !== connName, + ); + } + }); +} + +export async function handleLabelsConnMap(labels: string[], connName: string) { + removeConnFromLabels(connName); + labels.forEach((label) => { + addConnToLabel(label, connName); + }); + await workspace + .getConfiguration() + .update("kdb.labelsConnectionMap", ext.labelConnMapList, true); +} + +export function retrieveConnLabelsNames( + conn: KdbNode | InsightsNode, +): string[] { + const connName = + conn instanceof KdbNode ? conn.details.serverAlias : conn.details.alias; + const labels: string[] = []; + ext.labelConnMapList.forEach((labelConnMap) => { + if (labelConnMap.connections.includes(connName)) { + labels.push(labelConnMap.labelName); + } + }); + 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), + ); + NewConnectionPannel.refreshLabels(); +} + +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); + NewConnectionPannel.refreshLabels(); +} + +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); + + NewConnectionPannel.refreshLabels(); +} + +export function isLabelEmpty(name: string) { + const found = ext.labelConnMapList.find((item) => item.labelName === name); + if (found) { + return found.connections.length === 0; + } + return true; +} + +export function isLabelContentChanged(name: string) { + const found = ext.latestLblsChanged.find((item) => item === name); + if (found) { + return true; + } + return false; +} diff --git a/src/utils/core.ts b/src/utils/core.ts index 6a1e4091..66532762 100644 --- a/src/utils/core.ts +++ b/src/utils/core.ts @@ -190,6 +190,46 @@ export function getServers(): Server | undefined { return workspace.getConfiguration().get("kdb.servers"); } +// TODO: Remove this on 1.9.0 release +/* istanbul ignore next */ +export function fixUnnamedAlias(): void { + const servers = getServers(); + const insights = getInsights(); + let counter = 1; + + if (servers) { + const updatedServers: Server = {}; + for (const key in servers) { + if (servers.hasOwnProperty(key)) { + const server = servers[key]; + if (server.serverAlias === "") { + server.serverAlias = `unnamedServer-${counter}`; + counter++; + } + updatedServers[server.serverAlias] = server; + } + } + updateServers(updatedServers); + ext.serverProvider.refresh(servers); + } + + if (insights) { + const updatedInsights: Insights = {}; + for (const key in insights) { + if (insights.hasOwnProperty(key)) { + const insight = insights[key]; + if (insight.alias === "") { + insight.alias = `unnamedServer-${counter}`; + counter++; + } + updatedInsights[insight.alias] = insight; + } + } + updateInsights(updatedInsights); + ext.serverProvider.refreshInsights(insights); + } +} + export function getHideDetailedConsoleQueryOutput(): void { const setting = workspace .getConfiguration() @@ -310,6 +350,21 @@ export function offerConnectAction(connLabel: string): void { }); } +/* istanbul ignore next */ +export function offerReconnectionAfterEdit(connLabel: string): void { + window + .showInformationMessage( + `You are no longer connected to ${connLabel}, would you like to connect?`, + "Connect", + "Cancel", + ) + .then(async (result) => { + if (result === "Connect") { + await commands.executeCommand("kdb.connect.via.dialog", connLabel); + } + }); +} + export function getInsightsAlias(insightsList: InsightDetails[]): void { insightsList.forEach((x) => { ext.kdbConnectionAliasList.push(x.alias); diff --git a/src/utils/workspace.ts b/src/utils/workspace.ts index da344976..8ca042e1 100644 --- a/src/utils/workspace.ts +++ b/src/utils/workspace.ts @@ -11,10 +11,10 @@ * specific language governing permissions and limitations under the License. */ -import { workspace } from "vscode"; +import { Uri, window, workspace } from "vscode"; export function getWorkspaceRoot( - ignoreException: boolean = false + ignoreException: boolean = false, ): string | undefined { const workspaceRoot = workspace.workspaceFolders && workspace.workspaceFolders[0].uri.fsPath; @@ -32,3 +32,17 @@ export function isWorkspaceOpen(): boolean { workspace.workspaceFolders && workspace.workspaceFolders[0].uri.fsPath ); } + +export async function activateTextDocument(item: Uri) { + if (item.fsPath) { + let document = workspace.textDocuments.find( + (doc) => doc.uri.fsPath === item.fsPath, + ); + if (!document) { + document = await workspace.openTextDocument(item); + } + if (document) { + await window.showTextDocument(document); + } + } +} diff --git a/src/validators/kdbValidator.ts b/src/validators/kdbValidator.ts index bdbe6655..623b52de 100644 --- a/src/validators/kdbValidator.ts +++ b/src/validators/kdbValidator.ts @@ -21,7 +21,10 @@ export function validateServerAlias( isLocal: boolean, ): string | undefined { // server alias is not required, but should be validated if entered - if (input !== undefined && input !== "") { + if (input !== undefined) { + if (input === "") { + return "Server Name is required"; + } if (isAliasInUse(input)) { return "Server Name is already in use. Please use a different name."; } diff --git a/src/webview/components/kdbNewConnectionView.ts b/src/webview/components/kdbNewConnectionView.ts index 63455338..cc1d551f 100644 --- a/src/webview/components/kdbNewConnectionView.ts +++ b/src/webview/components/kdbNewConnectionView.ts @@ -12,20 +12,54 @@ */ import { LitElement, html } from "lit"; -import { customElement, state } from "lit/decorators.js"; +import { customElement } from "lit/decorators.js"; import { ServerDetails, ServerType } from "../../models/server"; import { InsightDetails } from "../../models/insights"; import { kdbStyles, newConnectionStyles, vscodeStyles } from "./styles"; +import { EditConnectionMessage } from "../../models/messages"; +import { repeat } from "lit/directives/repeat.js"; +import { LabelColors, Labels } from "../../models/labels"; @customElement("kdb-new-connection-view") export class KdbNewConnectionView extends LitElement { static styles = [vscodeStyles, kdbStyles, newConnectionStyles]; - @state() declare kdbServer: ServerDetails; - @state() declare bundledServer: ServerDetails; - @state() declare insightsServer: InsightDetails; - @state() declare serverType: ServerType; - @state() declare isBundledQ: boolean; + lblColorsList: LabelColors[] = []; + lblNamesList: Labels[] = []; + newLblName = ""; + newLblColorName = ""; + kdbServer: ServerDetails = { + serverName: "", + serverPort: "", + auth: false, + serverAlias: "", + managed: false, + tls: false, + username: "", + password: "", + }; + bundledServer: ServerDetails = { + serverName: "127.0.0.1", + serverPort: "", + auth: false, + serverAlias: "local", + managed: false, + tls: false, + }; + insightsServer: InsightDetails = { + alias: "", + server: "", + auth: true, + realm: "", + insecure: false, + }; + labels: string[] = []; + serverType: ServerType = ServerType.KDB; + isBundledQ: boolean = true; + oldAlias: string = ""; + editAuth: boolean = false; + private isModalOpen = false; + private _connectionData: EditConnectionMessage | undefined = undefined; private readonly vscode = acquireVsCodeApi(); private tabConfig = { 1: { isBundledQ: true, serverType: ServerType.KDB }, @@ -34,34 +68,36 @@ export class KdbNewConnectionView extends LitElement { default: { isBundledQ: true, serverType: ServerType.KDB }, }; - constructor() { - super(); - this.isBundledQ = true; - this.serverType = ServerType.KDB; - this.kdbServer = { - serverName: "", - serverPort: "", - auth: false, - serverAlias: "", - managed: false, - tls: false, - username: "", - password: "", - }; - this.insightsServer = { - alias: "", - server: "", - auth: true, - realm: "", - }; - this.bundledServer = { - serverName: "127.0.0.1", - serverPort: "", - auth: false, - serverAlias: "local", - managed: false, - tls: false, - }; + get connectionData(): EditConnectionMessage | undefined { + return this._connectionData; + } + + set connectionData(value: EditConnectionMessage | undefined) { + const oldValue = this._connectionData; + this._connectionData = value; + this.requestUpdate("connectionData", oldValue); + } + + openModal() { + this.isModalOpen = true; + this.requestUpdate(); + } + + closeModal() { + this.newLblColorName = ""; + this.newLblName = ""; + this.isModalOpen = false; + this.requestUpdate(); + } + + connectedCallback() { + super.connectedCallback(); + window.addEventListener("message", this.handleMessage.bind(this)); + } + + disconnectedCallback() { + window.removeEventListener("message", this.handleMessage.bind(this)); + super.disconnectedCallback(); } get selectConnection(): string { @@ -77,12 +113,31 @@ export class KdbNewConnectionView extends LitElement { } } + handleMessage(event: { data: any }) { + const message = event.data; + if (message.command === "editConnection") { + this.connectionData = message.data; + this.labels = message.labels; + this.requestUpdate(); + } + if (message.command === "refreshLabels") { + this.lblNamesList = message.data; + this.lblColorsList = message.colors; + this.requestUpdate(); + } + } + changeTLS() { this.kdbServer.tls = !this.kdbServer.tls; } - renderServerNameDesc() { - return this.isBundledQ + editAuthOfConn() { + this.editAuth = !this.editAuth; + this.requestUpdate(); + } + + renderServerNameDesc(isBundleQ?: boolean) { + return isBundleQ ? html`Name your server "local"; this name has been reserved for use by the packaged q in the kdb VS Code extension and must be used to access @@ -95,8 +150,8 @@ export class KdbNewConnectionView extends LitElement { >`; } - renderServerNameField(serverType: ServerType) { - return this.isBundledQ + renderServerNameField(serverType: ServerType, isBundleQ?: boolean) { + return isBundleQ ? html``; } - renderServerName(serverType: ServerType) { + renderServerName(serverType: ServerType, isBundleQ?: boolean) { return html` -
${this.renderServerNameField(serverType)}
+
+ ${this.renderServerNameField(serverType, isBundleQ)} +
- ${this.renderServerNameDesc()} + ${this.renderServerNameDesc(isBundleQ)}
`; } - renderPortNumberDesc() { - return this.isBundledQ + renderPortNumberDesc(isBundleQ?: boolean) { + return isBundleQ ? html`Ensure the port number you use does not conflict with another port.`; } - renderPortNumber() { + renderPortNumber(isBundleQ?: boolean) { return html`
- ${this.renderPortNumberDesc()} + ${this.renderPortNumberDesc(isBundleQ)}
`; } - renderConnAddDesc(serverType: ServerType) { - return this.isBundledQ + renderConnAddDesc(serverType: ServerType, isBundleQ?: boolean) { + return isBundleQ ? html`The localhost connection is already set up for you.` : serverType === ServerType.KDB ? html`Set the IP of your kdb+ database connection.` @@ -179,8 +236,8 @@ export class KdbNewConnectionView extends LitElement { connection must be deployed for kdb VS Code to access.`; } - renderConnAddress(serverType: ServerType) { - return this.isBundledQ + renderConnAddress(serverType: ServerType, isBundleQ?: boolean) { + return isBundleQ ? html`
No Color Selected + ${repeat( + this.lblColorsList, + (color) => color, + (color) => + html` +
+ ${color.name}
+
`, + )} + `; + } + + renderLblDropdownOptions() { + return html` + No Label Selected + ${repeat( + this.lblNamesList, + (lbl) => lbl.name, + (lbl) => html` + + +
+ ${lbl.name} +
+
+ `, + )} + `; + } + + renderNewLabelModal() { + return html` +
+ + + + `; + } + + renderNewLblBtn() { + return html` + + Create New Label + + `; + } + + renderConnectionLabelsSection() { + return html`
+
+
Connection label (optional)
+
+ + ${this.renderNewLblBtn()} +
+
+
`; + } + + renderNewConnectionForm() { return html`
+ ${this.isModalOpen ? this.renderNewLabelModal() : ""}
@@ -263,165 +454,353 @@ export class KdbNewConnectionView extends LitElement {
If you are new to kdb and q, start with the - “Bundled q” that comes packaged with the - kdb VS Code extension.“Bundled q” that comes packaged with the kdb VS Code + extension. -
-
-
+
+
+
+ If you are familiar with q and are running a remote q process, - then use “My q”. Please ensure your remote q process is running - before connecting it to the kdb VS Code extension otherwise you - will get a connection error. -
-
-
- If you are an Insights user, then use an “Insights connection”. - You will be required to authenticate the connection prior to its - availability in the kdb VS Code extension. -
-
-
- + then use “My q”. Please ensure your remote q process is + running before connecting it to the kdb VS Code extension + otherwise you will get a connection error. +
+
+
+ + If you are an Insights user, then use an + “Insights connection”. You will be required to + authenticate the connection prior to its availability in the kdb + VS Code extension. +
+
+
+ + Bundled q + My q Bundled q - My q - Insights connection - + id="tab-3" + @click="${() => this.tabClickAction(3)}" + >Insights connection +
- ${this.renderServerName(ServerType.KDB)} + ${this.renderServerName(ServerType.KDB, true)}
- ${this.renderConnAddress(ServerType.KDB)} + ${this.renderConnAddress(ServerType.KDB, true)}
+
+
${this.renderPortNumber(true)}
+
+ ${this.renderConnectionLabelsSection()} + ${this.renderCreateConnectionBtn()} +
+
+ +
- ${this.renderPortNumber()} + ${this.renderServerName(ServerType.KDB)}
-
-
- -
-
-
- ${this.renderServerName(ServerType.KDB)} -
+
+
+ ${this.renderConnAddress(ServerType.KDB)}
-
-
- ${this.renderConnAddress(ServerType.KDB)} +
+
+
${this.renderPortNumber()}
+
+
+ Add Authentication if enabled +
+
+
+
+ Username
-
-
-
- ${this.renderPortNumber()} +
+ Password
-
-
- Add Authentication if enabled -
-
-
-
- Username -
-
- Password -
-
- Add required authentication to get access to the - server connection if enabled. -
+
+ Add required authentication to get access to the server + connection if enabled.
-
-
-
- Optional: Enable TLS Encryption -
-
- Enable TLS Encryption on the kdb - connection -
+
+
+
+
+ Optional: Enable TLS Encryption +
+
+ Enable TLS Encryption on the kdb + connection
- - -
-
-
- ${this.renderServerName(ServerType.INSIGHTS)} -
+ ${this.renderConnectionLabelsSection()} + ${this.renderCreateConnectionBtn()} +
+ + +
+
+
+ ${this.renderServerName(ServerType.INSIGHTS)}
-
-
- ${this.renderConnAddress(ServerType.INSIGHTS)} -
+
+
+
+ ${this.renderConnAddress(ServerType.INSIGHTS)}
-
-
+
+
+
Advanced ${this.renderRealm()} -
-
+
+ Accept insecure SSL certifcates +
+
- - + ${this.renderConnectionLabelsSection()} + ${this.renderCreateConnectionBtn()} +
+ + +
+
+
+
+ `; + } + + renderCreateConnectionBtn() { + return html`
+ Create Connection +
`; + } + + renderEditConnectionForm() { + if (!this.connectionData) { + return html`
No connection found to be edited
`; + } + this.isBundledQ = this.connectionData.connType === 0; + this.oldAlias = this.connectionData.serverName; + const connTypeName = this.defineConnTypeName(this.connectionData.connType); + this.serverType = + this.connectionData.connType === 2 ? ServerType.INSIGHTS : ServerType.KDB; + return html` +
+ ${this.isModalOpen ? this.renderNewLabelModal() : ""} +
+
+
+

Edit ${connTypeName} Connection

+
+
+ Editing an active connection may require you to + restart the connection. If so, you will be prompted to + reconnect after saving your changes. +
+
+
${this.renderEditConnFields()}
+
${this.renderConnectionLabelsSection()}
+
+ Update Connection +
+
+
+ `; + } + + defineConnTypeName(connType: number) { + if (connType === 0) { + return "Bundled q"; + } else if (connType === 1) { + return "My q"; + } else { + return "Insights"; + } + } + + renderEditConnFields() { + if (!this.connectionData) { + return html`
No connection found to be edited
`; + } + if (this.connectionData.connType === 0) { + return this.renderBundleQEditForm(); + } else if (this.connectionData.connType === 1) { + return this.renderMyQEditForm(); + } else { + return this.renderInsightsEditForm(); + } + } + + renderBundleQEditForm() { + if (!this.connectionData) { + return html`
No connection found to be edited
`; + } + this.bundledServer.serverAlias = "local"; + this.bundledServer.serverPort = this.connectionData.port ?? ""; + this.bundledServer.serverName = this.connectionData.serverAddress; + return html` +
+
+
+ ${this.renderServerName(ServerType.KDB, true)} +
+
+
+
+ ${this.renderConnAddress(ServerType.KDB, true)} +
+
+
+
${this.renderPortNumber(true)}
+
+
+ `; + } + + renderMyQEditForm() { + if (!this.connectionData) { + return html`
No connection found to be edited
`; + } + this.kdbServer.serverAlias = this.connectionData.serverName; + this.kdbServer.serverPort = this.connectionData.port ?? ""; + this.kdbServer.serverName = this.connectionData.serverAddress; + this.kdbServer.auth = this.connectionData.auth ?? false; + this.kdbServer.tls = this.connectionData.tls ?? false; + return html` +
+
+
${this.renderServerName(ServerType.KDB)}
+
+
+
${this.renderConnAddress(ServerType.KDB)}
+
+
+
${this.renderPortNumber()}
+
+
+
+
Optional: Edit Auth options
+
+ Edit existing auth on the kdb connection
-
+
+ ${this.editAuth + ? html` +
+
+
+ Username +
+
+ Password +
+
+ Add required authentication to get access to the server + connection if enabled. +
+
+
+ ` + : ""} +
+
+
Optional: Enable TLS Encryption
- Create ConnectionEnable TLS Encryption on the kdb connection
@@ -430,6 +809,45 @@ export class KdbNewConnectionView extends LitElement { `; } + renderInsightsEditForm() { + if (!this.connectionData) { + return html`
No connection found to be edited
`; + } + this.insightsServer.alias = this.connectionData.serverName; + this.insightsServer.server = this.connectionData.serverAddress; + this.insightsServer.realm = this.connectionData.realm ?? ""; + return html` +
+
+
+ ${this.renderServerName(ServerType.INSIGHTS)} +
+
+
+
+ ${this.renderConnAddress(ServerType.INSIGHTS)} +
+
+
+
+
+ Advanced + ${this.renderRealm()} +
+
+
+
+ `; + } + + render() { + if (!this.connectionData) { + return html` ${this.renderNewConnectionForm()} `; + } else { + return html` ${this.renderEditConnectionForm()} `; + } + } + private get data(): ServerDetails | InsightDetails { switch (this.serverType) { case ServerType.INSIGHTS: @@ -449,16 +867,62 @@ export class KdbNewConnectionView extends LitElement { this.vscode.postMessage({ command: "kdb.newConnection.createNewBundledConnection", data: this.bundledServer, + labels: this.labels, }); } else if (this.serverType === ServerType.INSIGHTS) { this.vscode.postMessage({ command: "kdb.newConnection.createNewInsightConnection", data: this.data, + labels: this.labels, }); } else { this.vscode.postMessage({ command: "kdb.newConnection.createNewConnection", data: this.data, + labels: this.labels, + }); + } + } + + private createLabel() { + this.vscode.postMessage({ + command: "kdb.labels.create", + data: { + name: this.newLblName, + colorName: this.newLblColorName, + }, + }); + setTimeout(() => { + this.labels[0] = this.newLblName; + this.closeModal(); + }, 500); + } + + private editConnection() { + if (!this.connectionData) { + return; + } + if (this.connectionData.connType === 0) { + this.vscode.postMessage({ + command: "kdb.newConnection.editBundledConnection", + data: this.bundledServer, + oldAlias: "local", + labels: this.labels, + }); + } else if (this.connectionData.connType === 1) { + this.vscode.postMessage({ + command: "kdb.newConnection.editMyQConnection", + data: this.data, + oldAlias: this.oldAlias, + editAuth: this.editAuth, + labels: this.labels, + }); + } else { + this.vscode.postMessage({ + command: "kdb.newConnection.editInsightsConnection", + data: this.data, + oldAlias: this.oldAlias, + labels: this.labels, }); } } diff --git a/src/webview/components/styles.ts b/src/webview/components/styles.ts index 5066127f..ee290445 100644 --- a/src/webview/components/styles.ts +++ b/src/webview/components/styles.ts @@ -165,4 +165,37 @@ export const newConnectionStyles = css` .text-field.larger { width: 20em; } + + .modal { + position: fixed; + top: 50%; + transform: translate(-50%, -50%); + background: var(--vscode-editor-background); + color: var(--vscode-editor-foreground); + padding: 1rem; + z-index: 1001; + border: 1px solid var(--vscode-editorWidget-border); + box-shadow: 0 2px 10px var(--vscode-widget-shadow); + } + + .modal-content h2 { + color: var(--vscode-editor-foreground); + } + vscode-text-field, + vscode-dropdown, + vscode-button { + --vscode-input-background: var(--vscode-editor-background); + --vscode-input-foreground: var(--vscode-editor-foreground); + --vscode-input-border: var(--vscode-editorWidget-border); + } + + .overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + z-index: 1000; + } `; diff --git a/test/suite/commands.test.ts b/test/suite/commands.test.ts index fbd7ed44..548de197 100644 --- a/test/suite/commands.test.ts +++ b/test/suite/commands.test.ts @@ -53,7 +53,6 @@ import { WorkspaceTreeProvider } from "../../src/services/workspaceTreeProvider" import { GetDataError } from "../../src/models/data"; import * as clientCommand from "../../src/commands/clientCommands"; import { LanguageClient } from "vscode-languageclient/node"; -import { MetaContentProvider } from "../../src/services/metaContentProvider"; describe("dataSourceCommand", () => { afterEach(() => { @@ -942,6 +941,30 @@ describe("serverCommand", () => { sinon.restore(); }); + it("should call the Edit Connection Panel Renderer", async () => { + const newConnectionPanelStub = sinon.stub(NewConnectionPannel, "render"); + ext.context = {}; + await serverCommand.editConnection(kdbNode); + sinon.assert.calledOnce(newConnectionPanelStub); + sinon.restore(); + }); + + describe("isConnected", () => { + let connMngServiceMock: sinon.SinonStubbedInstance; + + beforeEach(() => { + connMngServiceMock = sinon.createStubInstance( + ConnectionManagementService, + ); + }); + + it("deve retornar false quando isConnected do ConnectionManagementService retornar false", () => { + connMngServiceMock.isConnected.returns(false); + const result = serverCommand.isConnected("127.0.0.1:6812 [CONNLABEL]"); + assert.deepStrictEqual(result, false); + }); + }); + describe("addInsightsConnection", () => { let insightsData: InsightDetails; let updateInsightsStub, getInsightsStub: sinon.SinonStub; @@ -962,7 +985,7 @@ describe("serverCommand", () => { }); it("should add new Insights connection", async () => { getInsightsStub.returns({}); - await serverCommand.addInsightsConnection(insightsData); + await serverCommand.addInsightsConnection(insightsData, ["lblTest"]); sinon.assert.calledOnce(updateInsightsStub); windowMock .expects("showInformationMessage") @@ -985,16 +1008,6 @@ describe("serverCommand", () => { .once() .withArgs("Invalid Insights connection"); }); - it("should add connection where alias is not provided", async () => { - insightsData.alias = ""; - getInsightsStub.returns({}); - await serverCommand.addInsightsConnection(insightsData); - sinon.assert.calledOnce(updateInsightsStub); - windowMock - .expects("showInformationMessage") - .once() - .withArgs("Insights connection added successfully"); - }); }); describe("addKdbConnection", () => { @@ -1022,7 +1035,7 @@ describe("serverCommand", () => { it("should add new Kdb connection", async () => { getServersStub.returns({}); - await serverCommand.addKdbConnection(kdbData); + await serverCommand.addKdbConnection(kdbData, false, ["lblTest"]); sinon.assert.calledOnce(updateServersStub); windowMock .expects("showInformationMessage") @@ -1045,15 +1058,13 @@ describe("serverCommand", () => { .once() .withArgs("Invalid Kdb connection"); }); - it("should add connection where alias is not provided", async () => { + it("should show error message if connection where alias is not provided", async () => { kdbData.serverAlias = ""; - getServersStub.returns({}); await serverCommand.addKdbConnection(kdbData); - sinon.assert.calledOnce(updateServersStub); windowMock - .expects("showInformationMessage") + .expects("showErrorMessage") .once() - .withArgs("Kdb connection added successfully"); + .withArgs("Server Name is required"); }); it("should give error if alias is local and isLocal is false", async () => { kdbData.serverAlias = "local"; diff --git a/test/suite/panels.test.ts b/test/suite/panels.test.ts index 554ed111..71570fc8 100644 --- a/test/suite/panels.test.ts +++ b/test/suite/panels.test.ts @@ -19,7 +19,7 @@ import { createDefaultDataSourceFile } from "../../src/models/dataSource"; import { DataSourcesPanel } from "../../src/panels/datasource"; import { KdbResultsViewProvider } from "../../src/services/resultsPanelProvider"; import * as utils from "../../src/utils/execution"; -import { InsightsNode } from "../../src/services/kdbTreeProvider"; +import { InsightsNode, KdbNode } from "../../src/services/kdbTreeProvider"; import { TreeItemCollapsibleState } from "vscode"; import { NewConnectionPannel } from "../../src/panels/newConnection"; import { InsightsConnection } from "../../src/classes/insightsConnection"; @@ -446,23 +446,68 @@ describe("WebPanels", () => { describe("kdbNewConnectionPanel", () => { const uriTest: vscode.Uri = vscode.Uri.parse("test"); + const insightsNode = new InsightsNode( + [], + "insightsnode1", + { + server: "insightsservername", + alias: "insightsserveralias", + auth: true, + }, + TreeItemCollapsibleState.None, + ); - beforeEach(() => { - NewConnectionPannel.render(uriTest); - }); + const kdbNode = new KdbNode( + [], + "kdbnode1", + { + serverName: "kdbservername", + serverPort: "kdbserverport", + auth: true, + serverAlias: "kdbserveralias", + managed: false, + tls: true, + }, + TreeItemCollapsibleState.None, + ); + + const localNode = new KdbNode( + [], + "local", + { + serverName: "kdbservername", + serverPort: "kdbserverport", + auth: false, + serverAlias: "local", + managed: true, + tls: false, + }, + TreeItemCollapsibleState.None, + ); afterEach(() => { NewConnectionPannel.close(); }); it("should create a new panel", () => { + NewConnectionPannel.render(uriTest); assert.ok( NewConnectionPannel.currentPanel, "NewConnectionPannel.currentPanel should be truthy", ); }); + it("should close teh panel if open", () => { + NewConnectionPannel.render(uriTest); + NewConnectionPannel.render(uriTest); + assert.ok( + !NewConnectionPannel.currentPanel, + "NewConnectionPannel.currentPanel should be falsy", + ); + }); + it("should close", () => { + NewConnectionPannel.render(uriTest); NewConnectionPannel.close(); assert.strictEqual( NewConnectionPannel.currentPanel, @@ -472,6 +517,37 @@ describe("WebPanels", () => { }); it("should make sure the Create New Connection panel is rendered, check if the web component exists", () => { + NewConnectionPannel.render(uriTest); + const expectedHtml = ``; + const actualHtml = NewConnectionPannel.currentPanel._panel.webview.html; + assert.ok( + actualHtml.indexOf(expectedHtml) !== -1, + "Panel HTML should include expected web component", + ); + }); + + it("should render panel with edit connection data for insights", () => { + NewConnectionPannel.render(uriTest, insightsNode); + const expectedHtml = ``; + const actualHtml = NewConnectionPannel.currentPanel._panel.webview.html; + assert.ok( + actualHtml.indexOf(expectedHtml) !== -1, + "Panel HTML should include expected web component", + ); + }); + + it("should render panel with edit connection data for kdb", () => { + NewConnectionPannel.render(uriTest, kdbNode); + const expectedHtml = ``; + const actualHtml = NewConnectionPannel.currentPanel._panel.webview.html; + assert.ok( + actualHtml.indexOf(expectedHtml) !== -1, + "Panel HTML should include expected web component", + ); + }); + + it("should render panel with edit connection data for local", () => { + NewConnectionPannel.render(uriTest, localNode); const expectedHtml = ``; const actualHtml = NewConnectionPannel.currentPanel._panel.webview.html; assert.ok( @@ -479,5 +555,14 @@ describe("WebPanels", () => { "Panel HTML should include expected web component", ); }); + + it("should refreshLabels", () => { + NewConnectionPannel.render(uriTest); + NewConnectionPannel.refreshLabels(); + assert.ok( + NewConnectionPannel.currentPanel, + "NewConnectionPannel.currentPanel should be truthy", + ); + }); }); }); diff --git a/test/suite/services.test.ts b/test/suite/services.test.ts index 71163bc5..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); + }); }); }); @@ -726,13 +758,18 @@ describe("Code flow login service tests", () => { it("Should return a correct login", async () => { sinon.stub(codeFlow, "signIn").returns(token); - const result = await signIn("http://localhost", "insights"); + const result = await signIn("http://localhost", "insights", false); assert.strictEqual(result, token, "Invalid token returned"); }); it("Should execute a correct logout", async () => { sinon.stub(axios, "post").resolves(Promise.resolve({ data: token })); - const result = await signOut("http://localhost", "insights", "token"); + const result = await signOut( + "http://localhost", + "insights", + false, + "token", + ); assert.strictEqual(result, undefined, "Invalid response from logout"); }); @@ -741,6 +778,7 @@ describe("Code flow login service tests", () => { const result = await refreshToken( "http://localhost", "insights", + false, JSON.stringify(token), ); assert.strictEqual( @@ -751,7 +789,7 @@ describe("Code flow login service tests", () => { }); it("Should not return token from secret store", async () => { - const result = await getCurrentToken("", "testalias", "insights"); + const result = await getCurrentToken("", "testalias", "insights", false); assert.strictEqual( result, undefined, @@ -760,7 +798,7 @@ describe("Code flow login service tests", () => { }); it("Should not return token from secret store", async () => { - const result = await getCurrentToken("testserver", "", "insights"); + const result = await getCurrentToken("testserver", "", "insights", false); assert.strictEqual( result, undefined, @@ -772,7 +810,7 @@ describe("Code flow login service tests", () => { sinon.stub(env, "openExternal").value(async () => { throw new Error(); }); - await assert.rejects(() => signIn("http://127.0.0.1", "insights")); + await assert.rejects(() => signIn("http://127.0.0.1", "insights", false)); }); }); diff --git a/test/suite/utils.test.ts b/test/suite/utils.test.ts index 3572807c..5e55684a 100644 --- a/test/suite/utils.test.ts +++ b/test/suite/utils.test.ts @@ -23,13 +23,13 @@ import { QueryResultType } from "../../src/models/queryResult"; import { ServerDetails, ServerType } from "../../src/models/server"; import { InsightsNode, KdbNode } from "../../src/services/kdbTreeProvider"; import { QueryHistoryProvider } from "../../src/services/queryHistoryProvider"; -import { KdbResultsViewProvider } from "../../src/services/resultsPanelProvider"; import * as coreUtils from "../../src/utils/core"; import * as cpUtils from "../../src/utils/cpUtils"; import * as dataSourceUtils from "../../src/utils/dataSource"; import * as decodeUtils from "../../src/utils/decode"; import * as executionUtils from "../../src/utils/execution"; import * as executionConsoleUtils from "../../src/utils/executionConsole"; +import * as LabelsUtils from "../../src/utils/connLabel"; import { getNonce } from "../../src/utils/getNonce"; import { getUri } from "../../src/utils/getUri"; import { openUrl } from "../../src/utils/openUrl"; @@ -51,6 +51,7 @@ import { import { DataSourceTypes } from "../../src/models/dataSource"; import { InsightDetails } from "../../src/models/insights"; import { LocalConnection } from "../../src/classes/localConnection"; +import { Labels } from "../../src/models/labels"; interface ITestItem extends vscode.QuickPickItem { id: number; @@ -1593,4 +1594,244 @@ describe("Utils", () => { }); }); }); + + describe("connLabelsUtils", () => { + let getConfigurationStub: sinon.SinonStub; + + beforeEach(() => { + getConfigurationStub = sinon.stub(vscode.workspace, "getConfiguration"); + ext.connLabelList.length = 0; + ext.labelConnMapList.length = 0; + }); + + afterEach(() => { + sinon.restore(); + }); + + it("should get workspace labels", () => { + const labels: Labels[] = [ + { name: "label1", color: { name: "red", colorHex: "#FF0000" } }, + ]; + getConfigurationStub.returns({ + get: sinon.stub().returns(labels), + update: sinon.stub(), + }); + + LabelsUtils.getWorkspaceLabels(); + + assert.strictEqual(ext.connLabelList.length, 1); + + assert.deepStrictEqual(ext.connLabelList, labels); + }); + + it("should create a new label", () => { + getConfigurationStub.returns({ + get: sinon.stub().returns([]), + update: sinon.stub(), + }); + + LabelsUtils.createNewLabel("label1", "red"); + + assert.strictEqual(ext.connLabelList.length, 1); + assert.strictEqual(ext.connLabelList[0].name, "label1"); + assert.strictEqual(ext.connLabelList[0].color.name, "Red"); + }); + + it("should handle empty label name", () => { + getConfigurationStub.returns({ + get: sinon.stub(), + update: sinon.stub(), + }); + const logStub = sinon.stub(coreUtils, "kdbOutputLog"); + + LabelsUtils.createNewLabel("", "red"); + + sinon.assert.calledWith(logStub, "Label name can't be empty", "ERROR"); + }); + + it("should handle no color selected", () => { + getConfigurationStub.returns({ + get: sinon.stub(), + update: sinon.stub(), + }); + const logStub = sinon.stub(coreUtils, "kdbOutputLog"); + + LabelsUtils.createNewLabel("label1", "randomColorName"); + + sinon.assert.calledWith( + logStub, + "No Color selected for the label", + "ERROR", + ); + }); + + it("should get workspace labels connection map", () => { + const connMap = [{ labelName: "label1", connections: ["conn1"] }]; + getConfigurationStub.returns({ + get: sinon.stub().returns(connMap), + update: sinon.stub(), + }); + + LabelsUtils.getWorkspaceLabelsConnMap(); + + assert.deepStrictEqual(ext.labelConnMapList, connMap); + }); + + it("should add connection to label", () => { + ext.connLabelList.push({ + name: "label1", + color: { name: "red", colorHex: "#FF0000" }, + }); + + LabelsUtils.addConnToLabel("label1", "conn1"); + + assert.strictEqual(ext.labelConnMapList.length, 1); + assert.strictEqual(ext.labelConnMapList[0].labelName, "label1"); + assert.deepStrictEqual(ext.labelConnMapList[0].connections, ["conn1"]); + }); + + it("should remove connection from labels", () => { + ext.labelConnMapList.push({ + labelName: "label1", + connections: ["conn1", "conn2"], + }); + + LabelsUtils.removeConnFromLabels("conn1"); + + assert.deepStrictEqual(ext.labelConnMapList[0].connections, ["conn2"]); + }); + + it("should handle labels connection map", () => { + ext.connLabelList.push({ + name: "label1", + color: { name: "Red", colorHex: "#FF0000" }, + }); + ext.labelConnMapList.push({ + labelName: "label1", + connections: ["conn2"], + }); + + getConfigurationStub.returns({ + get: sinon.stub(), + update: sinon.stub(), + }); + + LabelsUtils.handleLabelsConnMap(["label1"], "conn2"); + + assert.strictEqual(ext.labelConnMapList.length, 1); + assert.deepStrictEqual(ext.labelConnMapList[0].connections, ["conn2"]); + }); + + it("should retrieve connection labels names", () => { + ext.labelConnMapList.push({ + labelName: "label1", + connections: ["conn1"], + }); + const conn = new KdbNode( + [], + "conn1", + { + serverName: "kdbservername", + serverAlias: "conn1", + serverPort: "5001", + managed: false, + auth: false, + tls: false, + }, + TreeItemCollapsibleState.None, + ); + + const labels = LabelsUtils.retrieveConnLabelsNames(conn); + + 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); + }); + }); + + describe("isLabelEmpty", () => { + beforeEach(() => { + ext.labelConnMapList.length = 0; + }); + + afterEach(() => { + ext.labelConnMapList.length = 0; + }); + it("should return true if label is empty", () => { + ext.labelConnMapList.push({ labelName: "label1", connections: [] }); + const result = LabelsUtils.isLabelEmpty("label1"); + assert.strictEqual(result, true); + }); + + it("should return false if label is not empty", () => { + ext.labelConnMapList.push({ + labelName: "label1", + connections: ["conn1"], + }); + const result = LabelsUtils.isLabelEmpty("label1"); + assert.strictEqual(result, false); + }); + + it("should return false if label is empty if label not on map list", () => { + const result = LabelsUtils.isLabelEmpty("label1"); + assert.strictEqual(result, true); + }); + }); + + describe("isLabelContentChanged", () => { + beforeEach(() => { + ext.latestLblsChanged.length = 0; + }); + + afterEach(() => { + ext.latestLblsChanged.length = 0; + }); + + it("should return true if label content is changed", () => { + ext.latestLblsChanged.push("label1"); + const result = LabelsUtils.isLabelContentChanged("label1"); + assert.strictEqual(result, true); + }); + + it("should return false if label content is not changed", () => { + const result = LabelsUtils.isLabelContentChanged("label1"); + assert.strictEqual(result, false); + }); + }); }); diff --git a/test/suite/webview.test.ts b/test/suite/webview.test.ts index 740d8948..e239b6dd 100644 --- a/test/suite/webview.test.ts +++ b/test/suite/webview.test.ts @@ -34,7 +34,8 @@ import { createSort, } from "../../src/models/dataSource"; import { MetaObjectPayload } from "../../src/models/meta"; -import { TemplateResult } from "lit"; +import { html, TemplateResult } from "lit"; +import { ext } from "../../src/extensionVariables"; describe("KdbDataSourceView", () => { let view: KdbDataSourceView; @@ -228,6 +229,64 @@ describe("KdbNewConnectionView", () => { view = new KdbNewConnectionView(); }); + describe("handleMessage", () => { + it('should update connectionData when command is "editConnection"', () => { + const event = { + data: { + command: "editConnection", + data: { + serverName: "test", + connType: 1, + serverAddress: "localhost", + }, + }, + }; + + view.handleMessage(event); + + assert.equal(view.connectionData, event.data.data); + }); + + it('should update connectionData when command is "refreshLabels"', () => { + const event = { + data: { + command: "refreshLabels", + data: ["test"], + colors: ext.labelColors, + }, + }; + + view.handleMessage(event); + + assert.equal(view.lblNamesList, event.data.data); + }); + + it('should not update connectionData when command is not "editConnection"', () => { + const event = { + data: { + command: "otherCommand", + data: { serverName: "testServer" }, + }, + }; + + view.handleMessage(event); + + assert.equal(view.connectionData, undefined); + }); + }); + + describe("editAuthOfConn", () => { + it("should toggle editAuth", () => { + view.editAuth = false; + + view.editAuthOfConn(); + assert.equal(view.editAuth, true); + + view.editAuthOfConn(); + assert.equal(view.editAuth, false); + }); + }); + describe("selectConnection", () => { it("should return tab-1", () => { view.isBundledQ = true; @@ -314,8 +373,7 @@ describe("KdbNewConnectionView", () => { }); it("should render port number desc for KDB server", () => { - view.isBundledQ = false; - const result = view.renderPortNumberDesc(ServerType.KDB); + const result = view.renderPortNumberDesc(); assert.strictEqual( JSON.stringify(result).includes("Set port number"), true, @@ -334,8 +392,7 @@ describe("KdbNewConnectionView", () => { }); it("should render port number for KDB server", () => { - view.isBundledQ = false; - const result = view.renderPortNumber(ServerType.KDB); + const result = view.renderPortNumber(); assert.strictEqual( JSON.stringify(result).includes("Set port number"), true, @@ -362,9 +419,10 @@ describe("KdbNewConnectionView", () => { }); it("should render connection address for Bundled q", () => { - const result = view.renderConnAddDesc(ServerType.KDB); + const result = view.renderConnAddDesc(ServerType.KDB, true); + console.log(JSON.stringify(result)); assert.strictEqual( - result.strings[0].includes("lready set up for you"), + result.strings[0].includes("already set up for you"), true, ); }); @@ -380,9 +438,8 @@ describe("KdbNewConnectionView", () => { ); }); - it("should render connection address for bundled q", () => { - view.isBundledQ = true; - const result = view.renderConnAddress(ServerType.KDB); + it("should render connection address for Bundled q", () => { + const result = view.renderConnAddress(ServerType.KDB, true); assert.strictEqual( JSON.stringify(result).includes("127.0.0.1 or localhost"), false, @@ -397,6 +454,62 @@ describe("KdbNewConnectionView", () => { true, ); }); + + it("should render label dropdown color options", () => { + view.lblColorsList = [ + { name: "red", colorHex: "#FF0000" }, + { name: "green", colorHex: "#00FF00" }, + ]; + + const result = view.renderLblDropdownColorOptions(); + + assert.strictEqual( + JSON.stringify(result).includes("No Color Selected"), + true, + ); + }); + + it("should render label dropdown options", () => { + view.lblNamesList = [ + { name: "label1", color: { colorHex: "#FF0000" } }, + { name: "label2", color: { colorHex: "#00FF00" } }, + ]; + view.labels = ["label1"]; + + const result = view.renderLblDropdownOptions(); + + assert.strictEqual( + JSON.stringify(result).includes("No Label Selected"), + true, + ); + }); + + it("should render New Label Modal", () => { + const result = view.renderNewLabelModal(); + + assert.strictEqual( + JSON.stringify(result).includes("Add a New Label"), + true, + ); + }); + + it("should render New Label Btn", () => { + const result = view.renderNewLblBtn(); + + assert.strictEqual( + JSON.stringify(result).includes("Create New Label"), + true, + ); + }); + + it("should render Connection Label Section", () => { + const result = view.renderConnectionLabelsSection(); + + assert.strictEqual( + JSON.stringify(result).includes("Connection label (optional)"), + true, + ); + }); }); describe("tabClickAction", () => { @@ -472,6 +585,189 @@ describe("KdbNewConnectionView", () => { }); }); + describe("renderCreateConnectionBtn", () => { + it("should render create connection button", () => { + const result = view.renderCreateConnectionBtn(); + + assert.strictEqual( + JSON.stringify(result).includes("Create Connection"), + true, + ); + }); + }); + + describe("renderEditConnectionForm", () => { + it('should return "No connection found to be edited" when connectionData is null', () => { + view.connectionData = null; + + const result = view.renderEditConnectionForm(); + + assert.strictEqual( + result.strings[0], + "
No connection found to be edited
", + ); + }); + + it("should set isBundledQ to true and return correct HTML when connType is 0", () => { + view.connectionData = { connType: 0, serverName: "testServer" }; + + const result = view.renderEditConnectionForm(); + assert.strictEqual(view.isBundledQ, true); + assert.strictEqual(view.oldAlias, "testServer"); + assert.strictEqual(view.serverType, ServerType.KDB); + assert.strictEqual(result.values[1].includes("Bundled q"), true); + }); + + it("should set isBundledQ to false and return correct HTML when connType is 1", () => { + view.connectionData = { connType: 1, serverName: "testServer" }; + + const result = view.renderEditConnectionForm(); + + assert.strictEqual(view.isBundledQ, false); + assert.strictEqual(view.oldAlias, "testServer"); + assert.strictEqual(view.serverType, ServerType.KDB); + assert.strictEqual(result.values[1].includes("My q"), true); + }); + + it("should set serverType to INSIGHTS and return correct HTML when connType is 2", () => { + view.connectionData = { connType: 2, serverName: "testServer" }; + + const result = view.renderEditConnectionForm(); + + assert.strictEqual(view.isBundledQ, false); + assert.strictEqual(view.oldAlias, "testServer"); + assert.strictEqual(view.serverType, ServerType.INSIGHTS); + assert.strictEqual(result.values[1].includes("Insights"), true); + }); + + it("should set serverType to INSIGHTS and open labels modal", () => { + view.connectionData = { connType: 2, serverName: "testServer" }; + view.openModal(); + + const result = view.renderEditConnectionForm(); + const resultsStrings = JSON.stringify(result); + + assert.strictEqual(view.isBundledQ, false); + assert.strictEqual(view.oldAlias, "testServer"); + assert.strictEqual(view.serverType, ServerType.INSIGHTS); + assert.strictEqual(result.values[1].includes("Insights"), true); + assert.strictEqual(resultsStrings.includes("Add a New Label"), true); + }); + }); + + describe("renderEditConnFields", () => { + it('should return "No connection found to be edited" when connectionData is null', () => { + view.connectionData = null; + const result = view.renderEditConnFields(); + assert.equal( + result.strings[0], + "
No connection found to be edited
", + ); + }); + + it("should call renderBundleQEditForm when connectionData.connType is 0", () => { + view.connectionData = { connType: 0 }; + const renderBundleQEditFormStub = sinon + .stub(view, "renderBundleQEditForm") + .returns(html``); + view.renderEditConnFields(); + assert.ok(renderBundleQEditFormStub.calledOnce); + renderBundleQEditFormStub.restore(); + }); + + it("should call renderMyQEditForm when connectionData.connType is 1", () => { + view.connectionData = { connType: 1 }; + const renderMyQEditFormStub = sinon + .stub(view, "renderMyQEditForm") + .returns(html``); + view.renderEditConnFields(); + assert.ok(renderMyQEditFormStub.calledOnce); + renderMyQEditFormStub.restore(); + }); + + it("should call renderInsightsEditForm when connectionData.connType is any other value", () => { + view.connectionData = { connType: 2 }; + const renderInsightsEditFormStub = sinon + .stub(view, "renderInsightsEditForm") + .returns(html``); + view.renderEditConnFields(); + assert.ok(renderInsightsEditFormStub.calledOnce); + renderInsightsEditFormStub.restore(); + }); + }); + + describe("renderBundleQEditForm", () => { + it('should return "No connection found to be edited" when connectionData is null', () => { + view.connectionData = null; + const result = view.renderBundleQEditForm(); + assert.strictEqual( + result.strings[0], + "
No connection found to be edited
", + ); + }); + + it("should return the correct HTML structure when connectionData is provided", () => { + view.connectionData = { + port: "5000", + serverAddress: "localhost", + serverName: "local", + }; + const result = view.renderBundleQEditForm(); + assert.ok(result.strings[0].includes('
')); + assert.ok(result.strings[1].includes('
')); + assert.ok(result.strings[2].includes('
')); + assert.ok(!result.strings[3].includes('
')); + }); + }); + + describe("renderMyQEditForm", () => { + it('should return "No connection found to be edited" when connectionData is null', () => { + view.connectionData = null; + const result = view.renderMyQEditForm(); + assert.strictEqual( + result.strings[0], + "
No connection found to be edited
", + ); + }); + + it("should return the correct HTML structure when connectionData is provided", () => { + view.connectionData = { + port: "5000", + serverAddress: "localhost", + serverName: "local", + }; + const result = view.renderMyQEditForm(); + assert.ok(result.strings[0].includes('
')); + assert.ok(result.strings[1].includes('
')); + assert.ok(result.strings[2].includes('
')); + assert.ok(result.strings[3].includes('
')); + }); + }); + + describe("renderInsightsEditForm", () => { + it('should return "No connection found to be edited" when connectionData is null', () => { + view.connectionData = null; + const result = view.renderInsightsEditForm(); + assert.strictEqual( + result.strings[0], + "
No connection found to be edited
", + ); + }); + + it("should return the correct HTML structure when connectionData is provided", () => { + view.connectionData = { + port: "5000", + serverAddress: "localhost", + serverName: "local", + }; + const result = view.renderInsightsEditForm(); + assert.ok(result.strings[0].includes('
')); + assert.ok(result.strings[1].includes('
')); + assert.ok(result.strings[2].includes('
')); + assert.ok(!result.strings[3].includes('
')); + }); + }); + describe("get data", () => { it("should return Insights data", () => { view.serverType = ServerType.INSIGHTS; @@ -480,6 +776,7 @@ describe("KdbNewConnectionView", () => { server: "", auth: true, realm: "", + insecure: false, }; const data = view["data"]; assert.deepEqual(data, expectedData); diff --git a/test/suite/workspace.test.ts b/test/suite/workspace.test.ts index e14ec77e..fdf55a58 100644 --- a/test/suite/workspace.test.ts +++ b/test/suite/workspace.test.ts @@ -46,7 +46,7 @@ describe("Workspace tests", () => { assert.throws( () => workspaceHelper.getWorkspaceRoot(), Error, - "Workspace root should be defined." + "Workspace root should be defined.", ); }); @@ -68,3 +68,13 @@ describe("Workspace tests", () => { assert.strictEqual(result, true); }); }); + +describe("activateTextDocument", () => { + it("should activate document", async () => { + sinon.stub(vscode.workspace, "openTextDocument").value(() => ({})); + const stub = sinon.stub(vscode.window, "showTextDocument"); + const uri = vscode.Uri.file("/test/test.q"); + await workspaceHelper.activateTextDocument(uri); + assert.strictEqual(stub.calledOnce, true); + }); +});