diff --git a/.evergreen/buildvariants.yml b/.evergreen/buildvariants.yml index 14555b11c5d..81c603aeb72 100644 --- a/.evergreen/buildvariants.yml +++ b/.evergreen/buildvariants.yml @@ -150,8 +150,8 @@ buildvariants: depends_on: package-compass - name: rhel - display_name: RHEL 7.6 (Test and Package) - run_on: rhel76-large + display_name: RHEL 8.0 (Test and Package) + run_on: rhel80-large tasks: - name: check diff --git a/.evergreen/config.json b/.evergreen/config.json index 204c9603835..18eac8fd0f0 100644 --- a/.evergreen/config.json +++ b/.evergreen/config.json @@ -3,7 +3,7 @@ "run_on": [ "ubuntu2004-large", "windows-vsCurrent-large", - "rhel76-large", + "rhel80-large", ["macos-1100", { "gui": "macos-1100-gui" }], ["macos-1100-arm64", { "gui": "macos-1100-arm64-gui" }] ], @@ -136,7 +136,7 @@ "vars": { "mongodb_version": "latest-alpha-enterprise" }, - "skip_on": ["macos-1100", "macos-1100-arm64", "rhel76-large"] + "skip_on": ["macos-1100", "macos-1100-arm64", "rhel80-large"] } ], "test-web-sandbox": [ @@ -146,7 +146,7 @@ "mongodb_version": "latest-alpha-enterprise", "browser_name": "chrome" }, - "skip_on": ["macos-1100", "macos-1100-arm64", "rhel76-large", "windows-vsCurrent-large"] + "skip_on": ["macos-1100", "macos-1100-arm64", "rhel80-large", "windows-vsCurrent-large"] }, { "name": "firefox", @@ -154,7 +154,7 @@ "mongodb_version": "latest-alpha-enterprise", "browser_name": "firefox" }, - "skip_on": ["macos-1100", "macos-1100-arm64", "rhel76-large", "windows-vsCurrent-large"] + "skip_on": ["macos-1100", "macos-1100-arm64", "rhel80-large", "windows-vsCurrent-large"] } ] } @@ -163,7 +163,7 @@ "short": { "ubuntu2004-large": "ubuntu", "windows-vsCurrent-large": "windows", - "rhel76-large": "rhel", + "rhel80-large": "rhel", "macos-1100": "macos", "macos-1100-arm64": "macos-arm", "macos-1100-gui": "macos-gui", @@ -172,7 +172,7 @@ "long": { "ubuntu2004-large": "Ubuntu 20.04", "windows-vsCurrent-large": "Windows 10", - "rhel76-large": "RHEL 7.6", + "rhel80-large": "RHEL 8.0", "macos-1100": "MacOS x64 11.00", "macos-1100-arm64": "MacOS arm64 11.00", "macos-1100-gui": "MacOS x64 11.00 w/ GUI Session", diff --git a/.evergreen/verify-artifacts.sh b/.evergreen/verify-artifacts.sh index 007e300843e..11864a81b55 100755 --- a/.evergreen/verify-artifacts.sh +++ b/.evergreen/verify-artifacts.sh @@ -6,7 +6,7 @@ ARTIFACTS_DIR="packages/compass/dist" echo "Verifying artifacts at $ARTIFACTS_DIR" ls -l $ARTIFACTS_DIR -# Use tmp directory for all gpg operations +# Use tmp directory for all gpg operations/the rpm database GPG_HOME=$(mktemp -d) TMP_FILE=$(mktemp) COMPASS_KEY="https://pgp.mongodb.com/compass.asc" @@ -45,16 +45,14 @@ verify_using_rpm() { # RPM packages are signed using gpg and the signature is embedded in the package. # Here, we need to import the key in `rpm` and then verify the signature. echo "Importing key into rpm" - rpm --import $COMPASS_KEY > "$TMP_FILE" 2>&1 - # Even if the file is not signed, the command below will exit with 0 and output something like: sha1 md5 OK + rpm --dbpath "$GPG_HOME" --import $COMPASS_KEY > "$TMP_FILE" 2>&1 + # Even if the file is not signed, the command below will exit with 0 and output something like: digests OK # So we need to check the output of the command to see if the file is signed successfully. echo "Verifying $1 using rpm" - output=$(rpm -K $ARTIFACTS_DIR/$1) - # Remove the imported key from rpm - rpm -e $(rpm -q gpg-pubkey --qf '%{name}-%{version}-%{release}:%{summary}\n' | grep compass | awk -F: '{print $1}') - + output=$(rpm --dbpath "$GPG_HOME" -K $ARTIFACTS_DIR/$1) + # Check if the output contains the string "pgp md5 OK" - if [[ $output != *"pgp md5 OK"* ]]; then + if [[ $output != *"digests signatures OK"* ]]; then echo "File $1 is not signed" exit 1 fi @@ -84,4 +82,4 @@ elif [ "$IS_OSX" = true ]; then else echo "Unknown OS, failed to verify file signing" exit 1 -fi \ No newline at end of file +fi diff --git a/.evergreen/xvfb-service.sh b/.evergreen/xvfb-service.sh index 4d6650a8c7d..836754a5be3 100755 --- a/.evergreen/xvfb-service.sh +++ b/.evergreen/xvfb-service.sh @@ -3,7 +3,7 @@ set -e XVFB=/usr/bin/Xvfb -XVFBARGS=":1 -screen 0 1024x768x24 -ac +extension GLX +render -noreset" +XVFBARGS=":1 -screen 0 1432x840x24 -ac +extension GLX +render -noreset" PIDFILE=/var/run/xvfb.pid case "$1" in start) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index d89ef9501f3..2144bf98080 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -14,6 +14,7 @@ name: "CodeQL" on: push: branches: [ "main", '*-releases' ] + tags: [ 'v*' ] pull_request: # The branches below must be a subset of the branches above branches: [ "main" ] @@ -53,7 +54,7 @@ jobs: # Prefix the list here with "+" to use these queries and those in the config file. # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs - # queries: security-extended,security-and-quality + queries: security-extended config: | paths-ignore: - '**/*.test.js' diff --git a/AUTHORS b/AUTHORS index 5e1c2628bbb..88cff74d465 100644 --- a/AUTHORS +++ b/AUTHORS @@ -82,3 +82,5 @@ Bailey Pearson Hao Paula Stachova Basit +Luca +Alena Khineika diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b1bb1385df6..e4d9c53ebbf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -37,9 +37,13 @@ This repository includes a few recommended plugins for your convenience: ### Working on Plugins -Most of the plugins have their own development environment so you can work on them in isolation. If you want to work on a plugin without running the whole Compass application, you can run `npm run start` in the plugin directory (such as at the top of the `compass/packages/compass-aggregations` directory), either with the help of `lerna` or `npm workspaces`. For example, to start `compass-aggregations` plugin locally, you can either run `npm run start --workspace @mongodb-js/compass-aggregations` from the top of `compass` directory, run `npx lerna run start --scope @mongodb-js/compass-aggregations --stream` from anywhere in the `compass` directory, or run `npm run start` from the top of the `compass/packages/compass-aggregations` directory. Same approaches will work for any other workspace-specific script. If you want to run commands like `test` or `check` only for one specific workspace in the repository, you can use any of the methods described above. As an example, to run all tests in one plugin that you are working on such as the `compass-aggregations` plugin, you can run `npm run test` from the top of the `compass/packages/compass-aggregations` directory. +> [!NOTE] +> For documentation regarding how to write plugin packages, check out the +> [hadron-app-registry](./packages/hadron-app-registry/README.md) documentation. -If you want to see your changes applied in Compass, you might need to rebuild plugins that you changed with the `compile` command. Instead of manually writing out the `scope` you might want to use `lerna --since` filter to rebuild everything since your local or origin `HEAD` of the git history: `npx lerna run compile --stream --since origin/HEAD`. Restarting or hard-reloading (Shift+CMD+R) Compass after compilation is finished should apply your changes. +To run npm scripts inside specific workspaces in the monorepo you can use either `lerna --scope` or `npm --workspace` command line arguments. As an example, to run all tests in one plugin that you are working on such as the `compass-aggregations` plugin, you can run `npm run test --workspace packages/compass-aggregation` or `lerna run test --scope @mongodb-js/compass-aggregations` commands + +When running the application locally and changing any code in the monorepo, webpack will take care of automatically rebuilding the modules. In some cases, like changing styles or React component code, webpack might be able to hot-reload the code, but in most cases a page refresh is required to see the changes. In addition to running lerna commands directly, there are a few convenient npm scripts for working with packages: diff --git a/README.md b/README.md index 7c07a9c98eb..2ed50b06f54 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Is there anything else you’d like to see in Compass? Let us know by submitting - [**@mongodb-js/compass-components**](packages/compass-components): React Components used in Compass - [**@mongodb-js/compass-connection-import-export**](packages/compass-connection-import-export): UI for Compass connection import/export - [**@mongodb-js/compass-connections**](packages/compass-connections): Manage your MongoDB connections and connect in Compass -- [**@mongodb-js/compass-databases-navigation**](packages/compass-databases-navigation): Databases and collections sidebar navigation tree +- [**@mongodb-js/compass-connections-navigation**](packages/compass-connections-navigation): Databases and collections sidebar navigation tree - [**@mongodb-js/compass-editor**](packages/compass-editor): Reusable Compass editor component based on ace-editor with MongoDB-specific ace modes, themes, and autocompleters - [**@mongodb-js/compass-generative-ai**](packages/compass-generative-ai): Shared components and helpers for the generative ai aspects of Compass - [**@mongodb-js/compass-logging**](packages/compass-logging): Shared helpers for logging in Compass packages diff --git a/THIRD-PARTY-NOTICES.md b/THIRD-PARTY-NOTICES.md index 16f7a140976..1a333b45210 100644 --- a/THIRD-PARTY-NOTICES.md +++ b/THIRD-PARTY-NOTICES.md @@ -1,5 +1,5 @@ The following third-party software is used by and included in **Mongodb Compass**. -This document was automatically generated on Thu Apr 04 2024. +This document was automatically generated on Tue Apr 30 2024. ## List of dependencies @@ -249,7 +249,7 @@ This document was automatically generated on Thu Apr 04 2024. | **[ejson-shell-parser](#88e1a447c02d9ef3274034c023a5ebcec8188381b1bd951de7c57c140f7f3d4b)** | 2.0.1 | MIT | | **[electron-dl](#e97e034c7b93c63e7a433d75f6f1de3e0668764225ebbd61dbde8d1b55d6f3b7)** | 3.5.0 | MIT | | **[electron-squirrel-startup](#dcda22e402581a033ec2a017d6d05c094bf3173c1b03ae0471b2ce9078d3f601)** | 1.0.0 | Apache-2.0 | -| **[electron](#ad35eed0d5dc019b2a1b78fbd7a5cc660fa2de2ca7810045df9fdc1866366e7a)** | 28.2.10 | MIT | +| **[electron](#4e183d4b327e0da0de21843054b6fb7903970cc998c3b84a5ef79d7692e563c7)** | 29.3.1 | MIT | | **[encodeurl](#b89152db475e86531e570f87b45d8a51aa5e5d87d4cc3b960cee7b8febf1d26a)** | 1.0.2 | MIT | | **[end-of-stream](#fadc10994f5fa767d06fb25cfff35fb17a895daf3bc3477c782907668ed16563)** | 1.4.4 | MIT | | **[ensure-error](#3b1eba5276d89414cef21a1007e85c4f1d6749bf57b300e082ab23975a41dbc9)** | 3.0.1 | MIT | @@ -449,7 +449,7 @@ This document was automatically generated on Thu Apr 04 2024. | **[ms](#2083576c5af8054927640b4788059806d07e250a26066c9ccb2d928394fb9226)** | 2.1.3 | MIT | | **[napi-build-utils](#26912b5ff7632f262d64273f99cd1a869376c5c378960e24501585e35b31054a)** | 1.0.2 | MIT | | **[negotiator](#e3856213d8f0a7d28cd4166e53ec7e2c019cb7becf4a8535097bac28d21e8579)** | 0.6.3 | MIT | -| **[node-abi](#20fad7d445931097574f4af1495e728fb65673f1571f4d81ae4f01e6ede407c4)** | 3.57.0 | MIT | +| **[node-abi](#6decee1d1b9007201af4806e0ecb5bf1e8bc345d58c7bdf99c4c2a07f88e2f6e)** | 3.59.0 | MIT | | **[node-addon-api](#af9f7588524ca4e68f4efe7b24aea46d9c8004263b1d7cf3b558f86d87a163e8)** | 4.3.0 | MIT | | **[node-fetch](#364527ef1b51cc6ac34872b931049c9e25b5014f9b40e3898c84e1a830e21720)** | 2.6.7 | MIT | | **[node-fetch](#23d7d5a419e9a25e6384dee4aa24f7162544418f0cdc2d92e94e2cf924507b8c)** | 2.7.0 | MIT | @@ -582,7 +582,6 @@ This document was automatically generated on Thu Apr 04 2024. | **[use-sync-external-store](#f7d4a0ea4ee9d814fb264db8103d6ad3a831aa79bf7f3ea70bfa182f8a930f0a)** | 1.2.0 | MIT | | **[util-deprecate](#a1bd80d6a50b36e34032c402c5204d6276747d8212b68b164a9e3f895b90c2d6)** | 1.0.2 | MIT | | **[utils-merge](#daf17cb7acc6dd4694e84d0920d7b32dba2be0fcf114309bfce8538812e7c458)** | 1.0.1 | MIT | -| **[uuid](#a34e7819c122fe308fef4f0c9e534d305065097a743da82251b903f2761b2d26)** | 8.3.2 | MIT | | **[uuid](#8e5d6b0bb24ea0188cd3a88b1f790f104e774bb8ed04c0dac0db7cfe2911227e)** | 9.0.1 | MIT | | **[vary](#d308bd3935a6f29310b20de016cdb7b3de3aa40a7d4c3365b96e35d2c248d74a)** | 1.1.2 | MIT | | **[w3c-keyname](#160316b2bf7a19e2cc0418d5ef94b5a999f9092fee3023ac9b2c7d76d2934f5b)** | 2.2.6 | MIT | @@ -20880,9 +20879,9 @@ License files: See the License for the specific language governing permissions and limitations under the License. - + -### [electron](https://www.npmjs.com/package/electron) (version 28.2.10) +### [electron](https://www.npmjs.com/package/electron) (version 29.3.1) License tags: MIT @@ -29884,9 +29883,9 @@ License files: TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - + -### [node-abi](https://www.npmjs.com/package/node-abi) (version 3.57.0) +### [node-abi](https://www.npmjs.com/package/node-abi) (version 3.59.0) License tags: MIT @@ -40659,26 +40658,6 @@ License files: IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - -### [uuid](https://www.npmjs.com/package/uuid) (version 8.3.2) - -License tags: MIT - -License files: - -- LICENSE.md: - - The MIT License (MIT) - - Copyright (c) 2010-2020 Robert Kieffer and other contributors - - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ### [uuid](https://www.npmjs.com/package/uuid) (version 9.0.1) diff --git a/configs/eslint-config-compass/package.json b/configs/eslint-config-compass/package.json index 5d6756be00a..d475dd143cf 100644 --- a/configs/eslint-config-compass/package.json +++ b/configs/eslint-config-compass/package.json @@ -1,6 +1,6 @@ { "name": "@mongodb-js/eslint-config-compass", - "version": "1.0.17", + "version": "1.1.1", "description": "Shared Compass eslint configuration", "license": "SSPL", "main": "index.js", @@ -16,7 +16,7 @@ "@babel/core": "^7.21.4", "@babel/eslint-parser": "^7.14.3", "@mongodb-js/eslint-config-devtools": "^0.9.9", - "@mongodb-js/eslint-plugin-compass": "^1.0.15", + "@mongodb-js/eslint-plugin-compass": "^1.0.17", "@typescript-eslint/eslint-plugin": "^5.59.0", "@typescript-eslint/parser": "^5.59.0", "eslint-config-prettier": "^8.3.0", diff --git a/configs/eslint-plugin-compass/.eslintrc.js b/configs/eslint-plugin-compass/.eslintrc.js index e4cf824b6ac..9c3ab95632f 100644 --- a/configs/eslint-plugin-compass/.eslintrc.js +++ b/configs/eslint-plugin-compass/.eslintrc.js @@ -1,3 +1,4 @@ +'use strict'; module.exports = { root: true, extends: ['@mongodb-js/eslint-config-compass'], diff --git a/configs/eslint-plugin-compass/.mocharc.js b/configs/eslint-plugin-compass/.mocharc.js index d78ea78f622..9b8e94ed7f9 100644 --- a/configs/eslint-plugin-compass/.mocharc.js +++ b/configs/eslint-plugin-compass/.mocharc.js @@ -1,3 +1,4 @@ +'use strict'; module.exports = { ...require('@mongodb-js/mocha-config-compass'), spec: ['rules/**/*.test.js'], diff --git a/configs/eslint-plugin-compass/index.js b/configs/eslint-plugin-compass/index.js index 772c80654b2..c267f46ea2c 100644 --- a/configs/eslint-plugin-compass/index.js +++ b/configs/eslint-plugin-compass/index.js @@ -1,3 +1,4 @@ +'use strict'; module.exports = { rules: { 'no-leafygreen-outside-compass-components': require('./rules/no-leafygreen-outside-compass-components'), diff --git a/configs/eslint-plugin-compass/package.json b/configs/eslint-plugin-compass/package.json index 14cc9d7a5ce..100a4a45be4 100644 --- a/configs/eslint-plugin-compass/package.json +++ b/configs/eslint-plugin-compass/package.json @@ -13,7 +13,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "1.0.15", + "version": "1.0.17", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -37,8 +37,8 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "devDependencies": { - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", "depcheck": "^1.4.1", "eslint": "^7.25.0", "mocha": "^10.2.0", diff --git a/configs/eslint-plugin-compass/rules/no-leafygreen-outside-compass-components.js b/configs/eslint-plugin-compass/rules/no-leafygreen-outside-compass-components.js index 1dec1a0a305..8e10403742e 100644 --- a/configs/eslint-plugin-compass/rules/no-leafygreen-outside-compass-components.js +++ b/configs/eslint-plugin-compass/rules/no-leafygreen-outside-compass-components.js @@ -1,3 +1,4 @@ +'use strict'; const path = require('path'); /** diff --git a/configs/eslint-plugin-compass/rules/no-leafygreen-outside-compass-components.test.js b/configs/eslint-plugin-compass/rules/no-leafygreen-outside-compass-components.test.js index 064c1273f14..1f946503a87 100644 --- a/configs/eslint-plugin-compass/rules/no-leafygreen-outside-compass-components.test.js +++ b/configs/eslint-plugin-compass/rules/no-leafygreen-outside-compass-components.test.js @@ -1,3 +1,4 @@ +'use strict'; const { RuleTester } = require('eslint'); const path = require('path'); const rule = require('./no-leafygreen-outside-compass-components'); diff --git a/configs/eslint-plugin-compass/rules/unique-mongodb-log-id.js b/configs/eslint-plugin-compass/rules/unique-mongodb-log-id.js index 098de5f2ddc..e781891d590 100644 --- a/configs/eslint-plugin-compass/rules/unique-mongodb-log-id.js +++ b/configs/eslint-plugin-compass/rules/unique-mongodb-log-id.js @@ -1,3 +1,4 @@ +'use strict'; const path = require('path'); const { spawnSync } = require('child_process'); diff --git a/configs/eslint-plugin-compass/rules/unique-mongodb-log-id.test.js b/configs/eslint-plugin-compass/rules/unique-mongodb-log-id.test.js index 04871444c32..2f83e995686 100644 --- a/configs/eslint-plugin-compass/rules/unique-mongodb-log-id.test.js +++ b/configs/eslint-plugin-compass/rules/unique-mongodb-log-id.test.js @@ -1,3 +1,4 @@ +'use strict'; const { RuleTester } = require('eslint'); const path = require('path'); const rule = require('./unique-mongodb-log-id'); diff --git a/configs/eslint-plugin-compass/test/__fixtures__/index.js b/configs/eslint-plugin-compass/test/__fixtures__/index.js index 854acf9eedb..b0530aed857 100644 --- a/configs/eslint-plugin-compass/test/__fixtures__/index.js +++ b/configs/eslint-plugin-compass/test/__fixtures__/index.js @@ -1,3 +1,4 @@ +'use strict'; /* eslint-disable @mongodb-js/compass/unique-mongodb-log-id */ /* global mongoLogId */ mongoLogId(1); diff --git a/configs/mocha-config-compass/package.json b/configs/mocha-config-compass/package.json index 4b4fbafbc0d..a0e3405a227 100644 --- a/configs/mocha-config-compass/package.json +++ b/configs/mocha-config-compass/package.json @@ -1,11 +1,11 @@ { "name": "@mongodb-js/mocha-config-compass", - "version": "1.3.7", + "version": "1.3.9", "description": "Shared mocha mocha configuration for Compass packages", "license": "SSPL", "main": "index.js", "devDependencies": { - "@mongodb-js/prettier-config-compass": "^1.0.1", + "@mongodb-js/prettier-config-compass": "^1.0.2", "prettier": "^2.7.1" }, "scripts": { diff --git a/configs/mocha-config-compass/register/compass-preferences-register.js b/configs/mocha-config-compass/register/compass-preferences-register.js index 3e06cceb435..3a7eb4ed5dd 100644 --- a/configs/mocha-config-compass/register/compass-preferences-register.js +++ b/configs/mocha-config-compass/register/compass-preferences-register.js @@ -1,3 +1,4 @@ +'use strict'; let setupPreferencesPromise; module.exports = { diff --git a/configs/mocha-config-compass/register/jsdom-extra-mocks-register.js b/configs/mocha-config-compass/register/jsdom-extra-mocks-register.js index c8510f9d77d..847562e6387 100644 --- a/configs/mocha-config-compass/register/jsdom-extra-mocks-register.js +++ b/configs/mocha-config-compass/register/jsdom-extra-mocks-register.js @@ -1,3 +1,4 @@ +'use strict'; // Not implemented in jsdom if (!window.matchMedia) { window.matchMedia = globalThis.matchMedia = (media) => { diff --git a/configs/mocha-config-compass/register/leaflet-register.js b/configs/mocha-config-compass/register/leaflet-register.js index 6b36bc11585..e7792981330 100644 --- a/configs/mocha-config-compass/register/leaflet-register.js +++ b/configs/mocha-config-compass/register/leaflet-register.js @@ -1,3 +1,4 @@ +'use strict'; // Proxy window.L between window and globalThis for leaflet/leaflet-draw compatibility if (typeof window !== undefined && window !== globalThis) { Object.defineProperty(window, 'L', { diff --git a/configs/mocha-config-compass/register/node-env-register.js b/configs/mocha-config-compass/register/node-env-register.js index f7534b31d99..206645f9feb 100644 --- a/configs/mocha-config-compass/register/node-env-register.js +++ b/configs/mocha-config-compass/register/node-env-register.js @@ -1 +1,2 @@ +'use strict'; process.env.NODE_ENV = 'test'; diff --git a/configs/mocha-config-compass/reporter.js b/configs/mocha-config-compass/reporter.js index 7f999d81581..9fa85944f5a 100644 --- a/configs/mocha-config-compass/reporter.js +++ b/configs/mocha-config-compass/reporter.js @@ -1,3 +1,4 @@ +'use strict'; const Mocha = require('mocha'); const fs = require('fs'); const path = require('path'); diff --git a/configs/prettier-config-compass/index.js b/configs/prettier-config-compass/index.js index 4a20d7650ae..77700680e68 100644 --- a/configs/prettier-config-compass/index.js +++ b/configs/prettier-config-compass/index.js @@ -1,3 +1,4 @@ +'use strict'; module.exports = { ...require('@mongodb-js/prettier-config-devtools'), }; diff --git a/configs/prettier-config-compass/package.json b/configs/prettier-config-compass/package.json index 8043838769c..9e610c806c0 100644 --- a/configs/prettier-config-compass/package.json +++ b/configs/prettier-config-compass/package.json @@ -1,6 +1,6 @@ { "name": "@mongodb-js/prettier-config-compass", - "version": "1.0.1", + "version": "1.0.2", "description": "Shared Compass prettier configuration", "license": "SSPL", "main": "index.js", diff --git a/configs/tsconfig-compass/package.json b/configs/tsconfig-compass/package.json index 986f937345a..d3ab82d9fc8 100644 --- a/configs/tsconfig-compass/package.json +++ b/configs/tsconfig-compass/package.json @@ -1,6 +1,6 @@ { "name": "@mongodb-js/tsconfig-compass", - "version": "1.0.3", + "version": "1.0.4", "description": "Shared Compass Typescript configuration", "license": "SSPL", "files": [ @@ -11,7 +11,7 @@ "typescript": "^5.0.4" }, "devDependencies": { - "@mongodb-js/prettier-config-compass": "^1.0.1", + "@mongodb-js/prettier-config-compass": "^1.0.2", "prettier": "^2.7.1" }, "dependencies": { diff --git a/configs/webpack-config-compass/.eslintrc.js b/configs/webpack-config-compass/.eslintrc.js index e4cf824b6ac..9c3ab95632f 100644 --- a/configs/webpack-config-compass/.eslintrc.js +++ b/configs/webpack-config-compass/.eslintrc.js @@ -1,3 +1,4 @@ +'use strict'; module.exports = { root: true, extends: ['@mongodb-js/eslint-config-compass'], diff --git a/configs/webpack-config-compass/bin/webpack.js b/configs/webpack-config-compass/bin/webpack.js index 2904da52550..9b18304317c 100755 --- a/configs/webpack-config-compass/bin/webpack.js +++ b/configs/webpack-config-compass/bin/webpack.js @@ -1,4 +1,5 @@ #!/usr/bin/env node +'use strict'; // XXX: This mimics (simplified) webpack behavior, but makes sure that we are // always resolving webpack-cli relative to the config. It is required to do, to diff --git a/configs/webpack-config-compass/package.json b/configs/webpack-config-compass/package.json index c6f48b61974..fa73b99e2ea 100644 --- a/configs/webpack-config-compass/package.json +++ b/configs/webpack-config-compass/package.json @@ -13,7 +13,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "1.3.5", + "version": "1.3.7", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -45,9 +45,9 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/cli-progress": "^3.9.2", "@types/html-webpack-plugin": "^3.2.6", "@types/mini-css-extract-plugin": "^2.3.0", @@ -74,7 +74,7 @@ "cli-progress": "^3.9.1", "core-js": "^3.17.3", "css-loader": "^4.3.0", - "electron": "^28.2.10", + "electron": "^29.3.1", "html-webpack-plugin": "^5.3.2", "less-loader": "^10.0.1", "mini-css-extract-plugin": "^2.3.0", diff --git a/package-lock.json b/package-lock.json index 76c42b85b40..a9836ec6cc1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,13 +29,13 @@ }, "configs/eslint-config-compass": { "name": "@mongodb-js/eslint-config-compass", - "version": "1.0.17", + "version": "1.1.1", "license": "SSPL", "dependencies": { "@babel/core": "^7.21.4", "@babel/eslint-parser": "^7.14.3", "@mongodb-js/eslint-config-devtools": "^0.9.9", - "@mongodb-js/eslint-plugin-compass": "^1.0.15", + "@mongodb-js/eslint-plugin-compass": "^1.0.17", "@typescript-eslint/eslint-plugin": "^5.59.0", "@typescript-eslint/parser": "^5.59.0", "eslint-config-prettier": "^8.3.0", @@ -102,11 +102,11 @@ }, "configs/eslint-plugin-compass": { "name": "@mongodb-js/eslint-plugin-compass", - "version": "1.0.15", + "version": "1.0.17", "license": "SSPL", "devDependencies": { - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", "depcheck": "^1.4.1", "eslint": "^7.25.0", "mocha": "^10.2.0", @@ -116,7 +116,7 @@ }, "configs/mocha-config-compass": { "name": "@mongodb-js/mocha-config-compass", - "version": "1.3.7", + "version": "1.3.9", "license": "SSPL", "dependencies": { "@electron/remote": "^2.1.2", @@ -135,13 +135,13 @@ "why-is-node-running": "^2.2.2" }, "devDependencies": { - "@mongodb-js/prettier-config-compass": "^1.0.1", + "@mongodb-js/prettier-config-compass": "^1.0.2", "prettier": "^2.7.1" } }, "configs/prettier-config-compass": { "name": "@mongodb-js/prettier-config-compass", - "version": "1.0.1", + "version": "1.0.2", "license": "SSPL", "devDependencies": { "@mongodb-js/prettier-config-devtools": "^1.0.1", @@ -153,13 +153,13 @@ }, "configs/tsconfig-compass": { "name": "@mongodb-js/tsconfig-compass", - "version": "1.0.3", + "version": "1.0.4", "license": "SSPL", "dependencies": { "@mongodb-js/tsconfig-devtools": "^1.0.0" }, "devDependencies": { - "@mongodb-js/prettier-config-compass": "^1.0.1", + "@mongodb-js/prettier-config-compass": "^1.0.2", "prettier": "^2.7.1" }, "peerDependencies": { @@ -168,7 +168,7 @@ }, "configs/webpack-config-compass": { "name": "@mongodb-js/webpack-config-compass", - "version": "1.3.5", + "version": "1.3.7", "license": "SSPL", "dependencies": { "@babel/core": "^7.21.4", @@ -187,7 +187,7 @@ "cli-progress": "^3.9.1", "core-js": "^3.17.3", "css-loader": "^4.3.0", - "electron": "^28.2.10", + "electron": "^29.3.1", "html-webpack-plugin": "^5.3.2", "less-loader": "^10.0.1", "mini-css-extract-plugin": "^2.3.0", @@ -208,9 +208,9 @@ "webpack-compass": "bin/webpack.js" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/cli-progress": "^3.9.2", "@types/html-webpack-plugin": "^3.2.6", "@types/mini-css-extract-plugin": "^2.3.0", @@ -4649,16 +4649,6 @@ "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==" }, - "node_modules/@gribnoysup/mongodb-browser": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@gribnoysup/mongodb-browser/-/mongodb-browser-1.3.0.tgz", - "integrity": "sha512-kvX0Tk0jNT0y4OZ7SUbe0yMC7A00PDOHDMLz0ScUPDmLsPCcPU7mbE3ESUgH98uidCxHGpTkg1LI+dSGOd6gZA==", - "dev": true, - "peerDependencies": { - "bson": "^6.2.0", - "mongodb": "^6.3.0" - } - }, "node_modules/@hapi/hoek": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.2.0.tgz", @@ -5651,6 +5641,41 @@ "@leafygreen-ui/leafygreen-provider": "^3.1.11" } }, + "node_modules/@leichtgewicht/base64-codec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@leichtgewicht/base64-codec/-/base64-codec-1.0.0.tgz", + "integrity": "sha512-0cgP4lRBzh3F4tlpTfs7F+PJyBN8j5yUC9KrQFWp/bREswgzZVHE8T1rNyRDWgvALwwpPtnJDQfqWUmxI33Epg==", + "dev": true + }, + "node_modules/@leichtgewicht/dns-packet": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@leichtgewicht/dns-packet/-/dns-packet-6.0.3.tgz", + "integrity": "sha512-qmVHhFBFiBvPsk/wJ/EdoWHb+tGkzY4haybmDPukhF6w0+8wpEbrHTIRE9LzeUu2P0bAbmrK8WOXt5V5QN6jQg==", + "dev": true, + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.4", + "bytes.js": "^0.0.2", + "utf8-bytes": "^0.0.1", + "utf8-codec": "^1.0.0", + "utf8-length": "^0.0.1", + "utf8-string-bytes": "^1.0.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@leichtgewicht/dns-socket": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@leichtgewicht/dns-socket/-/dns-socket-5.0.0.tgz", + "integrity": "sha512-Sbrn/OG0HTTPGSkwIDCHy8/tUI6UglIzFsMNjzZn/Na1/i5owSm6rVi9CfKNNjRcUlYEzICELYW6EoZdjwVY2A==", + "dev": true, + "dependencies": { + "@leichtgewicht/dns-packet": "^6.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", @@ -7232,6 +7257,10 @@ "resolved": "packages/compass-connections", "link": true }, + "node_modules/@mongodb-js/compass-connections-navigation": { + "resolved": "packages/compass-connections-navigation", + "link": true + }, "node_modules/@mongodb-js/compass-crud": { "resolved": "packages/compass-crud", "link": true @@ -7240,10 +7269,6 @@ "resolved": "packages/databases-collections", "link": true }, - "node_modules/@mongodb-js/compass-databases-navigation": { - "resolved": "packages/compass-databases-navigation", - "link": true - }, "node_modules/@mongodb-js/compass-editor": { "resolved": "packages/compass-editor", "link": true @@ -12635,18 +12660,18 @@ } }, "node_modules/@testing-library/dom": { - "version": "8.11.1", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.11.1.tgz", - "integrity": "sha512-3KQDyx9r0RKYailW2MiYrSSKEfH0GTkI51UGEvJenvcoDoeRYs0PZpi2SXqtnMClQvCqdtTTpOfFETDTVADpAg==", + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.1.tgz", + "integrity": "sha512-/DiOQ5xBxgdYRC8LNk7U+RWat0S3qRLeIw3ZIkMQ9kkVlRmwD/Eg8k8CqIpD6GW7u20JIUOfMKbxtiLutpjQ4g==", "dev": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", - "@types/aria-query": "^4.2.0", - "aria-query": "^5.0.0", + "@types/aria-query": "^5.0.1", + "aria-query": "5.1.3", "chalk": "^4.1.0", "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.4.4", + "lz-string": "^1.5.0", "pretty-format": "^27.0.2" }, "engines": { @@ -12708,6 +12733,15 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/@testing-library/dom/node_modules/aria-query": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dev": true, + "dependencies": { + "deep-equal": "^2.0.5" + } + }, "node_modules/@testing-library/dom/node_modules/pretty-format": { "version": "27.3.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.3.1.tgz", @@ -12867,9 +12901,9 @@ } }, "node_modules/@types/aria-query": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz", - "integrity": "sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true }, "node_modules/@types/babel__core": { @@ -13111,6 +13145,15 @@ "@types/serve-static": "*" } }, + "node_modules/@types/express-http-proxy": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/@types/express-http-proxy/-/express-http-proxy-1.6.6.tgz", + "integrity": "sha512-J8ZqHG76rq1UB716IZ3RCmUhg406pbWxsM3oFCFccl5xlWUPzoR4if6Og/cE4juK8emH0H9quZa5ltn6ZdmQJg==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/express-serve-static-core": { "version": "4.17.28", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz", @@ -13627,12 +13670,6 @@ "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" }, - "node_modules/@types/uuid": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.1.tgz", - "integrity": "sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg==", - "dev": true - }, "node_modules/@types/webidl-conversions": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz", @@ -15739,12 +15776,12 @@ "integrity": "sha512-F2+Hkm9xFaRg+GkaNnbwXNDV5O6pnCFEmqyhvfC/Ic5LbgOWjJh3L+mN/s91rxVL3znE7DYVpW0GJFT+4YBgWw==" }, "node_modules/aria-query": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", - "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", "dev": true, "dependencies": { - "deep-equal": "^2.0.5" + "dequal": "^2.0.3" } }, "node_modules/array-buffer-byte-length": { @@ -15849,19 +15886,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array.prototype.find": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.1.1.tgz", - "integrity": "sha512-mi+MYNJYLTx2eNYy+Yh6raoQacCsNeeMUaspFPh9Y141lFSsWxxB8V9mM2ye+eqiRs917J6/pJ4M9ZPzenWckA==", - "dev": true, - "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/array.prototype.flat": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz", @@ -15939,6 +15963,23 @@ "safer-buffer": "~2.1.0" } }, + "node_modules/asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "dev": true, + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/asn1.js/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, "node_modules/assert": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", @@ -16162,73 +16203,6 @@ "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==", "dev": true }, - "node_modules/babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "dev": true, - "dependencies": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - } - }, - "node_modules/babel-code-frame/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/babel-code-frame/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/babel-code-frame/node_modules/js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true - }, - "node_modules/babel-code-frame/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/babel-eslint": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-7.2.3.tgz", - "integrity": "sha512-i2yKOhjgwUbUrJ8oJm6QqRzltIoFahGNPZ0HF22lUN4H1DW03JQyJm7WSv+I1LURQWjDNhVqFo04acYa07rhOQ==", - "deprecated": "babel-eslint is now @babel/eslint-parser. This package will no longer receive updates.", - "dev": true, - "dependencies": { - "babel-code-frame": "^6.22.0", - "babel-traverse": "^6.23.1", - "babel-types": "^6.23.0", - "babylon": "^6.17.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/babel-loader": { "version": "7.1.5", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-7.1.5.tgz", @@ -16373,15 +16347,6 @@ "node": ">=4" } }, - "node_modules/babel-messages": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", - "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", - "dev": true, - "dependencies": { - "babel-runtime": "^6.22.0" - } - }, "node_modules/babel-plugin-istanbul": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-5.2.0.tgz", @@ -16569,101 +16534,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "dependencies": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - } - }, - "node_modules/babel-runtime/node_modules/core-js": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", - "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", - "deprecated": "core-js@<3.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Please, upgrade your dependencies to the actual version of core-js.", - "dev": true, - "hasInstallScript": true - }, - "node_modules/babel-runtime/node_modules/regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true - }, - "node_modules/babel-traverse": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", - "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", - "dev": true, - "dependencies": { - "babel-code-frame": "^6.26.0", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "debug": "^2.6.8", - "globals": "^9.18.0", - "invariant": "^2.2.2", - "lodash": "^4.17.4" - } - }, - "node_modules/babel-traverse/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/babel-traverse/node_modules/globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/babel-traverse/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", - "dev": true, - "dependencies": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" - } - }, - "node_modules/babel-types/node_modules/to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", - "dev": true, - "bin": { - "babylon": "bin/babylon.js" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -16853,6 +16723,12 @@ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, + "node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true + }, "node_modules/body-parser": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", @@ -16981,12 +16857,128 @@ "node": ">=8" } }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "dev": true + }, + "node_modules/browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", + "dev": true + }, "node_modules/browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, + "node_modules/browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "dependencies": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "dependencies": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "node_modules/browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/browserify-rsa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", + "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", + "dev": true, + "dependencies": { + "bn.js": "^5.0.0", + "randombytes": "^2.0.1" + } + }, + "node_modules/browserify-sign": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.3.tgz", + "integrity": "sha512-JWCZW6SKhfhjJxO8Tyiiy+XYB7cqd2S5/+WeYHsKdNKFlCBhKbblba1A/HN/90YwtxKc8tCErjffZl++UNmGiw==", + "dev": true, + "dependencies": { + "bn.js": "^5.2.1", + "browserify-rsa": "^4.1.0", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.5", + "hash-base": "~3.0", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.7", + "readable-stream": "^2.3.8", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/browserify-sign/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/browserify-sign/node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/browserify-sign/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/browserify-sign/node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, "node_modules/browserslist": { "version": "4.23.0", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", @@ -17102,6 +17094,12 @@ "node": ">=0.10" } }, + "node_modules/buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", + "dev": true + }, "node_modules/buffers": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", @@ -17156,6 +17154,12 @@ "node": ">= 0.8" } }, + "node_modules/bytes.js": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/bytes.js/-/bytes.js-0.0.2.tgz", + "integrity": "sha512-KrLm4hv5Qs9w6b0U7h1bCdqxrsf+e9QMsfHeyQFzAz94x/5Aqa+FTEUSNBtt5d2VuV3Hfiea3c4ti74RZDDYkg==", + "dev": true + }, "node_modules/cacache": { "version": "15.2.0", "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.2.0.tgz", @@ -17599,12 +17603,15 @@ "node": ">=8" } }, - "node_modules/circular-json": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", - "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", - "deprecated": "CircularJSON is in maintenance only, flatted is its successor.", - "dev": true + "node_modules/cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } }, "node_modules/clean-css": { "version": "4.2.3", @@ -17784,6 +17791,21 @@ "node": ">=0.10.0" } }, + "node_modules/clone-regexp": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-3.0.0.tgz", + "integrity": "sha512-ujdnoq2Kxb8s3ItNBtnYeXdm07FcU0u8ARAT1lQ2YdMwQC+cdiXX8KoqMVuglztILivceTtp4ivqGSmEmhBUJw==", + "dev": true, + "dependencies": { + "is-regexp": "^3.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/clone-response": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", @@ -17839,16 +17861,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true, - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, "node_modules/code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", @@ -18344,6 +18356,18 @@ "typedarray": "^0.0.6" } }, + "node_modules/convert-hrtime": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/convert-hrtime/-/convert-hrtime-5.0.0.tgz", + "integrity": "sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/convert-source-map": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", @@ -18489,6 +18513,49 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" }, + "node_modules/create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + } + }, + "node_modules/create-ecdh/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -18630,6 +18697,28 @@ "node": "*" } }, + "node_modules/crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "dependencies": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + }, + "engines": { + "node": "*" + } + }, "node_modules/css-blank-pseudo": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz", @@ -19789,6 +19878,25 @@ "integrity": "sha1-UYZnt2kUYKXn4KNBvnbrfOgJAYQ=", "dev": true }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/des.js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", + "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -19840,6 +19948,23 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "node_modules/diffie-hellman/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, "node_modules/difflib": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/difflib/-/difflib-0.2.4.tgz", @@ -19920,6 +20045,22 @@ "node": ">=6" } }, + "node_modules/dns-query": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/dns-query/-/dns-query-0.11.2.tgz", + "integrity": "sha512-zF8qxQpqCB467o4A63DLpQClo77H642JEKMx0Ra9GFww7Rx0234Fo8NoG0LBoSBZxamWkXfLxhzDG19bTBHvXQ==", + "dev": true, + "dependencies": { + "@leichtgewicht/base64-codec": "^1.0.0", + "@leichtgewicht/dns-packet": "^6.0.2", + "@leichtgewicht/dns-socket": "^5.0.0", + "@leichtgewicht/ip-codec": "^2.0.4", + "utf8-codec": "^1.0.0" + }, + "bin": { + "dns-query": "bin/dns-query" + } + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -20547,13 +20688,13 @@ } }, "node_modules/electron": { - "version": "28.2.10", - "resolved": "https://registry.npmjs.org/electron/-/electron-28.2.10.tgz", - "integrity": "sha512-0rGBJNogcl2FIRxGRUv9zuMaBP78nSBJW+Bd1U7OGeg8IEkSIbHOhfn71XoGxgbOUSCEXjjyftq4mtAAVbUsZQ==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/electron/-/electron-29.3.1.tgz", + "integrity": "sha512-auge1/6RVqgUd6TgIq88wKdUCJi2cjESi3jy7d+6X4JzvBGprKBqMJ8JSSFpu/Px1YJrFUKAxfy6SC+TQf1uLw==", "hasInstallScript": true, "dependencies": { "@electron/get": "^2.0.0", - "@types/node": "^18.11.18", + "@types/node": "^20.9.0", "extract-zip": "^2.0.1" }, "bin": { @@ -21033,11 +21174,6 @@ "url": "https://github.com/sindresorhus/is?sponsor=1" } }, - "node_modules/electron/node_modules/@types/node": { - "version": "18.18.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.3.tgz", - "integrity": "sha512-0OVfGupTl3NBFr8+iXpfZ8NR7jfFO+P1Q+IO/q0wbo02wYkP5gy36phojeYWpLQ6WAMjl+VfmqUk2YbUfp0irA==" - }, "node_modules/electron/node_modules/cacheable-lookup": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", @@ -21132,6 +21268,27 @@ "node": ">= 4.0.0" } }, + "node_modules/elliptic": { + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.5.tgz", + "integrity": "sha512-7EjbcmUm17NQFu4Pmgmq2olYMj8nwMnpcddByChSUjArp8F5DQWcIcpriwO4ZToLNAJig0yiyjswfyGNje/ixw==", + "dev": true, + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, "node_modules/email-validator": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/email-validator/-/email-validator-2.0.4.tgz", @@ -21486,73 +21643,10 @@ "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" }, - "node_modules/es6-map": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", - "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", - "dev": true, - "dependencies": { - "d": "1", - "es5-ext": "~0.10.14", - "es6-iterator": "~2.0.1", - "es6-set": "~0.1.5", - "es6-symbol": "~3.1.1", - "event-emitter": "~0.3.5" - } - }, - "node_modules/es6-map/node_modules/d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dev": true, - "dependencies": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, - "node_modules/es6-map/node_modules/type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", - "dev": true - }, - "node_modules/es6-set": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", - "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", - "dev": true, - "dependencies": { - "d": "1", - "es5-ext": "~0.10.14", - "es6-iterator": "~2.0.1", - "es6-symbol": "3.1.1", - "event-emitter": "~0.3.5" - } - }, - "node_modules/es6-set/node_modules/d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dev": true, - "dependencies": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, - "node_modules/es6-set/node_modules/es6-symbol": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", - "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", - "dev": true, - "dependencies": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, - "node_modules/es6-set/node_modules/type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", "dev": true }, "node_modules/es6-symbol": { @@ -21666,49 +21760,6 @@ "node": ">=4.0" } }, - "node_modules/escope": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", - "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", - "dev": true, - "dependencies": { - "es6-map": "^0.1.3", - "es6-weak-map": "^2.0.1", - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/escope/node_modules/d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dev": true, - "dependencies": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, - "node_modules/escope/node_modules/es6-weak-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", - "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", - "dev": true, - "dependencies": { - "d": "1", - "es5-ext": "^0.10.46", - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.1" - } - }, - "node_modules/escope/node_modules/type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", - "dev": true - }, "node_modules/eslint": { "version": "7.32.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", @@ -23202,6 +23253,16 @@ "delegate-events": "^1.1.0" } }, + "node_modules/evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "dependencies": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -23232,15 +23293,6 @@ "node": ">= 0.8.0" } }, - "node_modules/exit-hook": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", - "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/expand-template": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", @@ -23296,6 +23348,29 @@ "node": ">= 0.10.0" } }, + "node_modules/express-http-proxy": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/express-http-proxy/-/express-http-proxy-2.0.0.tgz", + "integrity": "sha512-TXxcPFTWVUMSEmyM6iX2sT/JtmqhqngTq29P+eXTVFdtxZrTmM8THUYK59rUXiln0FfPGvxEpGRnVrgvHksXDw==", + "dev": true, + "dependencies": { + "debug": "^3.0.1", + "es6-promise": "^4.1.1", + "raw-body": "^2.3.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/express-http-proxy/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -24114,6 +24189,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/function-timeout": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/function-timeout/-/function-timeout-0.1.1.tgz", + "integrity": "sha512-0NVVC0TaP7dSTvn1yMiy6d6Q8gifzbvQafO46RtLG/kHJUBNd+pVRGOBoK44wNBvtSPUJRfdVvkFdD3p0xvyZg==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/function.prototype.name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.4.tgz", @@ -24707,7 +24794,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", - "devOptional": true, + "optional": true, "dependencies": { "is-property": "^1.0.2" } @@ -24716,7 +24803,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", - "devOptional": true, + "optional": true, "dependencies": { "is-property": "^1.0.0" } @@ -25668,12 +25755,35 @@ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" }, + "node_modules/hash-base": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha512-EeeoJKjTyt868liAlVmcv2ZsUfGHlE3Q+BICOXcZiwN3osr5Q/zFGYmTJpoIzuaSTAwndFy+GqhEwlU4L3j4Ow==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/hash-sum": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-2.0.0.tgz", "integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==", "dev": true }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, "node_modules/hasha": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", @@ -25744,6 +25854,17 @@ "resolved": "https://registry.npmjs.org/highlightjs-graphql/-/highlightjs-graphql-1.0.2.tgz", "integrity": "sha512-jShTftpKQDwMXc+7OHOpHXRYSweT08EO2YOIcLbwU00e9yuwJMYXGLF1eiDO0aUPeQU4/5EjAh5HtPt3ly7rvg==" }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "dev": true, + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -26587,15 +26708,6 @@ "node": ">= 0.4" } }, - "node_modules/interpret": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/interruptor": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/interruptor/-/interruptor-1.0.1.tgz", @@ -26674,6 +26786,18 @@ "ipaddr.js": "^1.0.1" } }, + "node_modules/ip-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-5.0.0.tgz", + "integrity": "sha512-fOCG6lhoKKakwv+C6KdsOnGvgXnmgfmp0myi3bcNwj3qfwPAxRKWEuFhvEFF7ceYIz6+1jRZ+yguLFAmUNPEfw==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -26905,6 +27029,22 @@ "node": ">=8" } }, + "node_modules/is-ip": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/is-ip/-/is-ip-5.0.1.tgz", + "integrity": "sha512-FCsGHdlrOnZQcp0+XT5a+pYowf33itBalCl+7ovNXC/7o5BhIpG14M3OrpPPdBSIQJCm+0M5+9mO7S9VVTTCFw==", + "dev": true, + "dependencies": { + "ip-regex": "^5.0.0", + "super-regex": "^0.2.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-lambda": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", @@ -26926,13 +27066,13 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==", - "devOptional": true + "optional": true }, "node_modules/is-my-json-valid": { "version": "2.20.5", "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.20.5.tgz", "integrity": "sha512-VTPuvvGQtxvCeghwspQu1rBgjYUT6FGxPlvFKbYuFtgc4ADsX3U5ihZOYN0qyU6u+d4X9xXb0IT5O6QpXKt87A==", - "devOptional": true, + "optional": true, "dependencies": { "generate-function": "^2.0.0", "generate-object-property": "^1.1.0", @@ -27053,7 +27193,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", - "devOptional": true + "optional": true }, "node_modules/is-regex": { "version": "1.1.4", @@ -27070,11 +27210,17 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", - "dev": true + "node_modules/is-regexp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-3.1.0.tgz", + "integrity": "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/is-retry-allowed": { "version": "1.2.0", @@ -27902,15 +28048,6 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, - "node_modules/json-stable-stringify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", - "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", - "dev": true, - "dependencies": { - "jsonify": "~0.0.0" - } - }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -27985,15 +28122,6 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/jsonify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/jsonparse": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", @@ -28006,7 +28134,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.1.0.tgz", "integrity": "sha512-CXcRvMyTlnR53xMcKnuMzfCA5i/nfblTnnr74CZb6C4vG39eu6w51t7nKmU5MfLfbTgGItliNyjO/ciNPDqClg==", - "devOptional": true, + "optional": true, "engines": { "node": ">=0.10.0" } @@ -31006,6 +31134,17 @@ "is-buffer": "~1.1.6" } }, + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, "node_modules/media-type": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-type/-/media-type-0.3.0.tgz", @@ -31269,6 +31408,25 @@ "node": ">=8.6" } }, + "node_modules/miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "dependencies": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "bin": { + "miller-rabin": "bin/miller-rabin" + } + }, + "node_modules/miller-rabin/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, "node_modules/mime": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", @@ -31340,6 +31498,12 @@ "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "dev": true + }, "node_modules/minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -32821,9 +32985,9 @@ } }, "node_modules/node-abi": { - "version": "3.57.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.57.0.tgz", - "integrity": "sha512-Dp+A9JWxRaKuHP35H77I4kCKesDy5HUDEmScia2FyncMTOXASMyg251F5PhFoDA5uqBrDDffiLpbqnrZmNXW+g==", + "version": "3.59.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.59.0.tgz", + "integrity": "sha512-HyyfzvTLCE8b1SX2nWimlra8cibEsypcSu/Az4SXMhWhtuctkwAX7qsEYNjUOIoYtPV884oN3wtYTN+iZKBtvw==", "dependencies": { "semver": "^7.3.5" }, @@ -34492,6 +34656,12 @@ "node": ">=8" } }, + "node_modules/os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==", + "dev": true + }, "node_modules/os-dns-native": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/os-dns-native/-/os-dns-native-1.2.1.tgz", @@ -34520,15 +34690,6 @@ } } }, - "node_modules/os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/os-locale": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", @@ -34929,6 +35090,23 @@ "node": ">=6" } }, + "node_modules/parse-asn1": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.7.tgz", + "integrity": "sha512-CTM5kuWR3sx9IFamcl5ErfPl6ea/N8IYwiJ+vpeB2g+1iknv7zBl5uPwbMbRVznRVbrNY6lGuDoE5b30grmbqg==", + "dev": true, + "dependencies": { + "asn1.js": "^4.10.1", + "browserify-aes": "^1.2.0", + "evp_bytestokey": "^1.0.3", + "hash-base": "~3.0", + "pbkdf2": "^3.1.2", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/parse-author": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/parse-author/-/parse-author-2.0.0.tgz", @@ -35175,6 +35353,22 @@ "node": "*" } }, + "node_modules/pbkdf2": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "dev": true, + "dependencies": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -35379,12 +35573,6 @@ "node": ">=6" } }, - "node_modules/pluralize": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz", - "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=", - "dev": true - }, "node_modules/polished": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/polished/-/polished-4.2.2.tgz", @@ -37118,6 +37306,26 @@ "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" }, + "node_modules/public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/public-encrypt/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -37626,6 +37834,16 @@ "safe-buffer": "^5.1.0" } }, + "node_modules/randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "dependencies": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -38403,47 +38621,6 @@ "node": ">=10" } }, - "node_modules/readline2": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", - "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=", - "dev": true, - "dependencies": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "mute-stream": "0.0.5" - } - }, - "node_modules/readline2/node_modules/is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "dependencies": { - "number-is-nan": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readline2/node_modules/mute-stream": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", - "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=", - "dev": true - }, - "node_modules/rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", - "dev": true, - "dependencies": { - "resolve": "^1.1.6" - }, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -38711,49 +38888,6 @@ "integrity": "sha1-wR6XJ2tluOKSP3Xav1+y7ww4Qbk=", "dev": true }, - "node_modules/require-uncached": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", - "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", - "dev": true, - "dependencies": { - "caller-path": "^0.1.0", - "resolve-from": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-uncached/node_modules/caller-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", - "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", - "dev": true, - "dependencies": { - "callsites": "^0.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-uncached/node_modules/callsites": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", - "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-uncached/node_modules/resolve-from": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", - "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -38926,6 +39060,16 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, "node_modules/roarr": { "version": "2.15.4", "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", @@ -39003,12 +39147,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/rx-lite": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz", - "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=", - "dev": true - }, "node_modules/rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", @@ -39368,6 +39506,19 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -39406,24 +39557,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/shelljs": { - "version": "0.7.8", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz", - "integrity": "sha512-/YF5Uk8hcwi7ima04ppkbA4RaRMdPMBfwAvAf8sufYOxsJRtbdoBsT8vGvlb+799BrlGdYrd+oczIA2eN2JdWA==", - "dev": true, - "dependencies": { - "glob": "^7.0.0", - "interpret": "^1.0.0", - "rechoir": "^0.6.2" - }, - "bin": { - "shjs": "bin/shjs" - }, - "engines": { - "iojs": "*", - "node": ">=0.11.0" - } - }, "node_modules/side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -40820,6 +40953,23 @@ "node": ">= 8.0" } }, + "node_modules/super-regex": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-0.2.0.tgz", + "integrity": "sha512-WZzIx3rC1CvbMDloLsVw0lkZVKJWbrkJ0k1ghKFmcnPrW1+jWbgTkTEWVtD9lMdmI4jZEz40+naBxl1dCUhXXw==", + "dev": true, + "dependencies": { + "clone-regexp": "^3.0.0", + "function-timeout": "^0.1.0", + "time-span": "^5.1.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -41262,6 +41412,21 @@ "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" }, + "node_modules/time-span": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/time-span/-/time-span-5.1.0.tgz", + "integrity": "sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==", + "dev": true, + "dependencies": { + "convert-hrtime": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/timed-out": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", @@ -41270,6 +41435,18 @@ "node": ">=0.10.0" } }, + "node_modules/timers-browserify": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", + "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", + "dev": true, + "dependencies": { + "setimmediate": "^1.0.4" + }, + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/timers-ext": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", @@ -42333,18 +42510,6 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, - "node_modules/user-home": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", - "integrity": "sha512-KMWqdlOcjCYdtIJpicDSFBQ8nFwS2i9sslAd6f4+CBGcU4gist2REnr2fxj2YocvJFxSF3ZOHLYLVZnUxv4BZQ==", - "dev": true, - "dependencies": { - "os-homedir": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/userhome": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/userhome/-/userhome-1.0.0.tgz", @@ -42354,6 +42519,30 @@ "node": ">= 0.8.0" } }, + "node_modules/utf8-bytes": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/utf8-bytes/-/utf8-bytes-0.0.1.tgz", + "integrity": "sha512-GifWmJAx2qAXT+lZLhbkWhBsy7pr6xWHiPWlVToDiELdWgZwt4Ogjf9tlgvKuALzTFR/d+EPQQI9ogJV3957Jg==", + "dev": true + }, + "node_modules/utf8-codec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/utf8-codec/-/utf8-codec-1.0.0.tgz", + "integrity": "sha512-S/QSLezp3qvG4ld5PUfXiH7mCFxLKjSVZRFkB3DOjgwHuJPFDkInAXc/anf7BAbHt/D38ozDzL+QMZ6/7gsI6w==", + "dev": true + }, + "node_modules/utf8-length": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/utf8-length/-/utf8-length-0.0.1.tgz", + "integrity": "sha512-j/XH2ftofBiobnyApxlN/J6j/ixwT89WEjDcjT66d2i0+GIn9RZfzt8lpEXXE4jUe4NsjBSUq70kS2euQ4nnMw==", + "dev": true + }, + "node_modules/utf8-string-bytes": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/utf8-string-bytes/-/utf8-string-bytes-1.0.3.tgz", + "integrity": "sha512-i/I1Omf6lADjVBlwJpQifZOePV15snHny9w04+lc71+3t8PyWuLC/7clyoOSHOBNGXFe2PAGxmTiZ+Z4HWsPyw==", + "dev": true + }, "node_modules/util": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", @@ -43946,19 +44135,19 @@ }, "packages/atlas-service": { "name": "@mongodb-js/atlas-service", - "version": "0.15.1", + "version": "0.17.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-user-data": "^0.1.17", - "@mongodb-js/compass-utils": "^0.6.1", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-user-data": "^0.1.19", + "@mongodb-js/compass-utils": "^0.6.3", "@mongodb-js/devtools-connect": "^2.6.0", "@mongodb-js/oidc-plugin": "^0.4.0", - "compass-preferences-model": "^2.18.1", - "electron": "^28.2.10", - "hadron-app-registry": "^9.1.8", - "hadron-ipc": "^3.2.12", + "compass-preferences-model": "^2.20.0", + "electron": "^29.3.1", + "hadron-app-registry": "^9.1.10", + "hadron-ipc": "^3.2.14", "lodash": "^4.17.21", "node-fetch": "^2.7.0", "react": "^17.0.2", @@ -43967,10 +44156,10 @@ "redux-thunk": "^2.4.2" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", @@ -44032,7 +44221,7 @@ } }, "packages/bson-transpilers": { - "version": "3.0.0", + "version": "3.0.2", "license": "SSPL", "dependencies": { "antlr4": "4.7.2", @@ -44040,645 +44229,26 @@ "js-yaml": "^3.13.1" }, "devDependencies": { + "@mongodb-js/eslint-config-compass": "^1.1.1", "chai": "^4.3.4", "depcheck": "^1.4.1", "eslint": "^7.25.0", - "eslint-config-mongodb-js": "^2.1.0", "mocha": "^10.2.0" } }, - "packages/bson-transpilers/node_modules/acorn": { - "version": "5.7.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", - "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "packages/bson-transpilers/node_modules/acorn-jsx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", - "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", - "dev": true, - "dependencies": { - "acorn": "^3.0.4" - } - }, - "packages/bson-transpilers/node_modules/acorn-jsx/node_modules/acorn": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", - "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "packages/bson-transpilers/node_modules/ajv": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", - "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", - "dev": true, - "dependencies": { - "co": "^4.6.0", - "json-stable-stringify": "^1.0.1" - } - }, - "packages/bson-transpilers/node_modules/ajv-keywords": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", - "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", - "dev": true, - "peerDependencies": { - "ajv": ">=4.10.0" - } - }, - "packages/bson-transpilers/node_modules/ansi-escapes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", - "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "packages/bson-transpilers/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "packages/bson-transpilers/node_modules/cli-cursor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", - "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", - "dev": true, - "dependencies": { - "restore-cursor": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "packages/bson-transpilers/node_modules/cli-width": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", - "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", - "dev": true - }, - "packages/bson-transpilers/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "packages/bson-transpilers/node_modules/eslint-config-mongodb-js": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/eslint-config-mongodb-js/-/eslint-config-mongodb-js-2.3.0.tgz", - "integrity": "sha512-9zxJawyp68GNX63pfqeLV47/ShSyY7Hce3l/XhrD8dihFhygs+5C7lk12ogDePK3OmOer1pREvwgR8q0YvV4Pw==", - "dev": true, - "dependencies": { - "babel-eslint": "^7.1.0", - "eslint": "^3.3.1", - "eslint-plugin-chai-friendly": "^0.4.0", - "eslint-plugin-react": "^6.1.2" - } - }, - "packages/bson-transpilers/node_modules/eslint-config-mongodb-js/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "packages/bson-transpilers/node_modules/eslint-config-mongodb-js/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "packages/bson-transpilers/node_modules/eslint-config-mongodb-js/node_modules/eslint": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-3.19.0.tgz", - "integrity": "sha1-yPxiAcf0DdCJQbh8CFdnOGpnmsw=", - "dev": true, - "dependencies": { - "babel-code-frame": "^6.16.0", - "chalk": "^1.1.3", - "concat-stream": "^1.5.2", - "debug": "^2.1.1", - "doctrine": "^2.0.0", - "escope": "^3.6.0", - "espree": "^3.4.0", - "esquery": "^1.0.0", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "file-entry-cache": "^2.0.0", - "glob": "^7.0.3", - "globals": "^9.14.0", - "ignore": "^3.2.0", - "imurmurhash": "^0.1.4", - "inquirer": "^0.12.0", - "is-my-json-valid": "^2.10.0", - "is-resolvable": "^1.0.0", - "js-yaml": "^3.5.1", - "json-stable-stringify": "^1.0.0", - "levn": "^0.3.0", - "lodash": "^4.0.0", - "mkdirp": "^0.5.0", - "natural-compare": "^1.4.0", - "optionator": "^0.8.2", - "path-is-inside": "^1.0.1", - "pluralize": "^1.2.1", - "progress": "^1.1.8", - "require-uncached": "^1.0.2", - "shelljs": "^0.7.5", - "strip-bom": "^3.0.0", - "strip-json-comments": "~2.0.1", - "table": "^3.7.8", - "text-table": "~0.2.0", - "user-home": "^2.0.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": ">=4" - } - }, - "packages/bson-transpilers/node_modules/eslint-config-mongodb-js/node_modules/eslint-plugin-react": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-6.10.3.tgz", - "integrity": "sha1-xUNb6wZ3ThLH2y9qut3L+QDNP3g=", - "dev": true, - "dependencies": { - "array.prototype.find": "^2.0.1", - "doctrine": "^1.2.2", - "has": "^1.0.1", - "jsx-ast-utils": "^1.3.4", - "object.assign": "^4.0.4" - }, - "engines": { - "node": ">=0.10" - }, - "peerDependencies": { - "eslint": "^2.0.0 || ^3.0.0" - } - }, - "packages/bson-transpilers/node_modules/eslint-config-mongodb-js/node_modules/eslint-plugin-react/node_modules/doctrine": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", - "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", - "dev": true, - "dependencies": { - "esutils": "^2.0.2", - "isarray": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "packages/bson-transpilers/node_modules/eslint-config-mongodb-js/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "packages/bson-transpilers/node_modules/eslint-config-mongodb-js/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "packages/bson-transpilers/node_modules/eslint-config-mongodb-js/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "packages/bson-transpilers/node_modules/espree": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", - "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", - "dev": true, - "dependencies": { - "acorn": "^5.5.0", - "acorn-jsx": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "packages/bson-transpilers/node_modules/figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "dev": true, - "dependencies": { - "escape-string-regexp": "^1.0.5", - "object-assign": "^4.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "packages/bson-transpilers/node_modules/file-entry-cache": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", - "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", - "dev": true, - "dependencies": { - "flat-cache": "^1.2.1", - "object-assign": "^4.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "packages/bson-transpilers/node_modules/flat-cache": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", - "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", - "dev": true, - "dependencies": { - "circular-json": "^0.3.1", - "graceful-fs": "^4.1.2", - "rimraf": "~2.6.2", - "write": "^0.2.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "packages/bson-transpilers/node_modules/globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "packages/bson-transpilers/node_modules/ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", - "dev": true - }, - "packages/bson-transpilers/node_modules/inquirer": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz", - "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=", - "dev": true, - "dependencies": { - "ansi-escapes": "^1.1.0", - "ansi-regex": "^2.0.0", - "chalk": "^1.0.0", - "cli-cursor": "^1.0.1", - "cli-width": "^2.0.0", - "figures": "^1.3.5", - "lodash": "^4.3.0", - "readline2": "^1.0.1", - "run-async": "^0.1.0", - "rx-lite": "^3.1.2", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.0", - "through": "^2.3.6" - } - }, - "packages/bson-transpilers/node_modules/inquirer/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "packages/bson-transpilers/node_modules/inquirer/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "packages/bson-transpilers/node_modules/is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "dependencies": { - "number-is-nan": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "packages/bson-transpilers/node_modules/jsx-ast-utils": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-1.4.1.tgz", - "integrity": "sha1-OGchPo3Xm/Ho8jAMDPwe+xgsDfE=", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "packages/bson-transpilers/node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "packages/bson-transpilers/node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "packages/bson-transpilers/node_modules/onetime": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", - "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "packages/bson-transpilers/node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "packages/bson-transpilers/node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "packages/bson-transpilers/node_modules/progress": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", - "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "packages/bson-transpilers/node_modules/restore-cursor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", - "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", - "dev": true, - "dependencies": { - "exit-hook": "^1.0.0", - "onetime": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "packages/bson-transpilers/node_modules/rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "packages/bson-transpilers/node_modules/run-async": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", - "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=", - "dev": true, - "dependencies": { - "once": "^1.3.0" - } - }, - "packages/bson-transpilers/node_modules/slice-ansi": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", - "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "packages/bson-transpilers/node_modules/string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "dependencies": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "packages/bson-transpilers/node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "packages/bson-transpilers/node_modules/table": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/table/-/table-3.8.3.tgz", - "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=", - "dev": true, - "dependencies": { - "ajv": "^4.7.0", - "ajv-keywords": "^1.0.0", - "chalk": "^1.1.1", - "lodash": "^4.0.0", - "slice-ansi": "0.0.4", - "string-width": "^2.0.0" - } - }, - "packages/bson-transpilers/node_modules/table/node_modules/ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "packages/bson-transpilers/node_modules/table/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "packages/bson-transpilers/node_modules/table/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "packages/bson-transpilers/node_modules/table/node_modules/string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "dependencies": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "packages/bson-transpilers/node_modules/table/node_modules/string-width/node_modules/strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "dependencies": { - "ansi-regex": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "packages/bson-transpilers/node_modules/table/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "packages/bson-transpilers/node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "packages/bson-transpilers/node_modules/write": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", - "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", - "dev": true, - "dependencies": { - "mkdirp": "^0.5.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "packages/collection-model": { "name": "mongodb-collection-model", - "version": "5.18.1", + "version": "5.19.1", "license": "SSPL", "dependencies": { "ampersand-collection": "^2.0.2", "ampersand-model": "^8.0.1", - "mongodb-data-service": "^22.18.1", + "mongodb-data-service": "^22.19.1", "mongodb-ns": "^2.4.0" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/prettier-config-compass": "^1.0.1", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/prettier-config-compass": "^1.0.2", "depcheck": "^1.4.1", "electron-mocha": "^12.2.0", "eslint": "^7.25.0", @@ -44688,7 +44258,7 @@ }, "packages/compass": { "name": "mongodb-compass", - "version": "1.42.5", + "version": "1.42.6-beta.5", "hasInstallScript": true, "license": "SSPL", "dependencies": { @@ -44703,66 +44273,66 @@ "devDependencies": { "@electron/rebuild": "^3.6.0", "@electron/remote": "^2.1.2", - "@mongodb-js/atlas-service": "^0.15.1", - "@mongodb-js/compass-aggregations": "^9.26.1", - "@mongodb-js/compass-app-stores": "^7.10.1", - "@mongodb-js/compass-collection": "^4.23.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-crud": "^13.24.1", - "@mongodb-js/compass-databases-collections": "^1.23.1", - "@mongodb-js/compass-explain-plan": "^6.24.1", - "@mongodb-js/compass-export-to-language": "^9.0.1", - "@mongodb-js/compass-field-store": "^9.0.19", - "@mongodb-js/compass-find-in-page": "^4.22.1", - "@mongodb-js/compass-generative-ai": "^0.8.1", - "@mongodb-js/compass-import-export": "^7.23.1", - "@mongodb-js/compass-indexes": "^5.23.1", - "@mongodb-js/compass-intercom": "^0.2.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-query-bar": "^8.25.1", - "@mongodb-js/compass-saved-aggregations-queries": "^1.24.1", - "@mongodb-js/compass-schema": "^6.25.1", - "@mongodb-js/compass-schema-validation": "^6.24.1", - "@mongodb-js/compass-serverstats": "^16.23.1", - "@mongodb-js/compass-settings": "^0.26.1", - "@mongodb-js/compass-shell": "^3.23.1", - "@mongodb-js/compass-sidebar": "^5.24.1", - "@mongodb-js/compass-utils": "^0.6.1", - "@mongodb-js/compass-welcome": "^0.22.1", - "@mongodb-js/compass-workspaces": "^0.5.1", - "@mongodb-js/connection-info": "^0.1.5", - "@mongodb-js/connection-storage": "^0.8.1", - "@mongodb-js/eslint-config-compass": "^1.0.17", + "@mongodb-js/atlas-service": "^0.17.0", + "@mongodb-js/compass-aggregations": "^9.28.0", + "@mongodb-js/compass-app-stores": "^7.12.0", + "@mongodb-js/compass-collection": "^4.25.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connection-import-export": "^0.23.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-crud": "^13.26.0", + "@mongodb-js/compass-databases-collections": "^1.25.0", + "@mongodb-js/compass-explain-plan": "^6.26.0", + "@mongodb-js/compass-export-to-language": "^9.2.0", + "@mongodb-js/compass-field-store": "^9.2.0", + "@mongodb-js/compass-find-in-page": "^4.24.0", + "@mongodb-js/compass-generative-ai": "^0.10.0", + "@mongodb-js/compass-import-export": "^7.25.0", + "@mongodb-js/compass-indexes": "^5.25.0", + "@mongodb-js/compass-intercom": "^0.4.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-query-bar": "^8.27.0", + "@mongodb-js/compass-saved-aggregations-queries": "^1.26.0", + "@mongodb-js/compass-schema": "^6.27.0", + "@mongodb-js/compass-schema-validation": "^6.26.0", + "@mongodb-js/compass-serverstats": "^16.25.0", + "@mongodb-js/compass-settings": "^0.28.0", + "@mongodb-js/compass-shell": "^3.25.0", + "@mongodb-js/compass-sidebar": "^5.26.0", + "@mongodb-js/compass-utils": "^0.6.3", + "@mongodb-js/compass-welcome": "^0.24.0", + "@mongodb-js/compass-workspaces": "^0.7.0", + "@mongodb-js/connection-info": "^0.2.1", + "@mongodb-js/connection-storage": "^0.10.0", + "@mongodb-js/eslint-config-compass": "^1.1.1", "@mongodb-js/get-os-info": "^0.3.23", - "@mongodb-js/mocha-config-compass": "^1.3.7", + "@mongodb-js/mocha-config-compass": "^1.3.9", "@mongodb-js/mongodb-downloader": "^0.2.1", - "@mongodb-js/my-queries-storage": "^0.5.1", - "@mongodb-js/prettier-config-compass": "^1.0.1", + "@mongodb-js/my-queries-storage": "^0.7.0", + "@mongodb-js/prettier-config-compass": "^1.0.2", "@mongodb-js/sbom-tools": "^0.5.3", - "@mongodb-js/tsconfig-compass": "^1.0.3", - "@mongodb-js/webpack-config-compass": "^1.3.5", + "@mongodb-js/tsconfig-compass": "^1.0.4", + "@mongodb-js/webpack-config-compass": "^1.3.7", "@segment/analytics-node": "^1.1.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "ampersand-view": "^9.0.0", - "bson": "^6.6.0", "chai": "^4.3.4", "chalk": "^4.1.2", "clean-stack": "^2.0.0", - "compass-preferences-model": "^2.18.1", + "compass-preferences-model": "^2.20.0", "debug": "^4.2.0", "depcheck": "^1.4.1", - "electron": "^28.2.10", + "electron": "^29.3.1", "electron-devtools-installer": "^3.2.0", "electron-dl": "^3.5.0", "electron-mocha": "^12.2.0", "electron-squirrel-startup": "^1.0.0", "ensure-error": "^3.0.1", "eslint": "^7.25.0", - "hadron-app-registry": "^9.1.8", - "hadron-build": "^25.4.9", - "hadron-ipc": "^3.2.12", + "hadron-app-registry": "^9.1.10", + "hadron-build": "^25.4.11", + "hadron-ipc": "^3.2.14", "local-links": "^1.4.0", "make-fetch-happen": "^8.0.14", "marky": "^1.2.1", @@ -44770,8 +44340,8 @@ "mongodb-build-info": "^1.7.0", "mongodb-cloud-info": "^2.1.1", "mongodb-connection-string-url": "^2.6.0", - "mongodb-data-service": "^22.18.1", - "mongodb-instance-model": "^12.18.1", + "mongodb-data-service": "^22.19.1", + "mongodb-instance-model": "^12.20.0", "mongodb-log-writer": "^1.3.0", "mongodb-ns": "^2.4.0", "node-fetch": "^2.7.0", @@ -44793,7 +44363,7 @@ }, "packages/compass-aggregations": { "name": "@mongodb-js/compass-aggregations", - "version": "9.26.1", + "version": "9.28.0", "license": "SSPL", "dependencies": { "@babel/generator": "^7.19.5", @@ -44802,33 +44372,33 @@ "@dnd-kit/core": "^6.0.7", "@dnd-kit/sortable": "^7.0.2", "@dnd-kit/utilities": "^3.2.1", - "@mongodb-js/atlas-service": "^0.15.1", - "@mongodb-js/compass-app-stores": "^7.10.1", - "@mongodb-js/compass-collection": "^4.23.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-crud": "^13.24.1", - "@mongodb-js/compass-editor": "^0.21.1", - "@mongodb-js/compass-field-store": "^9.0.19", - "@mongodb-js/compass-generative-ai": "^0.8.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-utils": "^0.6.1", - "@mongodb-js/compass-workspaces": "^0.5.1", - "@mongodb-js/explain-plan-helper": "^1.1.10", + "@mongodb-js/atlas-service": "^0.17.0", + "@mongodb-js/compass-app-stores": "^7.12.0", + "@mongodb-js/compass-collection": "^4.25.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-crud": "^13.26.0", + "@mongodb-js/compass-editor": "^0.23.0", + "@mongodb-js/compass-field-store": "^9.2.0", + "@mongodb-js/compass-generative-ai": "^0.10.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-utils": "^0.6.3", + "@mongodb-js/compass-workspaces": "^0.7.0", + "@mongodb-js/explain-plan-helper": "^1.1.12", "@mongodb-js/mongodb-constants": "^0.9.0", - "@mongodb-js/my-queries-storage": "^0.5.1", + "@mongodb-js/my-queries-storage": "^0.7.0", "bson": "^6.6.0", - "compass-preferences-model": "^2.18.1", + "compass-preferences-model": "^2.20.0", "ejson-shell-parser": "^2.0.1", - "hadron-app-registry": "^9.1.8", - "hadron-document": "^8.4.9", - "hadron-type-checker": "^7.1.2", + "hadron-app-registry": "^9.1.10", + "hadron-document": "^8.5.1", + "hadron-type-checker": "^7.2.1", "lodash": "^4.17.21", "mongodb": "^6.5.0", - "mongodb-collection-model": "^5.18.1", - "mongodb-data-service": "^22.18.1", - "mongodb-database-model": "^2.18.1", - "mongodb-instance-model": "^12.18.1", + "mongodb-collection-model": "^5.19.1", + "mongodb-data-service": "^22.19.1", + "mongodb-database-model": "^2.19.1", + "mongodb-instance-model": "^12.20.0", "mongodb-ns": "^2.4.0", "mongodb-query-parser": "^4.1.0", "mongodb-schema": "^12.1.0", @@ -44841,10 +44411,10 @@ "semver": "^5.7.2" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "@types/lodash": "^4.14.188", @@ -44911,24 +44481,27 @@ }, "packages/compass-app-stores": { "name": "@mongodb-js/compass-app-stores", - "version": "7.10.1", + "version": "7.12.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/connection-info": "^0.1.5", - "@mongodb-js/connection-storage": "^0.8.1", - "hadron-app-registry": "^9.1.8", - "mongodb-instance-model": "^12.18.1", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/connection-info": "^0.2.1", + "hadron-app-registry": "^9.1.10", + "mongodb-collection-model": "^5.19.1", + "mongodb-database-model": "^2.19.1", + "mongodb-instance-model": "^12.20.0", "mongodb-ns": "^2.4.0", "react": "^17.0.2" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", + "@testing-library/dom": "8.20.1", + "@testing-library/react": "12.1.5", "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/sinon-chai": "^3.2.5", @@ -44973,18 +44546,18 @@ }, "packages/compass-collection": { "name": "@mongodb-js/compass-collection", - "version": "4.23.1", + "version": "4.25.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-app-stores": "^7.10.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-workspaces": "^0.5.1", - "compass-preferences-model": "^2.18.1", - "hadron-app-registry": "^9.1.8", - "mongodb-collection-model": "^5.18.1", - "mongodb-instance-model": "^12.18.1", + "@mongodb-js/compass-app-stores": "^7.12.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-workspaces": "^0.7.0", + "@mongodb-js/connection-info": "^0.2.1", + "compass-preferences-model": "^2.20.0", + "hadron-app-registry": "^9.1.10", + "mongodb-collection-model": "^5.19.1", "mongodb-ns": "^2.4.0", "numeral": "^2.0.6", "react": "^17.0.2", @@ -44993,10 +44566,10 @@ "redux-thunk": "^2.4.2" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "@types/chai": "^4.2.21", @@ -45056,7 +44629,7 @@ }, "packages/compass-components": { "name": "@mongodb-js/compass-components", - "version": "1.22.1", + "version": "1.24.0", "license": "SSPL", "dependencies": { "@dnd-kit/core": "^6.0.7", @@ -45108,8 +44681,8 @@ "@react-stately/tooltip": "^3.0.5", "bson": "^6.6.0", "focus-trap-react": "^9.0.2", - "hadron-document": "^8.4.9", - "hadron-type-checker": "^7.1.2", + "hadron-document": "^8.5.1", + "hadron-type-checker": "^7.2.1", "is-electron-renderer": "^2.0.1", "lodash": "^4.17.21", "polished": "^4.2.2", @@ -45121,10 +44694,10 @@ }, "devDependencies": { "@emotion/css": "^11.11.2", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/dom": "^8.11.1", "@testing-library/react": "^12.1.4", "@testing-library/react-hooks": "^7.0.2", @@ -45172,20 +44745,21 @@ }, "packages/compass-connection-import-export": { "name": "@mongodb-js/compass-connection-import-export", - "version": "0.21.1", + "version": "0.23.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/connection-storage": "^0.8.1", - "compass-preferences-model": "^2.18.1", - "hadron-ipc": "^3.2.12", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/connection-storage": "^0.10.0", + "compass-preferences-model": "^2.20.0", + "hadron-ipc": "^3.2.14", "react": "^17.0.2" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/react-hooks": "^7.0.2", "@types/chai": "^4.2.21", @@ -45236,31 +44810,29 @@ }, "packages/compass-connections": { "name": "@mongodb-js/compass-connections", - "version": "1.25.1", + "version": "1.27.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connection-import-export": "^0.21.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-maybe-protect-connection-string": "^0.16.1", - "@mongodb-js/connection-form": "^1.23.1", - "@mongodb-js/connection-info": "^0.1.5", - "@mongodb-js/connection-storage": "^0.8.1", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-maybe-protect-connection-string": "^0.18.0", + "@mongodb-js/connection-form": "^1.25.0", + "@mongodb-js/connection-info": "^0.2.1", + "@mongodb-js/connection-storage": "^0.10.0", "bson": "^6.6.0", - "compass-preferences-model": "^2.18.1", - "hadron-app-registry": "^9.1.8", + "compass-preferences-model": "^2.20.0", + "hadron-app-registry": "^9.1.10", "lodash": "^4.17.21", "mongodb-build-info": "^1.7.0", "mongodb-connection-string-url": "^2.6.0", - "mongodb-data-service": "^22.18.1", - "react": "^17.0.2", - "uuid": "^8.2.0" + "mongodb-data-service": "^22.19.1", + "react": "^17.0.2" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/dom": "^8.11.1", "@testing-library/react": "^12.1.4", "@testing-library/react-hooks": "^7.0.2", @@ -45283,7 +44855,47 @@ "xvfb-maybe": "^0.2.1" } }, - "packages/compass-connections/node_modules/sinon": { + "packages/compass-connections-navigation": { + "name": "@mongodb-js/compass-connections-navigation", + "version": "1.25.0", + "license": "SSPL", + "dependencies": { + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-workspaces": "^0.7.0", + "@mongodb-js/connection-form": "^1.24.0", + "@mongodb-js/connection-info": "^0.2.1", + "compass-preferences-model": "^2.20.0", + "react": "^17.0.2", + "react-virtualized-auto-sizer": "^1.0.6", + "react-window": "^1.8.6" + }, + "devDependencies": { + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", + "@testing-library/react": "^12.1.4", + "@testing-library/user-event": "^13.5.0", + "@types/chai": "^4.2.21", + "@types/chai-dom": "^0.0.10", + "@types/mocha": "^9.0.0", + "@types/react": "^17.0.5", + "@types/react-dom": "^17.0.10", + "@types/react-virtualized-auto-sizer": "^1.0.1", + "@types/react-window": "^1.8.5", + "@types/sinon-chai": "^3.2.5", + "chai": "^4.3.4", + "depcheck": "^1.4.1", + "eslint": "^7.25.0", + "mocha": "^10.2.0", + "nyc": "^15.1.0", + "prettier": "^2.7.1", + "react-dom": "^17.0.2", + "sinon": "^9.2.3", + "typescript": "^5.0.4" + } + }, + "packages/compass-connections-navigation/node_modules/sinon": { "version": "9.2.4", "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.4.tgz", "integrity": "sha512-zljcULZQsJxVra28qIAL6ow1Z9tpattkCTEJR4RBP3TGc00FcttsP5pK284Nas5WjMZU5Yzy3kAIp3B3KRf5Yg==", @@ -45301,7 +44913,7 @@ "url": "https://opencollective.com/sinon" } }, - "packages/compass-connections/node_modules/sinon/node_modules/diff": { + "packages/compass-connections-navigation/node_modules/sinon/node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", @@ -45310,41 +44922,60 @@ "node": ">=0.3.1" } }, - "packages/compass-connections/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" + "packages/compass-connections/node_modules/sinon": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.4.tgz", + "integrity": "sha512-zljcULZQsJxVra28qIAL6ow1Z9tpattkCTEJR4RBP3TGc00FcttsP5pK284Nas5WjMZU5Yzy3kAIp3B3KRf5Yg==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.8.1", + "@sinonjs/fake-timers": "^6.0.1", + "@sinonjs/samsam": "^5.3.1", + "diff": "^4.0.2", + "nise": "^4.0.4", + "supports-color": "^7.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "packages/compass-connections/node_modules/sinon/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" } }, "packages/compass-crud": { "name": "@mongodb-js/compass-crud", - "version": "13.24.1", + "version": "13.26.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-app-stores": "^7.10.1", - "@mongodb-js/compass-collection": "^4.23.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-editor": "^0.21.1", - "@mongodb-js/compass-field-store": "^9.0.19", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-query-bar": "^8.25.1", - "@mongodb-js/compass-workspaces": "^0.5.1", - "@mongodb-js/explain-plan-helper": "^1.1.10", - "@mongodb-js/my-queries-storage": "^0.5.1", + "@mongodb-js/compass-app-stores": "^7.12.0", + "@mongodb-js/compass-collection": "^4.25.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-editor": "^0.23.0", + "@mongodb-js/compass-field-store": "^9.2.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-query-bar": "^8.27.0", + "@mongodb-js/compass-workspaces": "^0.7.0", + "@mongodb-js/explain-plan-helper": "^1.1.12", + "@mongodb-js/my-queries-storage": "^0.7.0", "ag-grid-community": "^20.2.0", "ag-grid-react": "^20.2.0", "bson": "^6.6.0", - "compass-preferences-model": "^2.18.1", + "compass-preferences-model": "^2.20.0", "ejson-shell-parser": "^2.0.1", - "hadron-app-registry": "^9.1.8", - "hadron-document": "^8.4.9", - "hadron-type-checker": "^7.1.2", + "hadron-app-registry": "^9.1.10", + "hadron-document": "^8.5.1", + "hadron-type-checker": "^7.2.1", "jsondiffpatch": "^0.5.0", "lodash": "^4.17.21", - "mongodb-data-service": "^22.18.1", + "mongodb-data-service": "^22.19.1", "mongodb-ns": "^2.4.0", "mongodb-query-parser": "^4.1.0", "prop-types": "^15.7.2", @@ -45354,23 +44985,23 @@ "semver": "^7.5.4" }, "devDependencies": { - "@mongodb-js/compass-test-server": "^0.1.13", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/compass-test-server": "^0.1.15", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "@types/reflux": "^6.4.3", "chai": "^4.1.2", "chai-as-promised": "^7.1.1", "depcheck": "^1.4.1", - "electron": "^28.2.10", + "electron": "^29.3.1", "electron-mocha": "^12.2.0", "enzyme": "^3.11.0", "eslint": "^7.25.0", "mocha": "^10.2.0", - "mongodb-instance-model": "^12.18.1", + "mongodb-instance-model": "^12.20.0", "nyc": "^15.1.0", "react-dom": "^17.0.2", "sinon": "^8.1.1", @@ -45413,79 +45044,15 @@ "react": "^17.0.2" } }, - "packages/compass-databases-navigation": { - "name": "@mongodb-js/compass-databases-navigation", - "version": "1.23.1", - "license": "SSPL", - "dependencies": { - "@mongodb-js/compass-components": "^1.22.1", - "compass-preferences-model": "^2.18.1", - "react": "^17.0.2", - "react-virtualized-auto-sizer": "^1.0.6", - "react-window": "^1.8.6" - }, - "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", - "@testing-library/react": "^12.1.4", - "@testing-library/user-event": "^13.5.0", - "@types/chai": "^4.2.21", - "@types/chai-dom": "^0.0.10", - "@types/mocha": "^9.0.0", - "@types/react": "^17.0.5", - "@types/react-dom": "^17.0.10", - "@types/react-virtualized-auto-sizer": "^1.0.1", - "@types/react-window": "^1.8.5", - "@types/sinon-chai": "^3.2.5", - "chai": "^4.3.4", - "depcheck": "^1.4.1", - "eslint": "^7.25.0", - "mocha": "^10.2.0", - "nyc": "^15.1.0", - "prettier": "^2.7.1", - "react-dom": "^17.0.2", - "sinon": "^9.2.3", - "typescript": "^5.0.4" - } - }, - "packages/compass-databases-navigation/node_modules/sinon": { - "version": "9.2.4", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.4.tgz", - "integrity": "sha512-zljcULZQsJxVra28qIAL6ow1Z9tpattkCTEJR4RBP3TGc00FcttsP5pK284Nas5WjMZU5Yzy3kAIp3B3KRf5Yg==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.8.1", - "@sinonjs/fake-timers": "^6.0.1", - "@sinonjs/samsam": "^5.3.1", - "diff": "^4.0.2", - "nise": "^4.0.4", - "supports-color": "^7.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/sinon" - } - }, - "packages/compass-databases-navigation/node_modules/sinon/node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, "packages/compass-e2e-tests": { - "version": "1.19.1", + "version": "1.21.0", "devDependencies": { "@electron/rebuild": "^3.6.0", - "@mongodb-js/compass-test-server": "^0.1.13", - "@mongodb-js/eslint-config-compass": "^1.0.17", + "@mongodb-js/compass-test-server": "^0.1.15", + "@mongodb-js/eslint-config-compass": "^1.1.1", "@mongodb-js/oidc-mock-provider": "^0.9.0", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/chai-as-promised": "^7.1.4", "@types/cross-spawn": "^6.0.2", "@types/puppeteer": "^5.4.4", @@ -45494,15 +45061,15 @@ "chai": "^4.3.4", "chai-as-promised": "^7.1.1", "clipboardy": "^2.3.0", - "compass-preferences-model": "^2.18.1", + "compass-preferences-model": "^2.20.0", "cross-spawn": "^7.0.3", "debug": "^4.2.0", "depcheck": "^1.4.1", - "electron": "^28.2.10", + "electron": "^29.3.1", "eslint": "^7.25.0", "fast-glob": "^3.2.7", "glob": "^10.2.5", - "hadron-build": "^25.4.9", + "hadron-build": "^25.4.11", "lodash": "^4.17.21", "mocha": "^10.2.0", "mongodb": "^6.5.0", @@ -45759,7 +45326,7 @@ }, "packages/compass-editor": { "name": "@mongodb-js/compass-editor", - "version": "0.21.1", + "version": "0.23.0", "license": "SSPL", "dependencies": { "@codemirror/autocomplete": "^6.4.0", @@ -45771,17 +45338,17 @@ "@codemirror/state": "^6.1.4", "@codemirror/view": "^6.7.1", "@lezer/highlight": "^1.1.3", - "@mongodb-js/compass-components": "^1.22.1", + "@mongodb-js/compass-components": "^1.24.0", "@mongodb-js/mongodb-constants": "^0.9.0", "polished": "^4.2.2", "prettier": "^2.7.1", "react": "^17.0.2" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/prettier": "^2.7.1", @@ -45824,20 +45391,20 @@ }, "packages/compass-explain-plan": { "name": "@mongodb-js/compass-explain-plan", - "version": "6.24.1", + "version": "6.26.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-collection": "^4.23.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-editor": "^0.21.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/explain-plan-helper": "^1.1.10", - "compass-preferences-model": "^2.18.1", + "@mongodb-js/compass-collection": "^4.25.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-editor": "^0.23.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/explain-plan-helper": "^1.1.12", + "compass-preferences-model": "^2.20.0", "d3": "^3.5.17", "d3-flextree": "^2.1.2", "d3-hierarchy": "^3.1.2", - "hadron-app-registry": "^9.1.8", + "hadron-app-registry": "^9.1.10", "lodash": "^4.17.21", "mongodb": "^6.5.0", "react": "^17.0.2", @@ -45846,17 +45413,17 @@ "redux-thunk": "^2.4.2" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@types/d3": "^3.5.x", "@types/d3-flextree": "^2.1.0", "@types/d3-hierarchy": "^3.1.2", "chai": "^4.2.0", "depcheck": "^1.4.1", - "electron": "^28.2.10", + "electron": "^29.3.1", "electron-mocha": "^12.2.0", "eslint": "^7.25.0", "mocha": "^10.2.0", @@ -45902,29 +45469,29 @@ }, "packages/compass-export-to-language": { "name": "@mongodb-js/compass-export-to-language", - "version": "9.0.1", + "version": "9.2.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-collection": "^4.23.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-editor": "^0.21.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-maybe-protect-connection-string": "^0.16.1", - "bson-transpilers": "^3.0.0", - "compass-preferences-model": "^2.18.1", + "@mongodb-js/compass-collection": "^4.25.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-editor": "^0.23.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-maybe-protect-connection-string": "^0.18.0", + "bson-transpilers": "^3.0.2", + "compass-preferences-model": "^2.20.0", "ejson-shell-parser": "^2.0.1", - "hadron-app-registry": "^9.1.8", + "hadron-app-registry": "^9.1.10", "mongodb-ns": "^2.4.0", "react": "^17.0.2", "react-redux": "^8.1.3", "redux": "^4.2.1" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "chai": "^4.3.6", @@ -45966,12 +45533,11 @@ }, "packages/compass-field-store": { "name": "@mongodb-js/compass-field-store", - "version": "9.0.19", + "version": "9.2.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/connection-storage": "^0.8.1", - "hadron-app-registry": "^9.1.8", + "@mongodb-js/compass-connections": "^1.27.0", + "hadron-app-registry": "^9.1.10", "lodash": "^4.17.21", "mongodb-schema": "^12.1.0", "react": "^17.0.2", @@ -45979,10 +45545,10 @@ "redux": "^4.2.1" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.5", "@testing-library/react-hooks": "^7.0.2", "@types/chai": "^4.2.21", @@ -46029,22 +45595,22 @@ }, "packages/compass-find-in-page": { "name": "@mongodb-js/compass-find-in-page", - "version": "4.22.1", + "version": "4.24.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-components": "^1.22.1", - "hadron-app-registry": "^9.1.8", - "hadron-ipc": "^3.2.12", + "@mongodb-js/compass-components": "^1.24.0", + "hadron-app-registry": "^9.1.10", + "hadron-ipc": "^3.2.14", "react": "^17.0.2", "react-redux": "^8.1.3", "redux": "^4.2.1", "redux-thunk": "^2.4.2" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "@types/chai": "^4.2.21", @@ -46055,7 +45621,7 @@ "@types/sinon-chai": "^3.2.5", "chai": "^4.3.4", "depcheck": "^1.4.1", - "electron": "^28.2.10", + "electron": "^29.3.1", "electron-mocha": "^12.2.0", "eslint": "^7.25.0", "mocha": "^10.2.0", @@ -46096,24 +45662,25 @@ }, "packages/compass-generative-ai": { "name": "@mongodb-js/compass-generative-ai", - "version": "0.8.1", + "version": "0.10.0", "license": "SSPL", "dependencies": { - "@mongodb-js/atlas-service": "^0.15.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-intercom": "^0.2.1", - "@mongodb-js/compass-logging": "^1.2.14", - "compass-preferences-model": "^2.18.1", - "hadron-app-registry": "^9.1.8", + "@mongodb-js/atlas-service": "^0.17.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-intercom": "^0.4.0", + "@mongodb-js/compass-logging": "^1.2.16", + "bson": "^6.6.0", + "compass-preferences-model": "^2.20.0", + "hadron-app-registry": "^9.1.10", "mongodb": "^6.5.0", "mongodb-schema": "^12.1.0", "react": "^17.0.2" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "@types/chai": "^4.2.21", @@ -46124,7 +45691,6 @@ "@types/react": "^17.0.5", "@types/react-dom": "^17.0.10", "@types/sinon-chai": "^3.2.5", - "bson": "^6.6.0", "chai": "^4.3.6", "decomment": "^0.9.5", "depcheck": "^1.4.1", @@ -46298,26 +45864,26 @@ }, "packages/compass-import-export": { "name": "@mongodb-js/compass-import-export", - "version": "7.23.1", + "version": "7.25.0", "license": "SSPL", "dependencies": { "@electron/remote": "^2.1.2", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-editor": "^0.21.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-utils": "^0.6.1", - "@mongodb-js/compass-workspaces": "^0.5.1", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-editor": "^0.23.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-utils": "^0.6.3", + "@mongodb-js/compass-workspaces": "^0.7.0", "bson": "^6.6.0", - "compass-preferences-model": "^2.18.1", + "compass-preferences-model": "^2.20.0", "debug": "^4.2.0", - "electron": "^28.2.10", - "hadron-app-registry": "^9.1.8", - "hadron-document": "^8.4.9", - "hadron-ipc": "^3.2.12", + "electron": "^29.3.1", + "hadron-app-registry": "^9.1.10", + "hadron-document": "^8.5.1", + "hadron-ipc": "^3.2.14", "lodash": "^4.17.21", "mongodb": "^6.5.0", - "mongodb-data-service": "^22.18.1", + "mongodb-data-service": "^22.19.1", "mongodb-ns": "^2.4.0", "mongodb-query-parser": "^4.1.0", "mongodb-schema": "^12.1.0", @@ -46331,11 +45897,11 @@ "temp": "^0.9.4" }, "devDependencies": { - "@mongodb-js/compass-test-server": "^0.1.13", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/compass-test-server": "^0.1.15", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "@types/chai": "^4.2.21", @@ -46392,26 +45958,26 @@ }, "packages/compass-indexes": { "name": "@mongodb-js/compass-indexes", - "version": "5.23.1", + "version": "5.25.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-app-stores": "^7.10.1", - "@mongodb-js/compass-collection": "^4.23.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-editor": "^0.21.1", - "@mongodb-js/compass-field-store": "^9.0.19", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-workspaces": "^0.5.1", - "@mongodb-js/connection-storage": "^0.8.1", + "@mongodb-js/compass-app-stores": "^7.12.0", + "@mongodb-js/compass-collection": "^4.25.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-editor": "^0.23.0", + "@mongodb-js/compass-field-store": "^9.2.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-workspaces": "^0.7.0", + "@mongodb-js/connection-storage": "^0.10.0", "@mongodb-js/mongodb-constants": "^0.9.0", "bson": "^6.6.0", - "compass-preferences-model": "^2.18.1", + "compass-preferences-model": "^2.20.0", "ejson-shell-parser": "^2.0.1", - "hadron-app-registry": "^9.1.8", + "hadron-app-registry": "^9.1.10", "lodash": "^4.17.21", "mongodb": "^6.5.0", - "mongodb-data-service": "^22.18.1", + "mongodb-data-service": "^22.19.1", "mongodb-query-parser": "^4.1.0", "numeral": "^2.0.6", "react": "^17.0.2", @@ -46421,15 +45987,15 @@ "semver": "^5.7.2" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "chai": "^4.2.0", "depcheck": "^1.4.1", - "electron": "^28.2.10", + "electron": "^29.3.1", "electron-mocha": "^12.2.0", "eslint": "^7.25.0", "mocha": "^10.2.0", @@ -46527,17 +46093,17 @@ }, "packages/compass-intercom": { "name": "@mongodb-js/compass-intercom", - "version": "0.2.1", + "version": "0.4.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-logging": "^1.2.14", - "compass-preferences-model": "^2.18.1" + "@mongodb-js/compass-logging": "^1.2.16", + "compass-preferences-model": "^2.20.0" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/sinon-chai": "^3.2.5", @@ -46643,21 +46209,21 @@ }, "packages/compass-logging": { "name": "@mongodb-js/compass-logging", - "version": "1.2.14", + "version": "1.2.16", "license": "SSPL", "dependencies": { "debug": "^4.3.4", - "hadron-app-registry": "^9.1.8", - "hadron-ipc": "^3.2.12", + "hadron-app-registry": "^9.1.10", + "hadron-ipc": "^3.2.14", "is-electron-renderer": "^2.0.1", "mongodb-log-writer": "^1.3.0", "react": "^17.0.2" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/chai": "^4.2.21", "@types/debug": "^4.1.9", "@types/mocha": "^9.0.0", @@ -46717,17 +46283,17 @@ }, "packages/compass-maybe-protect-connection-string": { "name": "@mongodb-js/compass-maybe-protect-connection-string", - "version": "0.16.1", + "version": "0.18.0", "license": "SSPL", "dependencies": { - "compass-preferences-model": "^2.18.1", + "compass-preferences-model": "^2.20.0", "mongodb-connection-string-url": "^2.6.0" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/sinon-chai": "^3.2.5", @@ -46770,14 +46336,14 @@ } }, "packages/compass-preferences-model": { - "version": "2.18.1", + "version": "2.20.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-user-data": "^0.1.17", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-user-data": "^0.1.19", "bson": "^6.6.0", - "hadron-app-registry": "^9.1.8", - "hadron-ipc": "^3.2.12", + "hadron-app-registry": "^9.1.10", + "hadron-ipc": "^3.2.14", "js-yaml": "^4.1.0", "lodash": "^4.17.21", "react": "^17.0.2", @@ -46785,8 +46351,8 @@ "zod": "^3.22.3" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.16", - "@mongodb-js/mocha-config-compass": "^1.3.6", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", "@testing-library/react": "^12.1.4", "@types/js-yaml": "^4.0.5", "@types/yargs-parser": "21.0.0", @@ -46850,29 +46416,29 @@ }, "packages/compass-query-bar": { "name": "@mongodb-js/compass-query-bar", - "version": "8.25.1", + "version": "8.27.0", "license": "SSPL", "dependencies": { - "@mongodb-js/atlas-service": "^0.15.1", - "@mongodb-js/compass-app-stores": "^7.10.1", - "@mongodb-js/compass-collection": "^4.23.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-editor": "^0.21.1", - "@mongodb-js/compass-field-store": "^9.0.19", - "@mongodb-js/compass-generative-ai": "^0.8.1", - "@mongodb-js/compass-logging": "^1.2.14", + "@mongodb-js/atlas-service": "^0.17.0", + "@mongodb-js/compass-app-stores": "^7.12.0", + "@mongodb-js/compass-collection": "^4.25.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-editor": "^0.23.0", + "@mongodb-js/compass-field-store": "^9.2.0", + "@mongodb-js/compass-generative-ai": "^0.10.0", + "@mongodb-js/compass-logging": "^1.2.16", "@mongodb-js/mongodb-constants": "^0.9.0", - "@mongodb-js/my-queries-storage": "^0.5.1", + "@mongodb-js/my-queries-storage": "^0.7.0", "bson": "^6.6.0", - "compass-preferences-model": "^2.18.1", - "hadron-app-registry": "^9.1.8", + "compass-preferences-model": "^2.20.0", + "hadron-app-registry": "^9.1.10", "lodash": "^4.17.21", "mongodb": "^6.5.0", - "mongodb-instance-model": "^12.18.1", + "mongodb-instance-model": "^12.20.0", "mongodb-ns": "^2.4.0", "mongodb-query-parser": "^4.1.0", - "mongodb-query-util": "^2.1.9", + "mongodb-query-util": "^2.2.1", "mongodb-schema": "^12.1.0", "react": "^17.0.2", "react-redux": "^8.1.3", @@ -46880,15 +46446,15 @@ "redux-thunk": "^2.4.2" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "chai": "^4.2.0", "depcheck": "^1.4.1", - "electron": "^28.2.10", + "electron": "^29.3.1", "electron-mocha": "^12.2.0", "eslint": "^7.25.0", "mocha": "^10.2.0", @@ -46928,18 +46494,18 @@ }, "packages/compass-saved-aggregations-queries": { "name": "@mongodb-js/compass-saved-aggregations-queries", - "version": "1.24.1", + "version": "1.26.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-app-stores": "^7.10.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-workspaces": "^0.5.1", - "@mongodb-js/my-queries-storage": "^0.5.1", + "@mongodb-js/compass-app-stores": "^7.12.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-workspaces": "^0.7.0", + "@mongodb-js/my-queries-storage": "^0.7.0", "bson": "^6.6.0", "fuse.js": "^6.5.3", - "hadron-app-registry": "^9.1.8", + "hadron-app-registry": "^9.1.10", "mongodb-ns": "^2.4.0", "react": "^17.0.2", "react-redux": "^8.1.3", @@ -46947,10 +46513,10 @@ "redux-thunk": "^2.4.2" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/react-hooks": "^7.0.2", "@testing-library/user-event": "^13.5.0", @@ -47002,28 +46568,28 @@ }, "packages/compass-schema": { "name": "@mongodb-js/compass-schema", - "version": "6.25.1", + "version": "6.27.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-collection": "^4.23.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-field-store": "^9.0.19", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-query-bar": "^8.25.1", - "@mongodb-js/connection-storage": "^0.8.1", + "@mongodb-js/compass-collection": "^4.25.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-field-store": "^9.2.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-query-bar": "^8.27.0", + "@mongodb-js/connection-storage": "^0.10.0", "bson": "^6.6.0", - "compass-preferences-model": "^2.18.1", + "compass-preferences-model": "^2.20.0", "d3": "^3.5.17", - "hadron-app-registry": "^9.1.8", - "hadron-document": "^8.4.9", + "hadron-app-registry": "^9.1.10", + "hadron-document": "^8.5.1", "leaflet": "^1.5.1", "leaflet-defaulticon-compatibility": "^0.1.1", "leaflet-draw": "^1.0.4", "lodash": "^4.17.21", "moment": "^2.29.4", "mongodb": "^6.5.0", - "mongodb-query-util": "^2.1.9", + "mongodb-query-util": "^2.2.1", "mongodb-schema": "^12.1.0", "numeral": "^1.5.6", "prop-types": "^15.7.2", @@ -47034,11 +46600,11 @@ "reflux-state-mixin": "github:mongodb-js/reflux-state-mixin" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/my-queries-storage": "^0.5.1", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/my-queries-storage": "^0.7.0", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "@types/chai": "^4.2.21", @@ -47061,20 +46627,20 @@ }, "packages/compass-schema-validation": { "name": "@mongodb-js/compass-schema-validation", - "version": "6.24.1", + "version": "6.26.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-app-stores": "^7.10.1", - "@mongodb-js/compass-collection": "^4.23.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-crud": "^13.24.1", - "@mongodb-js/compass-editor": "^0.21.1", - "@mongodb-js/compass-field-store": "^9.0.19", - "@mongodb-js/compass-logging": "^1.2.14", + "@mongodb-js/compass-app-stores": "^7.12.0", + "@mongodb-js/compass-collection": "^4.25.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-crud": "^13.26.0", + "@mongodb-js/compass-editor": "^0.23.0", + "@mongodb-js/compass-field-store": "^9.2.0", + "@mongodb-js/compass-logging": "^1.2.16", "bson": "^6.6.0", - "compass-preferences-model": "^2.18.1", - "hadron-app-registry": "^9.1.8", + "compass-preferences-model": "^2.20.0", + "hadron-app-registry": "^9.1.10", "javascript-stringify": "^2.0.1", "lodash": "^4.17.21", "mongodb-ns": "^2.4.0", @@ -47087,19 +46653,19 @@ "semver": "^5.7.2" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "chai": "^4.2.0", "depcheck": "^1.4.1", - "electron": "^28.2.10", + "electron": "^29.3.1", "electron-mocha": "^12.2.0", "enzyme": "^3.11.0", "eslint": "^7.25.0", - "hadron-ipc": "^3.2.12", + "hadron-ipc": "^3.2.14", "mocha": "^10.2.0", - "mongodb-instance-model": "^12.18.1", + "mongodb-instance-model": "^12.20.0", "nyc": "^15.1.0", "react-dom": "^17.0.2", "sinon": "^8.1.1", @@ -47143,18 +46709,18 @@ }, "packages/compass-serverstats": { "name": "@mongodb-js/compass-serverstats", - "version": "16.23.1", + "version": "16.25.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-app-stores": "^7.10.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-workspaces": "^0.5.1", + "@mongodb-js/compass-app-stores": "^7.12.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-workspaces": "^0.7.0", "d3": "^3.5.17", "d3-timer": "^1.0.3", "debug": "^4.2.0", - "hadron-app-registry": "^9.1.8", + "hadron-app-registry": "^9.1.10", "lodash": "^4.17.21", "mongodb-ns": "^2.4.0", "prop-types": "^15.7.2", @@ -47162,10 +46728,10 @@ "reflux": "^0.4.1" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/d3": "^3.5.x", "@types/enzyme": "^3.10.14", "chai": "^4.1.2", @@ -47192,26 +46758,26 @@ }, "packages/compass-settings": { "name": "@mongodb-js/compass-settings", - "version": "0.26.1", + "version": "0.28.0", "license": "SSPL", "dependencies": { - "@mongodb-js/atlas-service": "^0.15.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-generative-ai": "^0.8.1", - "@mongodb-js/compass-logging": "^1.2.14", - "compass-preferences-model": "^2.18.1", - "hadron-app-registry": "^9.1.8", - "hadron-ipc": "^3.2.12", + "@mongodb-js/atlas-service": "^0.17.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-generative-ai": "^0.10.0", + "@mongodb-js/compass-logging": "^1.2.16", + "compass-preferences-model": "^2.20.0", + "hadron-app-registry": "^9.1.10", + "hadron-ipc": "^3.2.14", "react": "^17.0.2", "react-redux": "^8.1.3", "redux": "^4.2.1", "redux-thunk": "^2.4.2" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "@types/chai": "^4.2.21", @@ -47262,33 +46828,33 @@ }, "packages/compass-shell": { "name": "@mongodb-js/compass-shell", - "version": "3.23.1", + "version": "3.25.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-user-data": "^0.1.17", - "@mongodb-js/compass-utils": "^0.6.1", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-user-data": "^0.1.19", + "@mongodb-js/compass-utils": "^0.6.3", "@mongosh/browser-repl": "^2.2.3", "@mongosh/logging": "^2.2.3", "@mongosh/node-runtime-worker-thread": "^2.2.3", - "compass-preferences-model": "^2.18.1", - "hadron-app-registry": "^9.1.8", + "compass-preferences-model": "^2.20.0", + "hadron-app-registry": "^9.1.10", "prop-types": "^15.7.2", "react": "^17.0.2", "react-redux": "^8.1.3", "redux": "^4.2.1" }, "devDependencies": { - "@mongodb-js/connection-storage": "^0.8.1", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/connection-storage": "^0.10.0", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "chai": "^4.2.0", "depcheck": "^1.4.1", - "electron": "^28.2.10", + "electron": "^29.3.1", "electron-mocha": "^12.2.0", "enzyme": "^3.11.0", "eslint": "^7.25.0", @@ -47328,24 +46894,23 @@ }, "packages/compass-sidebar": { "name": "@mongodb-js/compass-sidebar", - "version": "5.24.1", + "version": "5.26.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-app-stores": "^7.10.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-databases-navigation": "^1.23.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-maybe-protect-connection-string": "^0.16.1", - "@mongodb-js/compass-workspaces": "^0.5.1", - "@mongodb-js/connection-form": "^1.23.1", - "@mongodb-js/connection-info": "^0.1.5", - "@mongodb-js/connection-storage": "^0.8.1", - "compass-preferences-model": "^2.18.1", - "hadron-app-registry": "^9.1.8", + "@mongodb-js/compass-app-stores": "^7.12.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-connections-navigation": "^1.25.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-maybe-protect-connection-string": "^0.18.0", + "@mongodb-js/compass-workspaces": "^0.7.0", + "@mongodb-js/connection-form": "^1.25.0", + "@mongodb-js/connection-info": "^0.2.1", + "compass-preferences-model": "^2.20.0", + "hadron-app-registry": "^9.1.10", "lodash": "^4.17.21", "mongodb": "^6.5.0", - "mongodb-instance-model": "^12.18.1", + "mongodb-instance-model": "^12.20.0", "mongodb-ns": "^2.4.0", "react": "^17.0.2", "react-redux": "^8.1.3", @@ -47353,10 +46918,11 @@ "redux-thunk": "^2.4.2" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/connection-storage": "^0.10.0", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "@types/chai": "^4.2.21", @@ -47371,7 +46937,7 @@ "electron-mocha": "^12.2.0", "eslint": "^7.25.0", "mocha": "^10.2.0", - "mongodb-data-service": "^22.18.1", + "mongodb-data-service": "^22.19.1", "nyc": "^15.1.0", "prettier": "^2.7.1", "react-dom": "^17.0.2", @@ -47409,16 +46975,16 @@ }, "packages/compass-test-server": { "name": "@mongodb-js/compass-test-server", - "version": "0.1.13", + "version": "0.1.15", "license": "SSPL", "dependencies": { "mongodb-runner": "^5.4.4" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/mocha": "^9.0.0", "@types/sinon-chai": "^3.2.5", "depcheck": "^1.4.1", @@ -47460,19 +47026,19 @@ }, "packages/compass-user-data": { "name": "@mongodb-js/compass-user-data", - "version": "0.1.17", + "version": "0.1.19", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-utils": "^0.6.1", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-utils": "^0.6.3", "write-file-atomic": "^5.0.1", "zod": "^3.22.3" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/sinon-chai": "^3.2.5", @@ -47540,17 +47106,17 @@ }, "packages/compass-utils": { "name": "@mongodb-js/compass-utils", - "version": "0.6.1", + "version": "0.6.3", "license": "SSPL", "dependencies": { "@electron/remote": "^2.1.2", - "electron": "^28.2.10" + "electron": "^29.3.1" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/sinon-chai": "^3.2.5", @@ -47594,68 +47160,78 @@ }, "packages/compass-web": { "name": "@mongodb-js/compass-web", - "version": "0.2.7", + "version": "0.4.0", "license": "SSPL", "devDependencies": { - "@gribnoysup/mongodb-browser": "^1.3.0", - "@mongodb-js/atlas-service": "^0.15.1", - "@mongodb-js/compass-aggregations": "^9.26.1", - "@mongodb-js/compass-app-stores": "^7.10.1", - "@mongodb-js/compass-collection": "^4.23.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-crud": "^13.24.1", - "@mongodb-js/compass-databases-collections": "^1.23.1", - "@mongodb-js/compass-explain-plan": "^6.24.1", - "@mongodb-js/compass-export-to-language": "^9.0.1", - "@mongodb-js/compass-field-store": "^9.0.19", - "@mongodb-js/compass-generative-ai": "^0.8.1", - "@mongodb-js/compass-indexes": "^5.23.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-query-bar": "^8.25.1", - "@mongodb-js/compass-schema": "^6.25.1", - "@mongodb-js/compass-schema-validation": "^6.24.1", - "@mongodb-js/compass-sidebar": "^5.24.1", - "@mongodb-js/compass-workspaces": "^0.5.1", - "@mongodb-js/connection-storage": "^0.8.1", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", - "@mongodb-js/webpack-config-compass": "^1.3.5", + "@mongodb-js/atlas-service": "^0.17.0", + "@mongodb-js/compass-aggregations": "^9.28.0", + "@mongodb-js/compass-app-stores": "^7.12.0", + "@mongodb-js/compass-collection": "^4.25.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-crud": "^13.26.0", + "@mongodb-js/compass-databases-collections": "^1.25.0", + "@mongodb-js/compass-explain-plan": "^6.26.0", + "@mongodb-js/compass-export-to-language": "^9.2.0", + "@mongodb-js/compass-field-store": "^9.2.0", + "@mongodb-js/compass-generative-ai": "^0.10.0", + "@mongodb-js/compass-indexes": "^5.25.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-query-bar": "^8.27.0", + "@mongodb-js/compass-schema": "^6.27.0", + "@mongodb-js/compass-schema-validation": "^6.26.0", + "@mongodb-js/compass-sidebar": "^5.26.0", + "@mongodb-js/compass-workspaces": "^0.7.0", + "@mongodb-js/connection-storage": "^0.10.0", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", + "@mongodb-js/webpack-config-compass": "^1.3.7", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "@types/chai": "^4.2.21", "@types/chai-dom": "^0.0.10", + "@types/express-http-proxy": "^1.6.6", "@types/mocha": "^9.0.0", "@types/react": "^17.0.5", "@types/react-dom": "^17.0.10", "@types/sinon-chai": "^3.2.5", + "browser-process-hrtime": "^1.0.0", + "bson": "^6.2.0", "buffer": "^6.0.3", "chai": "^4.3.6", - "compass-preferences-model": "^2.18.1", + "compass-preferences-model": "^2.20.0", + "crypto-browserify": "^3.12.0", "debug": "^4.2.0", "depcheck": "^1.4.1", + "dns-query": "^0.11.2", + "electron": "^29.3.1", "eslint": "^7.25.0", "events": "^3.3.0", - "hadron-app-registry": "^9.1.8", + "express": "^4.19.2", + "express-http-proxy": "^2.0.0", + "hadron-app-registry": "^9.1.10", + "is-ip": "^5.0.1", "mocha": "^10.2.0", + "mongodb": "^6.5.0", "mongodb-connection-string-url": "^2.6.0", - "mongodb-data-service": "^22.18.1", + "mongodb-data-service": "^22.19.1", "mongodb-log-writer": "^1.3.0", "nyc": "^15.1.0", + "os-browserify": "^0.3.0", "path-browserify": "^1.0.1", "prettier": "^2.7.1", "process": "^0.11.10", "readable-stream": "^4.5.0", "sinon": "^17.0.1", + "timers-browserify": "^2.0.12", "util": "^0.12.5", "vm-browserify": "^1.1.2", - "whatwg-url": "^13.0.0" + "whatwg-url": "^13.0.0", + "ws": "^8.16.0" }, "peerDependencies": { - "bson": "^6.2.0", - "mongodb": "^6.5.0", "react": "^17.0.2", "react-dom": "^17.0.2" } @@ -47880,20 +47456,41 @@ "node": ">=16" } }, + "packages/compass-web/node_modules/ws": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "packages/compass-welcome": { "name": "@mongodb-js/compass-welcome", - "version": "0.22.1", + "version": "0.24.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-components": "^1.22.1", - "compass-preferences-model": "^2.18.1", + "@mongodb-js/compass-components": "^1.24.0", + "compass-preferences-model": "^2.20.0", "react": "^17.0.2" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "@types/chai": "^4.2.21", @@ -47944,18 +47541,18 @@ }, "packages/compass-workspaces": { "name": "@mongodb-js/compass-workspaces", - "version": "0.5.1", + "version": "0.7.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-app-stores": "^7.10.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-logging": "^1.2.14", + "@mongodb-js/compass-app-stores": "^7.12.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-logging": "^1.2.16", "bson": "^6.6.0", - "hadron-app-registry": "^9.1.8", + "hadron-app-registry": "^9.1.10", "lodash": "^4.17.21", - "mongodb-collection-model": "^5.18.1", - "mongodb-database-model": "^2.18.1", + "mongodb-collection-model": "^5.19.1", + "mongodb-database-model": "^2.19.1", "mongodb-ns": "^2.4.0", "react": "^17.0.2", "react-redux": "^8.1.3", @@ -47963,10 +47560,10 @@ "redux-thunk": "^2.4.2" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/react-hooks": "^7.0.2", "@testing-library/user-event": "^13.5.0", @@ -48146,27 +47743,27 @@ }, "packages/connection-form": { "name": "@mongodb-js/connection-form", - "version": "1.23.1", + "version": "1.25.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-editor": "^0.21.1", - "@mongodb-js/connection-info": "^0.1.5", - "compass-preferences-model": "^2.11.1", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-editor": "^0.23.0", + "@mongodb-js/connection-info": "^0.2.1", + "compass-preferences-model": "^2.20.0", "ejson-shell-parser": "^2.0.1", "lodash": "^4.17.21", "mongodb": "^6.5.0", "mongodb-build-info": "^1.7.0", "mongodb-connection-string-url": "^2.6.0", - "mongodb-data-service": "^22.18.1", + "mongodb-data-service": "^22.19.1", "mongodb-query-parser": "^4.1.0", "react": "^17.0.2" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/react-hooks": "^7.0.2", "@testing-library/user-event": "^13.5.0", @@ -48218,19 +47815,19 @@ }, "packages/connection-info": { "name": "@mongodb-js/connection-info", - "version": "0.1.5", + "version": "0.2.1", "license": "SSPL", "dependencies": { "lodash": "^4.17.21", "mongodb": "^6.5.0", "mongodb-connection-string-url": "^2.6.0", - "mongodb-data-service": "^22.18.1" + "mongodb-data-service": "^22.19.1" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/sinon-chai": "^3.2.5", @@ -48353,27 +47950,28 @@ }, "packages/connection-storage": { "name": "@mongodb-js/connection-storage", - "version": "0.8.1", + "version": "0.10.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-user-data": "^0.1.17", - "@mongodb-js/compass-utils": "^0.6.1", - "@mongodb-js/connection-info": "^0.1.5", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-user-data": "^0.1.19", + "@mongodb-js/compass-utils": "^0.6.3", + "@mongodb-js/connection-info": "^0.2.1", "bson": "^6.6.0", - "electron": "^28.2.10", - "hadron-app-registry": "^9.1.8", - "hadron-ipc": "^3.2.12", + "compass-preferences-model": "^2.20.0", + "electron": "^29.3.1", + "hadron-app-registry": "^9.1.10", + "hadron-ipc": "^3.2.14", "keytar": "^7.9.0", "lodash": "^4.17.21", "mongodb-connection-string-url": "^2.6.0", "react": "^17.0.2" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/sinon-chai": "^3.2.5", @@ -48416,13 +48014,13 @@ }, "packages/data-service": { "name": "mongodb-data-service", - "version": "22.18.1", + "version": "22.19.1", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-utils": "^0.6.1", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-utils": "^0.6.3", "@mongodb-js/devtools-connect": "^2.6.0", - "@mongodb-js/ssh-tunnel": "^2.1.13", + "@mongodb-js/ssh-tunnel": "^2.1.15", "bson": "^6.6.0", "lodash": "^4.17.21", "mongodb": "^6.5.0", @@ -48431,15 +48029,14 @@ "mongodb-ns": "^2.4.0" }, "devDependencies": { - "@mongodb-js/compass-test-server": "^0.1.13", + "@mongodb-js/compass-test-server": "^0.1.15", "@mongodb-js/devtools-docker-test-envs": "^1.3.2", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", "@mongodb-js/oidc-plugin": "^0.4.0", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/lodash": "^4.14.188", - "@types/uuid": "^8.3.1", "@types/whatwg-url": "^8.2.1", "chai": "^4.2.0", "chai-as-promised": "^7.1.1", @@ -48451,8 +48048,7 @@ "prettier": "^2.7.1", "sinon": "^9.2.3", "socks": "^2.7.3", - "typescript": "^5.0.4", - "uuid": "^8.3.2" + "typescript": "^5.0.4" }, "optionalDependencies": { "mongodb-client-encryption": "^6.0.0" @@ -48485,28 +48081,19 @@ "node": ">=0.3.1" } }, - "packages/data-service/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, "packages/database-model": { "name": "mongodb-database-model", - "version": "2.18.1", + "version": "2.19.1", "license": "SSPL", "dependencies": { "ampersand-collection": "^2.0.2", "ampersand-model": "^8.0.1", - "mongodb-collection-model": "^5.18.1", - "mongodb-data-service": "^22.18.1" + "mongodb-collection-model": "^5.19.1", + "mongodb-data-service": "^22.19.1" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/prettier-config-compass": "^1.0.1", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/prettier-config-compass": "^1.0.2", "depcheck": "^1.4.1", "eslint": "^7.25.0", "mocha": "^10.2.0" @@ -48563,23 +48150,24 @@ }, "packages/databases-collections": { "name": "@mongodb-js/compass-databases-collections", - "version": "1.23.1", + "version": "1.25.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-app-stores": "^7.10.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-editor": "^0.21.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-workspaces": "^0.5.1", - "@mongodb-js/databases-collections-list": "^1.23.1", - "@mongodb-js/my-queries-storage": "^0.5.1", - "compass-preferences-model": "^2.18.1", - "hadron-app-registry": "^9.1.8", + "@mongodb-js/compass-app-stores": "^7.12.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-editor": "^0.23.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-workspaces": "^0.7.0", + "@mongodb-js/connection-info": "^0.2.1", + "@mongodb-js/databases-collections-list": "^1.25.0", + "@mongodb-js/my-queries-storage": "^0.7.0", + "compass-preferences-model": "^2.20.0", + "hadron-app-registry": "^9.1.10", "lodash": "^4.17.21", - "mongodb-collection-model": "^5.18.1", - "mongodb-database-model": "^2.18.1", - "mongodb-instance-model": "^12.18.1", + "mongodb-collection-model": "^5.19.1", + "mongodb-database-model": "^2.19.1", + "mongodb-instance-model": "^12.20.0", "mongodb-ns": "^2.4.0", "mongodb-query-parser": "^4.1.0", "prop-types": "^15.7.2", @@ -48590,10 +48178,10 @@ "semver": "^5.7.2" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "bson": "^6.6.0", @@ -48610,19 +48198,19 @@ }, "packages/databases-collections-list": { "name": "@mongodb-js/databases-collections-list", - "version": "1.23.1", + "version": "1.25.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-logging": "^1.2.14", - "compass-preferences-model": "^2.18.1", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-logging": "^1.2.16", + "compass-preferences-model": "^2.20.0", "react": "^17.0.2" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "@types/chai": "^4.2.21", @@ -48734,16 +48322,16 @@ }, "packages/explain-plan-helper": { "name": "@mongodb-js/explain-plan-helper", - "version": "1.1.10", + "version": "1.1.12", "license": "SSPL", "dependencies": { - "mongodb-explain-compat": "^3.0.3" + "mongodb-explain-compat": "^3.0.4" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/sinon-chai": "^3.2.5", @@ -48812,7 +48400,7 @@ } }, "packages/hadron-app-registry": { - "version": "9.1.8", + "version": "9.1.10", "license": "SSPL", "dependencies": { "eventemitter3": "^4.0.0", @@ -48822,10 +48410,10 @@ "reflux": "^0.4.1" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", @@ -48868,7 +48456,7 @@ } }, "packages/hadron-build": { - "version": "25.4.9", + "version": "25.4.11", "hasInstallScript": true, "license": "SSPL", "dependencies": { @@ -48887,7 +48475,7 @@ "debug": "^4.2.0", "del": "^2.0.2", "download": "^8.0.0", - "electron": "^28.2.10", + "electron": "^29.3.1", "electron-packager": "^15.5.1", "electron-packager-plugin-non-proprietary-codecs-ffmpeg": "^1.0.2", "flatnest": "^1.0.0", @@ -48902,7 +48490,7 @@ "lodash": "^4.17.21", "moment": "^2.29.4", "mongodb-js-cli": "^0.0.3", - "node-abi": "^3.57.0", + "node-abi": "^3.59.0", "normalize-package-data": "^2.3.5", "parse-github-repo-url": "^1.3.0", "semver": "^5.7.2", @@ -48916,10 +48504,10 @@ "hadron-build": "cli.js" }, "devDependencies": { + "@mongodb-js/eslint-config-compass": "^1.1.1", "chai": "^4.2.0", "depcheck": "^1.4.1", "eslint": "^7.25.0", - "eslint-config-mongodb-js": "^5.0.3", "eslint-plugin-mocha": "^8.0.0", "mocha": "^10.2.0", "plist": "^3.0.1", @@ -49600,19 +49188,19 @@ } }, "packages/hadron-document": { - "version": "8.4.9", + "version": "8.5.1", "license": "SSPL", "dependencies": { "bson": "^6.6.0", "eventemitter3": "^4.0.0", - "hadron-type-checker": "^7.1.2", + "hadron-type-checker": "^7.2.1", "lodash": "^4.17.21" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "chai": "^4.2.0", "debug": "^4.2.0", "depcheck": "^1.4.1", @@ -49730,18 +49318,18 @@ } }, "packages/hadron-ipc": { - "version": "3.2.12", + "version": "3.2.14", "license": "SSPL", "dependencies": { "debug": "^4.3.4", - "electron": "^28.2.10", + "electron": "^29.3.1", "is-electron-renderer": "^2.0.1" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/chai": "^4.2.21", "@types/is-electron-renderer": "^2.0.1", "@types/mocha": "^9.0.0", @@ -49801,7 +49389,7 @@ } }, "packages/hadron-type-checker": { - "version": "7.1.2", + "version": "7.2.1", "license": "SSPL", "dependencies": { "bson": "^6.6.0", @@ -49817,17 +49405,17 @@ }, "packages/instance-model": { "name": "mongodb-instance-model", - "version": "12.18.1", + "version": "12.20.0", "license": "SSPL", "dependencies": { "ampersand-model": "^8.0.1", - "mongodb-collection-model": "^5.18.1", - "mongodb-data-service": "^22.18.1", - "mongodb-database-model": "^2.18.1" + "mongodb-collection-model": "^5.19.1", + "mongodb-data-service": "^22.19.1", + "mongodb-database-model": "^2.19.1" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/prettier-config-compass": "^1.0.1", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/prettier-config-compass": "^1.0.2", "chai": "^4.3.4", "depcheck": "^1.4.1", "eslint": "^7.25.0", @@ -49835,7 +49423,7 @@ } }, "packages/mongodb-explain-compat": { - "version": "3.0.3", + "version": "3.0.4", "license": "SSPL", "devDependencies": { "eslint": "^7.25.0", @@ -49851,17 +49439,17 @@ } }, "packages/mongodb-query-util": { - "version": "2.1.9", + "version": "2.2.1", "license": "SSPL", "dependencies": { "bson": "^6.6.0", "lodash": "^4.17.21" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/sinon-chai": "^3.2.5", @@ -50094,20 +49682,20 @@ }, "packages/my-queries-storage": { "name": "@mongodb-js/my-queries-storage", - "version": "0.5.1", + "version": "0.7.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-editor": "^0.21.1", - "@mongodb-js/compass-user-data": "^0.1.17", + "@mongodb-js/compass-editor": "^0.23.0", + "@mongodb-js/compass-user-data": "^0.1.19", "bson": "^6.6.0", - "hadron-app-registry": "^9.1.8", + "hadron-app-registry": "^9.1.10", "react": "^17.0.2" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/sinon-chai": "^3.2.5", @@ -50658,18 +50246,18 @@ }, "packages/ssh-tunnel": { "name": "@mongodb-js/ssh-tunnel", - "version": "2.1.13", + "version": "2.1.15", "license": "Apache-2.0", "dependencies": { - "@mongodb-js/compass-logging": "^1.2.14", + "@mongodb-js/compass-logging": "^1.2.16", "socksv5": "0.0.6", "ssh2": "^1.12.0" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/chai": "^4.2.21", "@types/chai-as-promised": "^7.1.4", "@types/mocha": "^9.0.0", @@ -50739,13 +50327,13 @@ }, "scripts": { "name": "@mongodb-js/compass-scripts", - "version": "0.16.6", + "version": "0.16.8", "license": "SSPL", "dependencies": { "@babel/core": "^7.24.3", "@mongodb-js/monorepo-tools": "^1.1.1", "commander": "^11.0.0", - "electron": "^28.2.10", + "electron": "^29.3.1", "glob": "^10.2.5", "jsdom": "^21.1.0", "make-fetch-happen": "^8.0.14", @@ -50758,8 +50346,8 @@ "compass-scripts": "cli.js" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/prettier-config-compass": "^1.0.1", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/prettier-config-compass": "^1.0.2", "depcheck": "^1.4.1", "eslint": "^7.25.0", "prettier": "^2.7.1" @@ -54067,12 +53655,6 @@ "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==" }, - "@gribnoysup/mongodb-browser": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@gribnoysup/mongodb-browser/-/mongodb-browser-1.3.0.tgz", - "integrity": "sha512-kvX0Tk0jNT0y4OZ7SUbe0yMC7A00PDOHDMLz0ScUPDmLsPCcPU7mbE3ESUgH98uidCxHGpTkg1LI+dSGOd6gZA==", - "dev": true - }, "@hapi/hoek": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.2.0.tgz", @@ -54899,6 +54481,35 @@ "@leafygreen-ui/tokens": "^2.5.1" } }, + "@leichtgewicht/base64-codec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@leichtgewicht/base64-codec/-/base64-codec-1.0.0.tgz", + "integrity": "sha512-0cgP4lRBzh3F4tlpTfs7F+PJyBN8j5yUC9KrQFWp/bREswgzZVHE8T1rNyRDWgvALwwpPtnJDQfqWUmxI33Epg==", + "dev": true + }, + "@leichtgewicht/dns-packet": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@leichtgewicht/dns-packet/-/dns-packet-6.0.3.tgz", + "integrity": "sha512-qmVHhFBFiBvPsk/wJ/EdoWHb+tGkzY4haybmDPukhF6w0+8wpEbrHTIRE9LzeUu2P0bAbmrK8WOXt5V5QN6jQg==", + "dev": true, + "requires": { + "@leichtgewicht/ip-codec": "^2.0.4", + "bytes.js": "^0.0.2", + "utf8-bytes": "^0.0.1", + "utf8-codec": "^1.0.0", + "utf8-length": "^0.0.1", + "utf8-string-bytes": "^1.0.3" + } + }, + "@leichtgewicht/dns-socket": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@leichtgewicht/dns-socket/-/dns-socket-5.0.0.tgz", + "integrity": "sha512-Sbrn/OG0HTTPGSkwIDCHy8/tUI6UglIzFsMNjzZn/Na1/i5owSm6rVi9CfKNNjRcUlYEzICELYW6EoZdjwVY2A==", + "dev": true, + "requires": { + "@leichtgewicht/dns-packet": "^6.0.0" + } + }, "@leichtgewicht/ip-codec": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", @@ -56117,27 +55728,27 @@ "@mongodb-js/atlas-service": { "version": "file:packages/atlas-service", "requires": { - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-user-data": "^0.1.17", - "@mongodb-js/compass-utils": "^0.6.1", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-user-data": "^0.1.19", + "@mongodb-js/compass-utils": "^0.6.3", "@mongodb-js/devtools-connect": "^2.6.0", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", "@mongodb-js/oidc-plugin": "^0.4.0", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/sinon-chai": "^3.2.5", "chai": "^4.3.6", - "compass-preferences-model": "^2.18.1", + "compass-preferences-model": "^2.20.0", "depcheck": "^1.4.1", - "electron": "^28.2.10", + "electron": "^29.3.1", "eslint": "^7.25.0", - "hadron-app-registry": "^9.1.8", - "hadron-ipc": "^3.2.12", + "hadron-app-registry": "^9.1.10", + "hadron-ipc": "^3.2.14", "lodash": "^4.17.21", "mocha": "^10.2.0", "node-fetch": "^2.7.0", @@ -56190,47 +55801,47 @@ "@dnd-kit/core": "^6.0.7", "@dnd-kit/sortable": "^7.0.2", "@dnd-kit/utilities": "^3.2.1", - "@mongodb-js/atlas-service": "^0.15.1", - "@mongodb-js/compass-app-stores": "^7.10.1", - "@mongodb-js/compass-collection": "^4.23.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-crud": "^13.24.1", - "@mongodb-js/compass-editor": "^0.21.1", - "@mongodb-js/compass-field-store": "^9.0.19", - "@mongodb-js/compass-generative-ai": "^0.8.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-utils": "^0.6.1", - "@mongodb-js/compass-workspaces": "^0.5.1", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/explain-plan-helper": "^1.1.10", - "@mongodb-js/mocha-config-compass": "^1.3.7", + "@mongodb-js/atlas-service": "^0.17.0", + "@mongodb-js/compass-app-stores": "^7.12.0", + "@mongodb-js/compass-collection": "^4.25.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-crud": "^13.26.0", + "@mongodb-js/compass-editor": "^0.23.0", + "@mongodb-js/compass-field-store": "^9.2.0", + "@mongodb-js/compass-generative-ai": "^0.10.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-utils": "^0.6.3", + "@mongodb-js/compass-workspaces": "^0.7.0", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/explain-plan-helper": "^1.1.12", + "@mongodb-js/mocha-config-compass": "^1.3.9", "@mongodb-js/mongodb-constants": "^0.9.0", - "@mongodb-js/my-queries-storage": "^0.5.1", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/my-queries-storage": "^0.7.0", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "@types/lodash": "^4.14.188", "@types/semver": "^7.3.9", "bson": "^6.6.0", "chai": "^4.3.6", - "compass-preferences-model": "^2.18.1", + "compass-preferences-model": "^2.20.0", "depcheck": "^1.4.1", "ejson-shell-parser": "^2.0.1", "electron-mocha": "^12.2.0", "enzyme": "^3.11.0", "eslint": "^7.25.0", - "hadron-app-registry": "^9.1.8", - "hadron-document": "^8.4.9", - "hadron-type-checker": "^7.1.2", + "hadron-app-registry": "^9.1.10", + "hadron-document": "^8.5.1", + "hadron-type-checker": "^7.2.1", "lodash": "^4.17.21", "mocha": "^10.2.0", "mongodb": "^6.5.0", - "mongodb-collection-model": "^5.18.1", - "mongodb-data-service": "^22.18.1", - "mongodb-database-model": "^2.18.1", - "mongodb-instance-model": "^12.18.1", + "mongodb-collection-model": "^5.19.1", + "mongodb-data-service": "^22.19.1", + "mongodb-database-model": "^2.19.1", + "mongodb-instance-model": "^12.20.0", "mongodb-ns": "^2.4.0", "mongodb-query-parser": "^4.1.0", "mongodb-schema": "^12.1.0", @@ -56284,15 +55895,16 @@ "@mongodb-js/compass-app-stores": { "version": "file:packages/compass-app-stores", "requires": { - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/connection-info": "^0.1.5", - "@mongodb-js/connection-storage": "^0.8.1", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/connection-info": "^0.2.1", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", + "@testing-library/dom": "8.20.1", + "@testing-library/react": "12.1.5", "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/sinon-chai": "^3.2.5", @@ -56300,9 +55912,11 @@ "depcheck": "^1.4.1", "electron-mocha": "^12.2.0", "eslint": "^7.25.0", - "hadron-app-registry": "^9.1.8", + "hadron-app-registry": "^9.1.10", "mocha": "^10.2.0", - "mongodb-instance-model": "^12.18.1", + "mongodb-collection-model": "^5.19.1", + "mongodb-database-model": "^2.19.1", + "mongodb-instance-model": "^12.20.0", "mongodb-ns": "^2.4.0", "nyc": "^15.1.0", "prettier": "^2.7.1", @@ -56337,15 +55951,16 @@ "@mongodb-js/compass-collection": { "version": "file:packages/compass-collection", "requires": { - "@mongodb-js/compass-app-stores": "^7.10.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-workspaces": "^0.5.1", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/compass-app-stores": "^7.12.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-workspaces": "^0.7.0", + "@mongodb-js/connection-info": "^0.2.1", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "@types/chai": "^4.2.21", @@ -56356,14 +55971,13 @@ "@types/react-dom": "^17.0.10", "@types/sinon-chai": "^3.2.5", "chai": "^4.3.6", - "compass-preferences-model": "^2.18.1", + "compass-preferences-model": "^2.20.0", "depcheck": "^1.4.1", "electron-mocha": "^12.2.0", "eslint": "^7.25.0", - "hadron-app-registry": "^9.1.8", + "hadron-app-registry": "^9.1.10", "mocha": "^10.2.0", - "mongodb-collection-model": "^5.18.1", - "mongodb-instance-model": "^12.18.1", + "mongodb-collection-model": "^5.19.1", "mongodb-ns": "^2.4.0", "numeral": "^2.0.6", "nyc": "^15.1.0", @@ -56451,10 +56065,10 @@ "@leafygreen-ui/tokens": "^2.5.1", "@leafygreen-ui/tooltip": "^11.0.2", "@leafygreen-ui/typography": "^18.2.3", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@react-aria/interactions": "^3.9.1", "@react-aria/tooltip": "^3.2.1", "@react-aria/utils": "^3.13.1", @@ -56472,8 +56086,8 @@ "chai": "^4.3.4", "eslint": "^7.25.0", "focus-trap-react": "^9.0.2", - "hadron-document": "^8.4.9", - "hadron-type-checker": "^7.1.2", + "hadron-document": "^8.5.1", + "hadron-type-checker": "^7.2.1", "is-electron-renderer": "^2.0.1", "lodash": "^4.17.21", "mocha": "^10.2.0", @@ -56517,12 +56131,13 @@ "@mongodb-js/compass-connection-import-export": { "version": "file:packages/compass-connection-import-export", "requires": { - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/connection-storage": "^0.8.1", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/connection-storage": "^0.10.0", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/react-hooks": "^7.0.2", "@types/chai": "^4.2.21", @@ -56532,11 +56147,11 @@ "@types/react-dom": "^17.0.10", "@types/sinon-chai": "^3.2.5", "chai": "^4.3.6", - "compass-preferences-model": "^2.18.1", + "compass-preferences-model": "^2.20.0", "depcheck": "^1.4.1", "eslint": "^7.25.0", "gen-esm-wrapper": "^1.1.0", - "hadron-ipc": "^3.2.12", + "hadron-ipc": "^3.2.14", "lodash": "^4.17.21", "mocha": "^10.2.0", "nyc": "^15.1.0", @@ -56574,17 +56189,16 @@ "@mongodb-js/compass-connections": { "version": "file:packages/compass-connections", "requires": { - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connection-import-export": "^0.21.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-maybe-protect-connection-string": "^0.16.1", - "@mongodb-js/connection-form": "^1.23.1", - "@mongodb-js/connection-info": "^0.1.5", - "@mongodb-js/connection-storage": "^0.8.1", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-maybe-protect-connection-string": "^0.18.0", + "@mongodb-js/connection-form": "^1.25.0", + "@mongodb-js/connection-info": "^0.2.1", + "@mongodb-js/connection-storage": "^0.10.0", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/dom": "^8.11.1", "@testing-library/react": "^12.1.4", "@testing-library/react-hooks": "^7.0.2", @@ -56597,22 +56211,21 @@ "@types/sinon-chai": "^3.2.5", "bson": "^6.6.0", "chai": "^4.3.4", - "compass-preferences-model": "^2.18.1", + "compass-preferences-model": "^2.20.0", "depcheck": "^1.4.1", "electron-mocha": "^12.2.0", "eslint": "^7.25.0", - "hadron-app-registry": "^9.1.8", + "hadron-app-registry": "^9.1.10", "lodash": "^4.17.21", "mocha": "^10.2.0", "mongodb-build-info": "^1.7.0", "mongodb-connection-string-url": "^2.6.0", - "mongodb-data-service": "^22.18.1", + "mongodb-data-service": "^22.19.1", "nyc": "^15.1.0", "prettier": "^2.7.1", "react": "^17.0.2", "react-dom": "^17.0.2", "sinon": "^9.2.3", - "uuid": "^8.2.0", "xvfb-maybe": "^0.2.1" }, "dependencies": { @@ -56637,33 +56250,88 @@ "dev": true } } - }, - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + } + } + }, + "@mongodb-js/compass-connections-navigation": { + "version": "file:packages/compass-connections-navigation", + "requires": { + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-workspaces": "^0.7.0", + "@mongodb-js/connection-form": "^1.24.0", + "@mongodb-js/connection-info": "^0.2.1", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", + "@testing-library/react": "^12.1.4", + "@testing-library/user-event": "^13.5.0", + "@types/chai": "^4.2.21", + "@types/chai-dom": "^0.0.10", + "@types/mocha": "^9.0.0", + "@types/react": "^17.0.5", + "@types/react-dom": "^17.0.10", + "@types/react-virtualized-auto-sizer": "^1.0.1", + "@types/react-window": "^1.8.5", + "@types/sinon-chai": "^3.2.5", + "chai": "^4.3.4", + "compass-preferences-model": "^2.20.0", + "depcheck": "^1.4.1", + "eslint": "^7.25.0", + "mocha": "^10.2.0", + "nyc": "^15.1.0", + "prettier": "^2.7.1", + "react": "^17.0.2", + "react-dom": "^17.0.2", + "react-virtualized-auto-sizer": "^1.0.6", + "react-window": "^1.8.6", + "sinon": "^9.2.3", + "typescript": "^5.0.4" + }, + "dependencies": { + "sinon": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.4.tgz", + "integrity": "sha512-zljcULZQsJxVra28qIAL6ow1Z9tpattkCTEJR4RBP3TGc00FcttsP5pK284Nas5WjMZU5Yzy3kAIp3B3KRf5Yg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.8.1", + "@sinonjs/fake-timers": "^6.0.1", + "@sinonjs/samsam": "^5.3.1", + "diff": "^4.0.2", + "nise": "^4.0.4", + "supports-color": "^7.1.0" + }, + "dependencies": { + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + } + } } } }, "@mongodb-js/compass-crud": { "version": "file:packages/compass-crud", "requires": { - "@mongodb-js/compass-app-stores": "^7.10.1", - "@mongodb-js/compass-collection": "^4.23.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-editor": "^0.21.1", - "@mongodb-js/compass-field-store": "^9.0.19", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-query-bar": "^8.25.1", - "@mongodb-js/compass-test-server": "^0.1.13", - "@mongodb-js/compass-workspaces": "^0.5.1", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/explain-plan-helper": "^1.1.10", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/my-queries-storage": "^0.5.1", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/compass-app-stores": "^7.12.0", + "@mongodb-js/compass-collection": "^4.25.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-editor": "^0.23.0", + "@mongodb-js/compass-field-store": "^9.2.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-query-bar": "^8.27.0", + "@mongodb-js/compass-test-server": "^0.1.15", + "@mongodb-js/compass-workspaces": "^0.7.0", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/explain-plan-helper": "^1.1.12", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/my-queries-storage": "^0.7.0", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "@types/reflux": "^6.4.3", @@ -56672,21 +56340,21 @@ "bson": "^6.6.0", "chai": "^4.1.2", "chai-as-promised": "^7.1.1", - "compass-preferences-model": "^2.18.1", + "compass-preferences-model": "^2.20.0", "depcheck": "^1.4.1", "ejson-shell-parser": "^2.0.1", - "electron": "^28.2.10", + "electron": "^29.3.1", "electron-mocha": "^12.2.0", "enzyme": "^3.11.0", "eslint": "^7.25.0", - "hadron-app-registry": "^9.1.8", - "hadron-document": "^8.4.9", - "hadron-type-checker": "^7.1.2", + "hadron-app-registry": "^9.1.10", + "hadron-document": "^8.5.1", + "hadron-type-checker": "^7.2.1", "jsondiffpatch": "^0.5.0", "lodash": "^4.17.21", "mocha": "^10.2.0", - "mongodb-data-service": "^22.18.1", - "mongodb-instance-model": "^12.18.1", + "mongodb-data-service": "^22.19.1", + "mongodb-instance-model": "^12.20.0", "mongodb-ns": "^2.4.0", "mongodb-query-parser": "^4.1.0", "nyc": "^15.1.0", @@ -56703,32 +56371,33 @@ "@mongodb-js/compass-databases-collections": { "version": "file:packages/databases-collections", "requires": { - "@mongodb-js/compass-app-stores": "^7.10.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-editor": "^0.21.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-workspaces": "^0.5.1", - "@mongodb-js/databases-collections-list": "^1.23.1", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/my-queries-storage": "^0.5.1", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/compass-app-stores": "^7.12.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-editor": "^0.23.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-workspaces": "^0.7.0", + "@mongodb-js/connection-info": "^0.2.1", + "@mongodb-js/databases-collections-list": "^1.25.0", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/my-queries-storage": "^0.7.0", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "bson": "^6.6.0", "chai": "^4.2.0", - "compass-preferences-model": "^2.18.1", + "compass-preferences-model": "^2.20.0", "depcheck": "^1.4.1", "enzyme": "^3.11.0", "eslint": "^7.25.0", - "hadron-app-registry": "^9.1.8", + "hadron-app-registry": "^9.1.10", "lodash": "^4.17.21", "mocha": "^10.2.0", - "mongodb-collection-model": "^5.18.1", - "mongodb-database-model": "^2.18.1", - "mongodb-instance-model": "^12.18.1", + "mongodb-collection-model": "^5.19.1", + "mongodb-database-model": "^2.19.1", + "mongodb-instance-model": "^12.20.0", "mongodb-ns": "^2.4.0", "mongodb-query-parser": "^4.1.0", "nyc": "^15.1.0", @@ -56770,63 +56439,6 @@ } } }, - "@mongodb-js/compass-databases-navigation": { - "version": "file:packages/compass-databases-navigation", - "requires": { - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", - "@testing-library/react": "^12.1.4", - "@testing-library/user-event": "^13.5.0", - "@types/chai": "^4.2.21", - "@types/chai-dom": "^0.0.10", - "@types/mocha": "^9.0.0", - "@types/react": "^17.0.5", - "@types/react-dom": "^17.0.10", - "@types/react-virtualized-auto-sizer": "^1.0.1", - "@types/react-window": "^1.8.5", - "@types/sinon-chai": "^3.2.5", - "chai": "^4.3.4", - "compass-preferences-model": "^2.18.1", - "depcheck": "^1.4.1", - "eslint": "^7.25.0", - "mocha": "^10.2.0", - "nyc": "^15.1.0", - "prettier": "^2.7.1", - "react": "^17.0.2", - "react-dom": "^17.0.2", - "react-virtualized-auto-sizer": "^1.0.6", - "react-window": "^1.8.6", - "sinon": "^9.2.3", - "typescript": "^5.0.4" - }, - "dependencies": { - "sinon": { - "version": "9.2.4", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.4.tgz", - "integrity": "sha512-zljcULZQsJxVra28qIAL6ow1Z9tpattkCTEJR4RBP3TGc00FcttsP5pK284Nas5WjMZU5Yzy3kAIp3B3KRf5Yg==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.8.1", - "@sinonjs/fake-timers": "^6.0.1", - "@sinonjs/samsam": "^5.3.1", - "diff": "^4.0.2", - "nise": "^4.0.4", - "supports-color": "^7.1.0" - }, - "dependencies": { - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - } - } - } - } - }, "@mongodb-js/compass-editor": { "version": "file:packages/compass-editor", "requires": { @@ -56839,12 +56451,12 @@ "@codemirror/state": "^6.1.4", "@codemirror/view": "^6.7.1", "@lezer/highlight": "^1.1.3", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", "@mongodb-js/mongodb-constants": "^0.9.0", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/prettier": "^2.7.1", @@ -56888,30 +56500,30 @@ "@mongodb-js/compass-explain-plan": { "version": "file:packages/compass-explain-plan", "requires": { - "@mongodb-js/compass-collection": "^4.23.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-editor": "^0.21.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/explain-plan-helper": "^1.1.10", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/compass-collection": "^4.25.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-editor": "^0.23.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/explain-plan-helper": "^1.1.12", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@types/d3": "^3.5.x", "@types/d3-flextree": "^2.1.0", "@types/d3-hierarchy": "^3.1.2", "chai": "^4.2.0", - "compass-preferences-model": "^2.18.1", + "compass-preferences-model": "^2.20.0", "d3": "^3.5.17", "d3-flextree": "^2.1.2", "d3-hierarchy": "^3.1.2", "depcheck": "^1.4.1", - "electron": "^28.2.10", + "electron": "^29.3.1", "electron-mocha": "^12.2.0", "eslint": "^7.25.0", - "hadron-app-registry": "^9.1.8", + "hadron-app-registry": "^9.1.10", "lodash": "^4.17.21", "mocha": "^10.2.0", "mongodb": "^6.5.0", @@ -56957,25 +56569,25 @@ "@mongodb-js/compass-export-to-language": { "version": "file:packages/compass-export-to-language", "requires": { - "@mongodb-js/compass-collection": "^4.23.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-editor": "^0.21.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-maybe-protect-connection-string": "^0.16.1", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/compass-collection": "^4.25.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-editor": "^0.23.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-maybe-protect-connection-string": "^0.18.0", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", - "bson-transpilers": "^3.0.0", + "bson-transpilers": "^3.0.2", "chai": "^4.3.6", - "compass-preferences-model": "^2.18.1", + "compass-preferences-model": "^2.20.0", "depcheck": "^1.4.1", "ejson-shell-parser": "^2.0.1", "eslint": "^7.25.0", - "hadron-app-registry": "^9.1.8", + "hadron-app-registry": "^9.1.10", "mocha": "^10.2.0", "mongodb-ns": "^2.4.0", "nyc": "^15.1.0", @@ -57014,12 +56626,11 @@ "@mongodb-js/compass-field-store": { "version": "file:packages/compass-field-store", "requires": { - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/connection-storage": "^0.8.1", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.5", "@testing-library/react-hooks": "^7.0.2", "@types/chai": "^4.2.21", @@ -57029,7 +56640,7 @@ "depcheck": "^1.4.1", "electron-mocha": "^12.2.0", "eslint": "^7.25.0", - "hadron-app-registry": "^9.1.8", + "hadron-app-registry": "^9.1.10", "lodash": "^4.17.21", "mocha": "^10.2.0", "mongodb-schema": "^12.1.0", @@ -57068,11 +56679,11 @@ "@mongodb-js/compass-find-in-page": { "version": "file:packages/compass-find-in-page", "requires": { - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "@types/chai": "^4.2.21", @@ -57083,11 +56694,11 @@ "@types/sinon-chai": "^3.2.5", "chai": "^4.3.4", "depcheck": "^1.4.1", - "electron": "^28.2.10", + "electron": "^29.3.1", "electron-mocha": "^12.2.0", "eslint": "^7.25.0", - "hadron-app-registry": "^9.1.8", - "hadron-ipc": "^3.2.12", + "hadron-app-registry": "^9.1.10", + "hadron-ipc": "^3.2.14", "mocha": "^10.2.0", "nyc": "^15.1.0", "prettier": "^2.7.1", @@ -57128,14 +56739,14 @@ "@mongodb-js/compass-generative-ai": { "version": "file:packages/compass-generative-ai", "requires": { - "@mongodb-js/atlas-service": "^0.15.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-intercom": "^0.2.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/atlas-service": "^0.17.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-intercom": "^0.4.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "@types/chai": "^4.2.21", @@ -57148,14 +56759,14 @@ "@types/sinon-chai": "^3.2.5", "bson": "^6.6.0", "chai": "^4.3.6", - "compass-preferences-model": "^2.18.1", + "compass-preferences-model": "^2.20.0", "decomment": "^0.9.5", "depcheck": "^1.4.1", "digest-fetch": "^2.0.3", "ejson-shell-parser": "^2.0.1", "electron-mocha": "^12.2.0", "eslint": "^7.25.0", - "hadron-app-registry": "^9.1.8", + "hadron-app-registry": "^9.1.10", "mocha": "^10.2.0", "mongodb": "^6.5.0", "mongodb-runner": "^5.4.4", @@ -57229,17 +56840,17 @@ "version": "file:packages/compass-import-export", "requires": { "@electron/remote": "^2.1.2", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-editor": "^0.21.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-test-server": "^0.1.13", - "@mongodb-js/compass-utils": "^0.6.1", - "@mongodb-js/compass-workspaces": "^0.5.1", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-editor": "^0.23.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-test-server": "^0.1.15", + "@mongodb-js/compass-utils": "^0.6.3", + "@mongodb-js/compass-workspaces": "^0.7.0", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "@types/chai": "^4.2.21", @@ -57255,19 +56866,19 @@ "bson": "^6.6.0", "chai": "^4.3.6", "chai-as-promised": "^7.1.1", - "compass-preferences-model": "^2.18.1", + "compass-preferences-model": "^2.20.0", "debug": "^4.2.0", "depcheck": "^1.4.1", - "electron": "^28.2.10", + "electron": "^29.3.1", "electron-mocha": "^12.2.0", "eslint": "^7.25.0", - "hadron-app-registry": "^9.1.8", - "hadron-document": "^8.4.9", - "hadron-ipc": "^3.2.12", + "hadron-app-registry": "^9.1.10", + "hadron-document": "^8.5.1", + "hadron-ipc": "^3.2.14", "lodash": "^4.17.21", "mocha": "^10.2.0", "mongodb": "^6.5.0", - "mongodb-data-service": "^22.18.1", + "mongodb-data-service": "^22.19.1", "mongodb-ns": "^2.4.0", "mongodb-query-parser": "^4.1.0", "mongodb-schema": "^12.1.0", @@ -57313,35 +56924,35 @@ "@mongodb-js/compass-indexes": { "version": "file:packages/compass-indexes", "requires": { - "@mongodb-js/compass-app-stores": "^7.10.1", - "@mongodb-js/compass-collection": "^4.23.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-editor": "^0.21.1", - "@mongodb-js/compass-field-store": "^9.0.19", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-workspaces": "^0.5.1", - "@mongodb-js/connection-storage": "^0.8.1", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", + "@mongodb-js/compass-app-stores": "^7.12.0", + "@mongodb-js/compass-collection": "^4.25.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-editor": "^0.23.0", + "@mongodb-js/compass-field-store": "^9.2.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-workspaces": "^0.7.0", + "@mongodb-js/connection-storage": "^0.10.0", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", "@mongodb-js/mongodb-constants": "^0.9.0", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "bson": "^6.6.0", "chai": "^4.2.0", - "compass-preferences-model": "^2.18.1", + "compass-preferences-model": "^2.20.0", "depcheck": "^1.4.1", "ejson-shell-parser": "^2.0.1", - "electron": "^28.2.10", + "electron": "^29.3.1", "electron-mocha": "^12.2.0", "eslint": "^7.25.0", - "hadron-app-registry": "^9.1.8", + "hadron-app-registry": "^9.1.10", "lodash": "^4.17.21", "mocha": "^10.2.0", "mongodb": "^6.5.0", - "mongodb-data-service": "^22.18.1", + "mongodb-data-service": "^22.19.1", "mongodb-query-parser": "^4.1.0", "numeral": "^2.0.6", "nyc": "^15.1.0", @@ -57393,16 +57004,16 @@ "@mongodb-js/compass-intercom": { "version": "file:packages/compass-intercom", "requires": { - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/sinon-chai": "^3.2.5", "chai": "^4.3.6", - "compass-preferences-model": "^2.18.1", + "compass-preferences-model": "^2.20.0", "depcheck": "^1.4.1", "eslint": "^7.25.0", "gen-esm-wrapper": "^1.1.0", @@ -57502,10 +57113,10 @@ "@mongodb-js/compass-logging": { "version": "file:packages/compass-logging", "requires": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/chai": "^4.2.21", "@types/debug": "^4.1.9", "@types/mocha": "^9.0.0", @@ -57514,8 +57125,8 @@ "debug": "^4.3.4", "depcheck": "^1.4.1", "eslint": "^7.25.0", - "hadron-app-registry": "^9.1.8", - "hadron-ipc": "^3.2.12", + "hadron-app-registry": "^9.1.10", + "hadron-ipc": "^3.2.14", "is-electron-renderer": "^2.0.1", "mocha": "^10.2.0", "mongodb-log-writer": "^1.3.0", @@ -57561,15 +57172,15 @@ "@mongodb-js/compass-maybe-protect-connection-string": { "version": "file:packages/compass-maybe-protect-connection-string", "requires": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/sinon-chai": "^3.2.5", "chai": "^4.3.6", - "compass-preferences-model": "^2.18.1", + "compass-preferences-model": "^2.20.0", "depcheck": "^1.4.1", "eslint": "^7.25.0", "gen-esm-wrapper": "^1.1.0", @@ -57608,38 +57219,38 @@ "@mongodb-js/compass-query-bar": { "version": "file:packages/compass-query-bar", "requires": { - "@mongodb-js/atlas-service": "^0.15.1", - "@mongodb-js/compass-app-stores": "^7.10.1", - "@mongodb-js/compass-collection": "^4.23.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-editor": "^0.21.1", - "@mongodb-js/compass-field-store": "^9.0.19", - "@mongodb-js/compass-generative-ai": "^0.8.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", + "@mongodb-js/atlas-service": "^0.17.0", + "@mongodb-js/compass-app-stores": "^7.12.0", + "@mongodb-js/compass-collection": "^4.25.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-editor": "^0.23.0", + "@mongodb-js/compass-field-store": "^9.2.0", + "@mongodb-js/compass-generative-ai": "^0.10.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", "@mongodb-js/mongodb-constants": "^0.9.0", - "@mongodb-js/my-queries-storage": "^0.5.1", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/my-queries-storage": "^0.7.0", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "bson": "^6.6.0", "chai": "^4.2.0", - "compass-preferences-model": "^2.18.1", + "compass-preferences-model": "^2.20.0", "depcheck": "^1.4.1", - "electron": "^28.2.10", + "electron": "^29.3.1", "electron-mocha": "^12.2.0", "eslint": "^7.25.0", - "hadron-app-registry": "^9.1.8", + "hadron-app-registry": "^9.1.10", "lodash": "^4.17.21", "mocha": "^10.2.0", "mongodb": "^6.5.0", - "mongodb-instance-model": "^12.18.1", + "mongodb-instance-model": "^12.20.0", "mongodb-ns": "^2.4.0", "mongodb-query-parser": "^4.1.0", - "mongodb-query-util": "^2.1.9", + "mongodb-query-util": "^2.2.1", "mongodb-schema": "^12.1.0", "nyc": "^15.1.0", "react": "^17.0.2", @@ -57677,16 +57288,16 @@ "@mongodb-js/compass-saved-aggregations-queries": { "version": "file:packages/compass-saved-aggregations-queries", "requires": { - "@mongodb-js/compass-app-stores": "^7.10.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-workspaces": "^0.5.1", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/my-queries-storage": "^0.5.1", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/compass-app-stores": "^7.12.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-workspaces": "^0.7.0", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/my-queries-storage": "^0.7.0", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/react-hooks": "^7.0.2", "@testing-library/user-event": "^13.5.0", @@ -57702,7 +57313,7 @@ "electron-mocha": "^12.2.0", "eslint": "^7.25.0", "fuse.js": "^6.5.3", - "hadron-app-registry": "^9.1.8", + "hadron-app-registry": "^9.1.10", "mocha": "^10.2.0", "mongodb-ns": "^2.4.0", "nyc": "^15.1.0", @@ -57744,18 +57355,18 @@ "@mongodb-js/compass-schema": { "version": "file:packages/compass-schema", "requires": { - "@mongodb-js/compass-collection": "^4.23.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-field-store": "^9.0.19", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-query-bar": "^8.25.1", - "@mongodb-js/connection-storage": "^0.8.1", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/my-queries-storage": "^0.5.1", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/compass-collection": "^4.25.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-field-store": "^9.2.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-query-bar": "^8.27.0", + "@mongodb-js/connection-storage": "^0.10.0", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/my-queries-storage": "^0.7.0", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "@types/chai": "^4.2.21", @@ -57766,13 +57377,13 @@ "@types/react-dom": "^17.0.10", "bson": "^6.6.0", "chai": "^4.3.4", - "compass-preferences-model": "^2.18.1", + "compass-preferences-model": "^2.20.0", "d3": "^3.5.17", "depcheck": "^1.4.1", "electron-mocha": "^12.2.0", "eslint": "^7.25.0", - "hadron-app-registry": "^9.1.8", - "hadron-document": "^8.4.9", + "hadron-app-registry": "^9.1.10", + "hadron-document": "^8.5.1", "leaflet": "^1.5.1", "leaflet-defaulticon-compatibility": "^0.1.1", "leaflet-draw": "^1.0.4", @@ -57780,7 +57391,7 @@ "mocha": "^10.2.0", "moment": "^2.29.4", "mongodb": "^6.5.0", - "mongodb-query-util": "^2.1.9", + "mongodb-query-util": "^2.2.1", "mongodb-schema": "^12.1.0", "numeral": "^1.5.6", "nyc": "^15.1.0", @@ -57821,32 +57432,32 @@ "@mongodb-js/compass-schema-validation": { "version": "file:packages/compass-schema-validation", "requires": { - "@mongodb-js/compass-app-stores": "^7.10.1", - "@mongodb-js/compass-collection": "^4.23.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-crud": "^13.24.1", - "@mongodb-js/compass-editor": "^0.21.1", - "@mongodb-js/compass-field-store": "^9.0.19", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/compass-app-stores": "^7.12.0", + "@mongodb-js/compass-collection": "^4.25.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-crud": "^13.26.0", + "@mongodb-js/compass-editor": "^0.23.0", + "@mongodb-js/compass-field-store": "^9.2.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "bson": "^6.6.0", "chai": "^4.2.0", - "compass-preferences-model": "^2.18.1", + "compass-preferences-model": "^2.20.0", "depcheck": "^1.4.1", - "electron": "^28.2.10", + "electron": "^29.3.1", "electron-mocha": "^12.2.0", "enzyme": "^3.11.0", "eslint": "^7.25.0", - "hadron-app-registry": "^9.1.8", - "hadron-ipc": "^3.2.12", + "hadron-app-registry": "^9.1.10", + "hadron-ipc": "^3.2.14", "javascript-stringify": "^2.0.1", "lodash": "^4.17.21", "mocha": "^10.2.0", - "mongodb-instance-model": "^12.18.1", + "mongodb-instance-model": "^12.20.0", "mongodb-ns": "^2.4.0", "mongodb-query-parser": "^4.1.0", "nyc": "^15.1.0", @@ -57872,12 +57483,12 @@ "version": "file:scripts", "requires": { "@babel/core": "^7.24.3", - "@mongodb-js/eslint-config-compass": "^1.0.17", + "@mongodb-js/eslint-config-compass": "^1.1.1", "@mongodb-js/monorepo-tools": "^1.1.1", - "@mongodb-js/prettier-config-compass": "^1.0.1", + "@mongodb-js/prettier-config-compass": "^1.0.2", "commander": "^11.0.0", "depcheck": "^1.4.1", - "electron": "^28.2.10", + "electron": "^29.3.1", "eslint": "^7.25.0", "glob": "^10.2.5", "jsdom": "^21.1.0", @@ -58122,15 +57733,15 @@ "@mongodb-js/compass-serverstats": { "version": "file:packages/compass-serverstats", "requires": { - "@mongodb-js/compass-app-stores": "^7.10.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-workspaces": "^0.5.1", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/compass-app-stores": "^7.12.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-workspaces": "^0.7.0", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/d3": "^3.5.x", "@types/enzyme": "^3.10.14", "chai": "^4.1.2", @@ -58141,7 +57752,7 @@ "electron-mocha": "^12.2.0", "enzyme": "^3.11.0", "eslint": "^7.25.0", - "hadron-app-registry": "^9.1.8", + "hadron-app-registry": "^9.1.10", "lodash": "^4.17.21", "mocha": "^10.2.0", "mongodb-ns": "^2.4.0", @@ -58169,14 +57780,14 @@ "@mongodb-js/compass-settings": { "version": "file:packages/compass-settings", "requires": { - "@mongodb-js/atlas-service": "^0.15.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-generative-ai": "^0.8.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/atlas-service": "^0.17.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-generative-ai": "^0.10.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "@types/chai": "^4.2.21", @@ -58186,12 +57797,12 @@ "@types/react-dom": "^17.0.10", "@types/sinon-chai": "^3.2.5", "chai": "^4.3.6", - "compass-preferences-model": "^2.18.1", + "compass-preferences-model": "^2.20.0", "depcheck": "^1.4.1", "electron-mocha": "^12.2.0", "eslint": "^7.25.0", - "hadron-app-registry": "^9.1.8", - "hadron-ipc": "^3.2.12", + "hadron-app-registry": "^9.1.10", + "hadron-ipc": "^3.2.14", "mocha": "^10.2.0", "nyc": "^15.1.0", "prettier": "^2.7.1", @@ -58232,27 +57843,27 @@ "@mongodb-js/compass-shell": { "version": "file:packages/compass-shell", "requires": { - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-user-data": "^0.1.17", - "@mongodb-js/compass-utils": "^0.6.1", - "@mongodb-js/connection-storage": "^0.8.1", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-user-data": "^0.1.19", + "@mongodb-js/compass-utils": "^0.6.3", + "@mongodb-js/connection-storage": "^0.10.0", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@mongosh/browser-repl": "^2.2.3", "@mongosh/logging": "^2.2.3", "@mongosh/node-runtime-worker-thread": "^2.2.3", "chai": "^4.2.0", - "compass-preferences-model": "^2.18.1", + "compass-preferences-model": "^2.20.0", "depcheck": "^1.4.1", - "electron": "^28.2.10", + "electron": "^29.3.1", "electron-mocha": "^12.2.0", "enzyme": "^3.11.0", "eslint": "^7.25.0", - "hadron-app-registry": "^9.1.8", + "hadron-app-registry": "^9.1.10", "mocha": "^10.2.0", "nyc": "^15.1.0", "prop-types": "^15.7.2", @@ -58289,20 +57900,20 @@ "@mongodb-js/compass-sidebar": { "version": "file:packages/compass-sidebar", "requires": { - "@mongodb-js/compass-app-stores": "^7.10.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-databases-navigation": "^1.23.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-maybe-protect-connection-string": "^0.16.1", - "@mongodb-js/compass-workspaces": "^0.5.1", - "@mongodb-js/connection-form": "^1.23.1", - "@mongodb-js/connection-info": "^0.1.5", - "@mongodb-js/connection-storage": "^0.8.1", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/compass-app-stores": "^7.12.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-connections-navigation": "^1.25.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-maybe-protect-connection-string": "^0.18.0", + "@mongodb-js/compass-workspaces": "^0.7.0", + "@mongodb-js/connection-form": "^1.25.0", + "@mongodb-js/connection-info": "^0.2.1", + "@mongodb-js/connection-storage": "^0.10.0", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "@types/chai": "^4.2.21", @@ -58312,17 +57923,17 @@ "@types/react-dom": "^17.0.10", "@types/sinon-chai": "^3.2.5", "chai": "^4.3.6", - "compass-preferences-model": "^2.18.1", + "compass-preferences-model": "^2.20.0", "debug": "^4.2.0", "depcheck": "^1.4.1", "electron-mocha": "^12.2.0", "eslint": "^7.25.0", - "hadron-app-registry": "^9.1.8", + "hadron-app-registry": "^9.1.10", "lodash": "^4.17.21", "mocha": "^10.2.0", "mongodb": "^6.5.0", - "mongodb-data-service": "^22.18.1", - "mongodb-instance-model": "^12.18.1", + "mongodb-data-service": "^22.19.1", + "mongodb-instance-model": "^12.20.0", "mongodb-ns": "^2.4.0", "nyc": "^15.1.0", "prettier": "^2.7.1", @@ -58363,10 +57974,10 @@ "@mongodb-js/compass-test-server": { "version": "file:packages/compass-test-server", "requires": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/mocha": "^9.0.0", "@types/sinon-chai": "^3.2.5", "depcheck": "^1.4.1", @@ -58405,12 +58016,12 @@ "@mongodb-js/compass-user-data": { "version": "file:packages/compass-user-data", "requires": { - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-utils": "^0.6.1", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-utils": "^0.6.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/sinon-chai": "^3.2.5", @@ -58468,16 +58079,16 @@ "version": "file:packages/compass-utils", "requires": { "@electron/remote": "^2.1.2", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/sinon-chai": "^3.2.5", "chai": "^4.3.6", "depcheck": "^1.4.1", - "electron": "^28.2.10", + "electron": "^29.3.1", "eslint": "^7.25.0", "gen-esm-wrapper": "^1.1.0", "mocha": "^10.2.0", @@ -58514,61 +58125,73 @@ "@mongodb-js/compass-web": { "version": "file:packages/compass-web", "requires": { - "@gribnoysup/mongodb-browser": "^1.3.0", - "@mongodb-js/atlas-service": "^0.15.1", - "@mongodb-js/compass-aggregations": "^9.26.1", - "@mongodb-js/compass-app-stores": "^7.10.1", - "@mongodb-js/compass-collection": "^4.23.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-crud": "^13.24.1", - "@mongodb-js/compass-databases-collections": "^1.23.1", - "@mongodb-js/compass-explain-plan": "^6.24.1", - "@mongodb-js/compass-export-to-language": "^9.0.1", - "@mongodb-js/compass-field-store": "^9.0.19", - "@mongodb-js/compass-generative-ai": "^0.8.1", - "@mongodb-js/compass-indexes": "^5.23.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-query-bar": "^8.25.1", - "@mongodb-js/compass-schema": "^6.25.1", - "@mongodb-js/compass-schema-validation": "^6.24.1", - "@mongodb-js/compass-sidebar": "^5.24.1", - "@mongodb-js/compass-workspaces": "^0.5.1", - "@mongodb-js/connection-storage": "^0.8.1", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", - "@mongodb-js/webpack-config-compass": "^1.3.5", + "@mongodb-js/atlas-service": "^0.17.0", + "@mongodb-js/compass-aggregations": "^9.28.0", + "@mongodb-js/compass-app-stores": "^7.12.0", + "@mongodb-js/compass-collection": "^4.25.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-crud": "^13.26.0", + "@mongodb-js/compass-databases-collections": "^1.25.0", + "@mongodb-js/compass-explain-plan": "^6.26.0", + "@mongodb-js/compass-export-to-language": "^9.2.0", + "@mongodb-js/compass-field-store": "^9.2.0", + "@mongodb-js/compass-generative-ai": "^0.10.0", + "@mongodb-js/compass-indexes": "^5.25.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-query-bar": "^8.27.0", + "@mongodb-js/compass-schema": "^6.27.0", + "@mongodb-js/compass-schema-validation": "^6.26.0", + "@mongodb-js/compass-sidebar": "^5.26.0", + "@mongodb-js/compass-workspaces": "^0.7.0", + "@mongodb-js/connection-storage": "^0.10.0", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", + "@mongodb-js/webpack-config-compass": "^1.3.7", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "@types/chai": "^4.2.21", "@types/chai-dom": "^0.0.10", + "@types/express-http-proxy": "^1.6.6", "@types/mocha": "^9.0.0", "@types/react": "^17.0.5", "@types/react-dom": "^17.0.10", "@types/sinon-chai": "^3.2.5", + "browser-process-hrtime": "^1.0.0", + "bson": "^6.2.0", "buffer": "^6.0.3", "chai": "^4.3.6", - "compass-preferences-model": "^2.18.1", + "compass-preferences-model": "^2.20.0", + "crypto-browserify": "^3.12.0", "debug": "^4.2.0", "depcheck": "^1.4.1", + "dns-query": "^0.11.2", + "electron": "^29.3.1", "eslint": "^7.25.0", "events": "^3.3.0", - "hadron-app-registry": "^9.1.8", + "express": "^4.19.2", + "express-http-proxy": "^2.0.0", + "hadron-app-registry": "^9.1.10", + "is-ip": "^5.0.1", "mocha": "^10.2.0", + "mongodb": "^6.5.0", "mongodb-connection-string-url": "^2.6.0", - "mongodb-data-service": "^22.18.1", + "mongodb-data-service": "^22.19.1", "mongodb-log-writer": "^1.3.0", "nyc": "^15.1.0", + "os-browserify": "^0.3.0", "path-browserify": "^1.0.1", "prettier": "^2.7.1", "process": "^0.11.10", "readable-stream": "^4.5.0", "sinon": "^17.0.1", + "timers-browserify": "^2.0.12", "util": "^0.12.5", "vm-browserify": "^1.1.2", - "whatwg-url": "^13.0.0" + "whatwg-url": "^13.0.0", + "ws": "^8.16.0" }, "dependencies": { "@sinonjs/commons": { @@ -58759,17 +58382,23 @@ "tr46": "^4.1.1", "webidl-conversions": "^7.0.0" } + }, + "ws": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "dev": true } } }, "@mongodb-js/compass-welcome": { "version": "file:packages/compass-welcome", "requires": { - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "@types/chai": "^4.2.21", @@ -58779,7 +58408,7 @@ "@types/react-dom": "^17.0.10", "@types/sinon-chai": "^3.2.5", "chai": "^4.3.6", - "compass-preferences-model": "^2.18.1", + "compass-preferences-model": "^2.20.0", "depcheck": "^1.4.1", "electron-mocha": "^12.2.0", "eslint": "^7.25.0", @@ -58820,14 +58449,14 @@ "@mongodb-js/compass-workspaces": { "version": "file:packages/compass-workspaces", "requires": { - "@mongodb-js/compass-app-stores": "^7.10.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/compass-app-stores": "^7.12.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/react-hooks": "^7.0.2", "@testing-library/user-event": "^13.5.0", @@ -58842,11 +58471,11 @@ "depcheck": "^1.4.1", "electron-mocha": "^12.2.0", "eslint": "^7.25.0", - "hadron-app-registry": "^9.1.8", + "hadron-app-registry": "^9.1.10", "lodash": "^4.17.21", "mocha": "^10.2.0", - "mongodb-collection-model": "^5.18.1", - "mongodb-database-model": "^2.18.1", + "mongodb-collection-model": "^5.19.1", + "mongodb-database-model": "^2.19.1", "mongodb-ns": "^2.4.0", "nyc": "^15.1.0", "prettier": "^2.7.1", @@ -58978,13 +58607,13 @@ "@mongodb-js/connection-form": { "version": "file:packages/connection-form", "requires": { - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-editor": "^0.21.1", - "@mongodb-js/connection-info": "^0.1.5", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-editor": "^0.23.0", + "@mongodb-js/connection-info": "^0.2.1", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/react-hooks": "^7.0.2", "@testing-library/user-event": "^13.5.0", @@ -58996,7 +58625,7 @@ "@types/sinon-chai": "^3.2.5", "bson": "^6.6.0", "chai": "^4.3.4", - "compass-preferences-model": "^2.11.1", + "compass-preferences-model": "^2.20.0", "depcheck": "^1.4.1", "ejson-shell-parser": "^2.0.1", "electron-mocha": "^12.2.0", @@ -59006,7 +58635,7 @@ "mongodb": "^6.5.0", "mongodb-build-info": "^1.7.0", "mongodb-connection-string-url": "^2.6.0", - "mongodb-data-service": "^22.18.1", + "mongodb-data-service": "^22.19.1", "mongodb-query-parser": "^4.1.0", "nyc": "^15.1.0", "prettier": "^2.7.1", @@ -59043,10 +58672,10 @@ "@mongodb-js/connection-info": { "version": "file:packages/connection-info", "requires": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/sinon-chai": "^3.2.5", @@ -59059,7 +58688,7 @@ "mocha": "^10.2.0", "mongodb": "^6.5.0", "mongodb-connection-string-url": "^2.6.0", - "mongodb-data-service": "^22.18.1", + "mongodb-data-service": "^22.19.1", "nyc": "^15.1.0", "prettier": "^2.7.1", "sinon": "^17.0.1", @@ -59175,24 +58804,25 @@ "@mongodb-js/connection-storage": { "version": "file:packages/connection-storage", "requires": { - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-user-data": "^0.1.17", - "@mongodb-js/compass-utils": "^0.6.1", - "@mongodb-js/connection-info": "^0.1.5", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-user-data": "^0.1.19", + "@mongodb-js/compass-utils": "^0.6.3", + "@mongodb-js/connection-info": "^0.2.1", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/sinon-chai": "^3.2.5", "bson": "^6.6.0", "chai": "^4.3.6", + "compass-preferences-model": "^2.20.0", "depcheck": "^1.4.1", - "electron": "^28.2.10", + "electron": "^29.3.1", "eslint": "^7.25.0", - "hadron-app-registry": "^9.1.8", - "hadron-ipc": "^3.2.12", + "hadron-app-registry": "^9.1.10", + "hadron-ipc": "^3.2.14", "keytar": "^7.9.0", "lodash": "^4.17.21", "mocha": "^10.2.0", @@ -59229,12 +58859,12 @@ "@mongodb-js/databases-collections-list": { "version": "file:packages/databases-collections-list", "requires": { - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "@types/chai": "^4.2.21", @@ -59244,7 +58874,7 @@ "@types/react-dom": "^17.0.10", "@types/sinon-chai": "^3.2.5", "chai": "^4.3.4", - "compass-preferences-model": "^2.18.1", + "compass-preferences-model": "^2.20.0", "depcheck": "^1.4.1", "eslint": "^7.25.0", "mocha": "^10.2.0", @@ -59472,7 +59102,7 @@ "@babel/core": "^7.21.4", "@babel/eslint-parser": "^7.14.3", "@mongodb-js/eslint-config-devtools": "^0.9.9", - "@mongodb-js/eslint-plugin-compass": "^1.0.15", + "@mongodb-js/eslint-plugin-compass": "^1.0.17", "@typescript-eslint/eslint-plugin": "^5.59.0", "@typescript-eslint/parser": "^5.59.0", "eslint-config-prettier": "^8.3.0", @@ -59569,8 +59199,8 @@ "@mongodb-js/eslint-plugin-compass": { "version": "file:configs/eslint-plugin-compass", "requires": { - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", "depcheck": "^1.4.1", "eslint": "^7.25.0", "mocha": "^10.2.0", @@ -59581,10 +59211,10 @@ "@mongodb-js/explain-plan-helper": { "version": "file:packages/explain-plan-helper", "requires": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/sinon-chai": "^3.2.5", @@ -59592,7 +59222,7 @@ "depcheck": "^1.4.1", "eslint": "^7.25.0", "mocha": "^10.2.0", - "mongodb-explain-compat": "^3.0.3", + "mongodb-explain-compat": "^3.0.4", "nyc": "^15.1.0", "prettier": "^2.7.1", "sinon": "^9.2.3", @@ -59634,7 +59264,7 @@ "requires": { "@electron/remote": "^2.1.2", "@mongodb-js/mocha-config-devtools": "^1.0.0", - "@mongodb-js/prettier-config-compass": "^1.0.1", + "@mongodb-js/prettier-config-compass": "^1.0.2", "@wojtekmaj/enzyme-adapter-react-17": "^0.8.0", "chai": "^4.3.4", "chai-dom": "^1.9.0", @@ -59809,12 +59439,12 @@ "@mongodb-js/my-queries-storage": { "version": "file:packages/my-queries-storage", "requires": { - "@mongodb-js/compass-editor": "^0.21.1", - "@mongodb-js/compass-user-data": "^0.1.17", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/compass-editor": "^0.23.0", + "@mongodb-js/compass-user-data": "^0.1.19", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/sinon-chai": "^3.2.5", @@ -59823,7 +59453,7 @@ "depcheck": "^1.4.1", "eslint": "^7.25.0", "gen-esm-wrapper": "^1.1.0", - "hadron-app-registry": "^9.1.8", + "hadron-app-registry": "^9.1.10", "mocha": "^10.2.0", "nyc": "^15.1.0", "prettier": "^2.7.1", @@ -60052,11 +59682,11 @@ "@mongodb-js/ssh-tunnel": { "version": "file:packages/ssh-tunnel", "requires": { - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/chai": "^4.2.21", "@types/chai-as-promised": "^7.1.4", "@types/mocha": "^9.0.0", @@ -60115,7 +59745,7 @@ "@mongodb-js/tsconfig-compass": { "version": "file:configs/tsconfig-compass", "requires": { - "@mongodb-js/prettier-config-compass": "^1.0.1", + "@mongodb-js/prettier-config-compass": "^1.0.2", "@mongodb-js/tsconfig-devtools": "^1.0.0", "prettier": "^2.7.1" } @@ -60136,9 +59766,9 @@ "@babel/preset-typescript": "^7.21.4", "@babel/runtime": "^7.21.0", "@cerner/duplicate-package-checker-webpack-plugin": "^2.1.0", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.5", "@types/cli-progress": "^3.9.2", "@types/html-webpack-plugin": "^3.2.6", @@ -60152,7 +59782,7 @@ "core-js": "^3.17.3", "css-loader": "^4.3.0", "depcheck": "^1.4.1", - "electron": "^28.2.10", + "electron": "^29.3.1", "eslint": "^7.25.0", "html-webpack-plugin": "^5.3.2", "less-loader": "^10.0.1", @@ -63999,18 +63629,18 @@ "integrity": "sha512-wDhpKJahGHWhmRt4RxtV3pES63CoeadljGWS/xeS9OJr1HBl2NB+OO44ht3sxDH5j5TRDAbQzC0NvSlsUfn7lQ==" }, "@testing-library/dom": { - "version": "8.11.1", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.11.1.tgz", - "integrity": "sha512-3KQDyx9r0RKYailW2MiYrSSKEfH0GTkI51UGEvJenvcoDoeRYs0PZpi2SXqtnMClQvCqdtTTpOfFETDTVADpAg==", + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.1.tgz", + "integrity": "sha512-/DiOQ5xBxgdYRC8LNk7U+RWat0S3qRLeIw3ZIkMQ9kkVlRmwD/Eg8k8CqIpD6GW7u20JIUOfMKbxtiLutpjQ4g==", "dev": true, "requires": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", - "@types/aria-query": "^4.2.0", - "aria-query": "^5.0.0", + "@types/aria-query": "^5.0.1", + "aria-query": "5.1.3", "chalk": "^4.1.0", "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.4.4", + "lz-string": "^1.5.0", "pretty-format": "^27.0.2" }, "dependencies": { @@ -64057,6 +63687,15 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true }, + "aria-query": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dev": true, + "requires": { + "deep-equal": "^2.0.5" + } + }, "pretty-format": { "version": "27.3.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.3.1.tgz", @@ -64172,9 +63811,9 @@ } }, "@types/aria-query": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz", - "integrity": "sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true }, "@types/babel__core": { @@ -64414,6 +64053,15 @@ "@types/serve-static": "*" } }, + "@types/express-http-proxy": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/@types/express-http-proxy/-/express-http-proxy-1.6.6.tgz", + "integrity": "sha512-J8ZqHG76rq1UB716IZ3RCmUhg406pbWxsM3oFCFccl5xlWUPzoR4if6Og/cE4juK8emH0H9quZa5ltn6ZdmQJg==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, "@types/express-serve-static-core": { "version": "4.17.28", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz", @@ -64932,12 +64580,6 @@ "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" }, - "@types/uuid": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.1.tgz", - "integrity": "sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg==", - "dev": true - }, "@types/webidl-conversions": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz", @@ -66666,12 +66308,12 @@ "integrity": "sha512-F2+Hkm9xFaRg+GkaNnbwXNDV5O6pnCFEmqyhvfC/Ic5LbgOWjJh3L+mN/s91rxVL3znE7DYVpW0GJFT+4YBgWw==" }, "aria-query": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", - "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", "dev": true, "requires": { - "deep-equal": "^2.0.5" + "dequal": "^2.0.3" } }, "array-buffer-byte-length": { @@ -66746,16 +66388,6 @@ "is-string": "^1.0.5" } }, - "array.prototype.find": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.1.1.tgz", - "integrity": "sha512-mi+MYNJYLTx2eNYy+Yh6raoQacCsNeeMUaspFPh9Y141lFSsWxxB8V9mM2ye+eqiRs917J6/pJ4M9ZPzenWckA==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.4" - } - }, "array.prototype.flat": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz", @@ -66809,6 +66441,25 @@ "safer-buffer": "~2.1.0" } }, + "asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } + }, "assert": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", @@ -66986,62 +66637,6 @@ "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==", "dev": true }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "babel-eslint": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-7.2.3.tgz", - "integrity": "sha512-i2yKOhjgwUbUrJ8oJm6QqRzltIoFahGNPZ0HF22lUN4H1DW03JQyJm7WSv+I1LURQWjDNhVqFo04acYa07rhOQ==", - "dev": true, - "requires": { - "babel-code-frame": "^6.22.0", - "babel-traverse": "^6.23.1", - "babel-types": "^6.23.0", - "babylon": "^6.17.0" - } - }, "babel-loader": { "version": "7.1.5", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-7.1.5.tgz", @@ -67148,15 +66743,6 @@ } } }, - "babel-messages": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", - "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, "babel-plugin-istanbul": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-5.2.0.tgz", @@ -67296,96 +66882,6 @@ "@babel/helper-define-polyfill-provider": "^0.3.3" } }, - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - }, - "dependencies": { - "core-js": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", - "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", - "dev": true - }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true - } - } - }, - "babel-traverse": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", - "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", - "dev": true, - "requires": { - "babel-code-frame": "^6.26.0", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "debug": "^2.6.8", - "globals": "^9.18.0", - "invariant": "^2.2.2", - "lodash": "^4.17.4" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" - }, - "dependencies": { - "to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", - "dev": true - } - } - }, - "babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", - "dev": true - }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -67542,6 +67038,12 @@ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, + "bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true + }, "body-parser": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", @@ -67658,12 +67160,131 @@ "fill-range": "^7.0.1" } }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "dev": true + }, + "browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", + "dev": true + }, "browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "requires": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "requires": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "browserify-rsa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", + "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", + "dev": true, + "requires": { + "bn.js": "^5.0.0", + "randombytes": "^2.0.1" + } + }, + "browserify-sign": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.3.tgz", + "integrity": "sha512-JWCZW6SKhfhjJxO8Tyiiy+XYB7cqd2S5/+WeYHsKdNKFlCBhKbblba1A/HN/90YwtxKc8tCErjffZl++UNmGiw==", + "dev": true, + "requires": { + "bn.js": "^5.2.1", + "browserify-rsa": "^4.1.0", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.5", + "hash-base": "~3.0", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.7", + "readable-stream": "^2.3.8", + "safe-buffer": "^5.2.1" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + } + } + }, "browserslist": { "version": "4.23.0", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", @@ -67683,512 +67304,14 @@ "bson-transpilers": { "version": "file:packages/bson-transpilers", "requires": { + "@mongodb-js/eslint-config-compass": "^1.1.1", "antlr4": "4.7.2", "bson": "^6.2.0", "chai": "^4.3.4", "depcheck": "^1.4.1", "eslint": "^7.25.0", - "eslint-config-mongodb-js": "^2.1.0", "js-yaml": "^3.13.1", "mocha": "^10.2.0" - }, - "dependencies": { - "acorn": { - "version": "5.7.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", - "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", - "dev": true - }, - "acorn-jsx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", - "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", - "dev": true, - "requires": { - "acorn": "^3.0.4" - }, - "dependencies": { - "acorn": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", - "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", - "dev": true - } - } - }, - "ajv": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", - "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", - "dev": true, - "requires": { - "co": "^4.6.0", - "json-stable-stringify": "^1.0.1" - } - }, - "ajv-keywords": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", - "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", - "dev": true - }, - "ansi-escapes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", - "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "cli-cursor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", - "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", - "dev": true, - "requires": { - "restore-cursor": "^1.0.1" - } - }, - "cli-width": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", - "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", - "dev": true - }, - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "eslint-config-mongodb-js": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/eslint-config-mongodb-js/-/eslint-config-mongodb-js-2.3.0.tgz", - "integrity": "sha512-9zxJawyp68GNX63pfqeLV47/ShSyY7Hce3l/XhrD8dihFhygs+5C7lk12ogDePK3OmOer1pREvwgR8q0YvV4Pw==", - "dev": true, - "requires": { - "babel-eslint": "^7.1.0", - "eslint": "^3.3.1", - "eslint-plugin-chai-friendly": "^0.4.0", - "eslint-plugin-react": "^6.1.2" - }, - "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "eslint": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-3.19.0.tgz", - "integrity": "sha1-yPxiAcf0DdCJQbh8CFdnOGpnmsw=", - "dev": true, - "requires": { - "babel-code-frame": "^6.16.0", - "chalk": "^1.1.3", - "concat-stream": "^1.5.2", - "debug": "^2.1.1", - "doctrine": "^2.0.0", - "escope": "^3.6.0", - "espree": "^3.4.0", - "esquery": "^1.0.0", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "file-entry-cache": "^2.0.0", - "glob": "^7.0.3", - "globals": "^9.14.0", - "ignore": "^3.2.0", - "imurmurhash": "^0.1.4", - "inquirer": "^0.12.0", - "is-my-json-valid": "^2.10.0", - "is-resolvable": "^1.0.0", - "js-yaml": "^3.5.1", - "json-stable-stringify": "^1.0.0", - "levn": "^0.3.0", - "lodash": "^4.0.0", - "mkdirp": "^0.5.0", - "natural-compare": "^1.4.0", - "optionator": "^0.8.2", - "path-is-inside": "^1.0.1", - "pluralize": "^1.2.1", - "progress": "^1.1.8", - "require-uncached": "^1.0.2", - "shelljs": "^0.7.5", - "strip-bom": "^3.0.0", - "strip-json-comments": "~2.0.1", - "table": "^3.7.8", - "text-table": "~0.2.0", - "user-home": "^2.0.0" - } - }, - "eslint-plugin-react": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-6.10.3.tgz", - "integrity": "sha1-xUNb6wZ3ThLH2y9qut3L+QDNP3g=", - "dev": true, - "requires": { - "array.prototype.find": "^2.0.1", - "doctrine": "^1.2.2", - "has": "^1.0.1", - "jsx-ast-utils": "^1.3.4", - "object.assign": "^4.0.4" - }, - "dependencies": { - "doctrine": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", - "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "isarray": "^1.0.0" - } - } - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "espree": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", - "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", - "dev": true, - "requires": { - "acorn": "^5.5.0", - "acorn-jsx": "^3.0.0" - } - }, - "figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5", - "object-assign": "^4.1.0" - } - }, - "file-entry-cache": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", - "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", - "dev": true, - "requires": { - "flat-cache": "^1.2.1", - "object-assign": "^4.0.1" - } - }, - "flat-cache": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", - "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", - "dev": true, - "requires": { - "circular-json": "^0.3.1", - "graceful-fs": "^4.1.2", - "rimraf": "~2.6.2", - "write": "^0.2.1" - } - }, - "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true - }, - "ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", - "dev": true - }, - "inquirer": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz", - "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=", - "dev": true, - "requires": { - "ansi-escapes": "^1.1.0", - "ansi-regex": "^2.0.0", - "chalk": "^1.0.0", - "cli-cursor": "^1.0.1", - "cli-width": "^2.0.0", - "figures": "^1.3.5", - "lodash": "^4.3.0", - "readline2": "^1.0.1", - "run-async": "^0.1.0", - "rx-lite": "^3.1.2", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.0", - "through": "^2.3.6" - }, - "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "jsx-ast-utils": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-1.4.1.tgz", - "integrity": "sha1-OGchPo3Xm/Ho8jAMDPwe+xgsDfE=", - "dev": true - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "onetime": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", - "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", - "dev": true - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "progress": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", - "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", - "dev": true - }, - "restore-cursor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", - "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", - "dev": true, - "requires": { - "exit-hook": "^1.0.0", - "onetime": "^1.0.0" - } - }, - "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "run-async": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", - "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=", - "dev": true, - "requires": { - "once": "^1.3.0" - } - }, - "slice-ansi": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", - "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", - "dev": true - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - }, - "table": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/table/-/table-3.8.3.tgz", - "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=", - "dev": true, - "requires": { - "ajv": "^4.7.0", - "ajv-keywords": "^1.0.0", - "chalk": "^1.1.1", - "lodash": "^4.0.0", - "slice-ansi": "0.0.4", - "string-width": "^2.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "dependencies": { - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "write": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", - "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", - "dev": true, - "requires": { - "mkdirp": "^0.5.1" - } - } } }, "buffer": { @@ -68240,6 +67363,12 @@ "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==", "dev": true }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", + "dev": true + }, "buffers": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", @@ -68276,6 +67405,12 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" }, + "bytes.js": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/bytes.js/-/bytes.js-0.0.2.tgz", + "integrity": "sha512-KrLm4hv5Qs9w6b0U7h1bCdqxrsf+e9QMsfHeyQFzAz94x/5Aqa+FTEUSNBtt5d2VuV3Hfiea3c4ti74RZDDYkg==", + "dev": true + }, "cacache": { "version": "15.2.0", "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.2.0.tgz", @@ -68583,11 +67718,15 @@ "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", "dev": true }, - "circular-json": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", - "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", - "dev": true + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } }, "clean-css": { "version": "4.2.3", @@ -68727,6 +67866,15 @@ } } }, + "clone-regexp": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-3.0.0.tgz", + "integrity": "sha512-ujdnoq2Kxb8s3ItNBtnYeXdm07FcU0u8ARAT1lQ2YdMwQC+cdiXX8KoqMVuglztILivceTtp4ivqGSmEmhBUJw==", + "dev": true, + "requires": { + "is-regexp": "^3.0.0" + } + }, "clone-response": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", @@ -68777,12 +67925,6 @@ "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-6.0.1.tgz", "integrity": "sha512-S9iI9y0nKR4hwEQsVWpyxld/6kRfGepGfzff83FcaiEBpmvlbA2nnGe7Cylgrx2f/p1P5S5wpRm9oL8z1PbS3Q==" }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", @@ -68890,11 +68032,11 @@ "version": "file:packages/compass-e2e-tests", "requires": { "@electron/rebuild": "^3.6.0", - "@mongodb-js/compass-test-server": "^0.1.13", - "@mongodb-js/eslint-config-compass": "^1.0.17", + "@mongodb-js/compass-test-server": "^0.1.15", + "@mongodb-js/eslint-config-compass": "^1.1.1", "@mongodb-js/oidc-mock-provider": "^0.9.0", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/chai-as-promised": "^7.1.4", "@types/cross-spawn": "^6.0.2", "@types/puppeteer": "^5.4.4", @@ -68903,15 +68045,15 @@ "chai": "^4.3.4", "chai-as-promised": "^7.1.1", "clipboardy": "^2.3.0", - "compass-preferences-model": "^2.18.1", + "compass-preferences-model": "^2.20.0", "cross-spawn": "^7.0.3", "debug": "^4.2.0", "depcheck": "^1.4.1", - "electron": "^28.2.10", + "electron": "^29.3.1", "eslint": "^7.25.0", "fast-glob": "^3.2.7", "glob": "^10.2.5", - "hadron-build": "^25.4.9", + "hadron-build": "^25.4.11", "lodash": "^4.17.21", "mocha": "^10.2.0", "mongodb": "^6.5.0", @@ -69103,10 +68245,10 @@ "compass-preferences-model": { "version": "file:packages/compass-preferences-model", "requires": { - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-user-data": "^0.1.17", - "@mongodb-js/eslint-config-compass": "^1.0.16", - "@mongodb-js/mocha-config-compass": "^1.3.6", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-user-data": "^0.1.19", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", "@testing-library/react": "^12.1.4", "@types/js-yaml": "^4.0.5", "@types/yargs-parser": "21.0.0", @@ -69114,8 +68256,8 @@ "chai": "^4.3.6", "depcheck": "^1.4.1", "eslint": "^7.25.0", - "hadron-app-registry": "^9.1.8", - "hadron-ipc": "^3.2.12", + "hadron-app-registry": "^9.1.10", + "hadron-ipc": "^3.2.14", "js-yaml": "^4.1.0", "lodash": "^4.17.21", "mocha": "^10.2.0", @@ -69473,6 +68615,12 @@ } } }, + "convert-hrtime": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/convert-hrtime/-/convert-hrtime-5.0.0.tgz", + "integrity": "sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==", + "dev": true + }, "convert-source-map": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", @@ -69589,6 +68737,51 @@ } } }, + "create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, "create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -69701,6 +68894,25 @@ "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=", "dev": true }, + "crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "requires": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + } + }, "css-blank-pseudo": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz", @@ -70557,6 +69769,22 @@ "integrity": "sha1-UYZnt2kUYKXn4KNBvnbrfOgJAYQ=", "dev": true }, + "dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true + }, + "des.js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", + "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, "destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -70595,6 +69823,25 @@ "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", "dev": true }, + "diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } + }, "difflib": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/difflib/-/difflib-0.2.4.tgz", @@ -70662,6 +69909,19 @@ "@leichtgewicht/ip-codec": "^2.0.1" } }, + "dns-query": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/dns-query/-/dns-query-0.11.2.tgz", + "integrity": "sha512-zF8qxQpqCB467o4A63DLpQClo77H642JEKMx0Ra9GFww7Rx0234Fo8NoG0LBoSBZxamWkXfLxhzDG19bTBHvXQ==", + "dev": true, + "requires": { + "@leichtgewicht/base64-codec": "^1.0.0", + "@leichtgewicht/dns-packet": "^6.0.2", + "@leichtgewicht/dns-socket": "^5.0.0", + "@leichtgewicht/ip-codec": "^2.0.4", + "utf8-codec": "^1.0.0" + } + }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -71160,12 +70420,12 @@ } }, "electron": { - "version": "28.2.10", - "resolved": "https://registry.npmjs.org/electron/-/electron-28.2.10.tgz", - "integrity": "sha512-0rGBJNogcl2FIRxGRUv9zuMaBP78nSBJW+Bd1U7OGeg8IEkSIbHOhfn71XoGxgbOUSCEXjjyftq4mtAAVbUsZQ==", + "version": "29.3.1", + "resolved": "https://registry.npmjs.org/electron/-/electron-29.3.1.tgz", + "integrity": "sha512-auge1/6RVqgUd6TgIq88wKdUCJi2cjESi3jy7d+6X4JzvBGprKBqMJ8JSSFpu/Px1YJrFUKAxfy6SC+TQf1uLw==", "requires": { "@electron/get": "^2.0.0", - "@types/node": "^18.11.18", + "@types/node": "^20.9.0", "extract-zip": "^2.0.1" }, "dependencies": { @@ -71189,11 +70449,6 @@ "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==" }, - "@types/node": { - "version": "18.18.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.3.tgz", - "integrity": "sha512-0OVfGupTl3NBFr8+iXpfZ8NR7jfFO+P1Q+IO/q0wbo02wYkP5gy36phojeYWpLQ6WAMjl+VfmqUk2YbUfp0irA==" - }, "cacheable-lookup": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", @@ -71607,6 +70862,29 @@ } } }, + "elliptic": { + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.5.tgz", + "integrity": "sha512-7EjbcmUm17NQFu4Pmgmq2olYMj8nwMnpcddByChSUjArp8F5DQWcIcpriwO4ZToLNAJig0yiyjswfyGNje/ixw==", + "dev": true, + "requires": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } + }, "email-validator": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/email-validator/-/email-validator-2.0.4.tgz", @@ -71899,78 +71177,11 @@ } } }, - "es6-map": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", - "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.14", - "es6-iterator": "~2.0.1", - "es6-set": "~0.1.5", - "es6-symbol": "~3.1.1", - "event-emitter": "~0.3.5" - }, - "dependencies": { - "d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dev": true, - "requires": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, - "type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", - "dev": true - } - } - }, - "es6-set": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", - "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.14", - "es6-iterator": "~2.0.1", - "es6-symbol": "3.1.1", - "event-emitter": "~0.3.5" - }, - "dependencies": { - "d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dev": true, - "requires": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, - "es6-symbol": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", - "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, - "type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", - "dev": true - } - } + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "dev": true }, "es6-symbol": { "version": "3.1.3", @@ -72068,48 +71279,6 @@ } } }, - "escope": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", - "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", - "dev": true, - "requires": { - "es6-map": "^0.1.3", - "es6-weak-map": "^2.0.1", - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - }, - "dependencies": { - "d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dev": true, - "requires": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, - "es6-weak-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", - "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.46", - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.1" - } - }, - "type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", - "dev": true - } - } - }, "eslint": { "version": "7.32.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", @@ -73236,6 +72405,16 @@ "delegate-events": "^1.1.0" } }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "requires": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, "execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -73257,12 +72436,6 @@ "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=" }, - "exit-hook": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", - "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", - "dev": true - }, "expand-template": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", @@ -73356,6 +72529,28 @@ } } }, + "express-http-proxy": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/express-http-proxy/-/express-http-proxy-2.0.0.tgz", + "integrity": "sha512-TXxcPFTWVUMSEmyM6iX2sT/JtmqhqngTq29P+eXTVFdtxZrTmM8THUYK59rUXiln0FfPGvxEpGRnVrgvHksXDw==", + "dev": true, + "requires": { + "debug": "^3.0.1", + "es6-promise": "^4.1.1", + "raw-body": "^2.3.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, "ext": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", @@ -73939,6 +73134,12 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" }, + "function-timeout": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/function-timeout/-/function-timeout-0.1.1.tgz", + "integrity": "sha512-0NVVC0TaP7dSTvn1yMiy6d6Q8gifzbvQafO46RtLG/kHJUBNd+pVRGOBoK44wNBvtSPUJRfdVvkFdD3p0xvyZg==", + "dev": true + }, "function.prototype.name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.4.tgz", @@ -74420,7 +73621,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", - "devOptional": true, + "optional": true, "requires": { "is-property": "^1.0.2" } @@ -74429,7 +73630,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", - "devOptional": true, + "optional": true, "requires": { "is-property": "^1.0.0" } @@ -75013,10 +74214,10 @@ "hadron-app-registry": { "version": "file:packages/hadron-app-registry", "requires": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", @@ -75065,6 +74266,7 @@ "@mongodb-js/devtools-github-repo": "^1.4.1", "@mongodb-js/dl-center": "^1.0.1", "@mongodb-js/electron-wix-msi": "^3.0.0", + "@mongodb-js/eslint-config-compass": "^1.1.1", "@mongodb-js/signing-utils": "^0.3.1", "@npmcli/arborist": "^6.2.0", "@octokit/rest": "^18.6.2", @@ -75078,7 +74280,7 @@ "del": "^2.0.2", "depcheck": "^1.4.1", "download": "^8.0.0", - "electron": "^28.2.10", + "electron": "^29.3.1", "electron-installer-debian": "^3.2.0", "electron-installer-dmg": "^4.0.0", "electron-installer-redhat": "^2.0.0", @@ -75086,7 +74288,6 @@ "electron-packager-plugin-non-proprietary-codecs-ffmpeg": "^1.0.2", "electron-winstaller": "^5.1.0", "eslint": "^7.25.0", - "eslint-config-mongodb-js": "^5.0.3", "eslint-plugin-mocha": "^8.0.0", "flatnest": "^1.0.0", "fs-extra": "^8.1.0", @@ -75101,7 +74302,7 @@ "mocha": "^10.2.0", "moment": "^2.29.4", "mongodb-js-cli": "^0.0.3", - "node-abi": "^3.57.0", + "node-abi": "^3.59.0", "normalize-package-data": "^2.3.5", "parse-github-repo-url": "^1.3.0", "plist": "^3.0.1", @@ -75660,10 +74861,10 @@ "hadron-document": { "version": "file:packages/hadron-document", "requires": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "bson": "^6.6.0", "chai": "^4.2.0", "debug": "^4.2.0", @@ -75671,7 +74872,7 @@ "eslint": "^7.25.0", "eslint-config-mongodb-js": "^5.0.3", "eventemitter3": "^4.0.0", - "hadron-type-checker": "^7.1.2", + "hadron-type-checker": "^7.2.1", "lodash": "^4.17.21", "mocha": "^10.2.0", "moment": "^2.29.4", @@ -75788,10 +74989,10 @@ "hadron-ipc": { "version": "file:packages/hadron-ipc", "requires": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/chai": "^4.2.21", "@types/is-electron-renderer": "^2.0.1", "@types/mocha": "^9.0.0", @@ -75799,7 +75000,7 @@ "chai": "^4.3.6", "debug": "^4.3.4", "depcheck": "^1.4.1", - "electron": "^28.2.10", + "electron": "^29.3.1", "eslint": "^7.25.0", "is-electron-renderer": "^2.0.1", "mocha": "^10.2.0", @@ -75964,12 +75165,32 @@ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" }, + "hash-base": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha512-EeeoJKjTyt868liAlVmcv2ZsUfGHlE3Q+BICOXcZiwN3osr5Q/zFGYmTJpoIzuaSTAwndFy+GqhEwlU4L3j4Ow==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "hash-sum": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-2.0.0.tgz", "integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==", "dev": true }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, "hasha": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", @@ -76021,6 +75242,17 @@ "resolved": "https://registry.npmjs.org/highlightjs-graphql/-/highlightjs-graphql-1.0.2.tgz", "integrity": "sha512-jShTftpKQDwMXc+7OHOpHXRYSweT08EO2YOIcLbwU00e9yuwJMYXGLF1eiDO0aUPeQU4/5EjAh5HtPt3ly7rvg==" }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "dev": true, + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, "hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -76677,12 +75909,6 @@ "side-channel": "^1.0.4" } }, - "interpret": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", - "dev": true - }, "interruptor": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/interruptor/-/interruptor-1.0.1.tgz", @@ -76750,6 +75976,12 @@ "ipaddr.js": "^1.0.1" } }, + "ip-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-5.0.0.tgz", + "integrity": "sha512-fOCG6lhoKKakwv+C6KdsOnGvgXnmgfmp0myi3bcNwj3qfwPAxRKWEuFhvEFF7ceYIz6+1jRZ+yguLFAmUNPEfw==", + "dev": true + }, "ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -76896,6 +76128,16 @@ "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==" }, + "is-ip": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/is-ip/-/is-ip-5.0.1.tgz", + "integrity": "sha512-FCsGHdlrOnZQcp0+XT5a+pYowf33itBalCl+7ovNXC/7o5BhIpG14M3OrpPPdBSIQJCm+0M5+9mO7S9VVTTCFw==", + "dev": true, + "requires": { + "ip-regex": "^5.0.0", + "super-regex": "^0.2.0" + } + }, "is-lambda": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", @@ -76911,13 +76153,13 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==", - "devOptional": true + "optional": true }, "is-my-json-valid": { "version": "2.20.5", "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.20.5.tgz", "integrity": "sha512-VTPuvvGQtxvCeghwspQu1rBgjYUT6FGxPlvFKbYuFtgc4ADsX3U5ihZOYN0qyU6u+d4X9xXb0IT5O6QpXKt87A==", - "devOptional": true, + "optional": true, "requires": { "generate-function": "^2.0.0", "generate-object-property": "^1.1.0", @@ -77002,7 +76244,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", - "devOptional": true + "optional": true }, "is-regex": { "version": "1.1.4", @@ -77013,10 +76255,10 @@ "has-tostringtag": "^1.0.0" } }, - "is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "is-regexp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-3.1.0.tgz", + "integrity": "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==", "dev": true }, "is-retry-allowed": { @@ -77649,15 +76891,6 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, - "json-stable-stringify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", - "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", - "dev": true, - "requires": { - "jsonify": "~0.0.0" - } - }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -77714,12 +76947,6 @@ "universalify": "^2.0.0" } }, - "jsonify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", - "dev": true - }, "jsonparse": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", @@ -77729,7 +76956,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.1.0.tgz", "integrity": "sha512-CXcRvMyTlnR53xMcKnuMzfCA5i/nfblTnnr74CZb6C4vG39eu6w51t7nKmU5MfLfbTgGItliNyjO/ciNPDqClg==", - "devOptional": true + "optional": true }, "JSONStream": { "version": "1.3.5", @@ -80248,6 +79475,17 @@ "is-buffer": "~1.1.6" } }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, "media-type": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-type/-/media-type-0.3.0.tgz", @@ -80456,6 +79694,24 @@ "picomatch": "^2.2.3" } }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } + }, "mime": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", @@ -80503,6 +79759,12 @@ "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "dev": true + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -80935,15 +80197,15 @@ "mongodb-collection-model": { "version": "file:packages/collection-model", "requires": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/prettier-config-compass": "^1.0.1", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/prettier-config-compass": "^1.0.2", "ampersand-collection": "^2.0.2", "ampersand-model": "^8.0.1", "depcheck": "^1.4.1", "electron-mocha": "^12.2.0", "eslint": "^7.25.0", "mocha": "^10.2.0", - "mongodb-data-service": "^22.18.1", + "mongodb-data-service": "^22.19.1", "mongodb-ns": "^2.4.0", "xvfb-maybe": "^0.2.1" } @@ -80953,68 +80215,68 @@ "requires": { "@electron/rebuild": "^3.6.0", "@electron/remote": "^2.1.2", - "@mongodb-js/atlas-service": "^0.15.1", - "@mongodb-js/compass-aggregations": "^9.26.1", - "@mongodb-js/compass-app-stores": "^7.10.1", - "@mongodb-js/compass-collection": "^4.23.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-crud": "^13.24.1", - "@mongodb-js/compass-databases-collections": "^1.23.1", - "@mongodb-js/compass-explain-plan": "^6.24.1", - "@mongodb-js/compass-export-to-language": "^9.0.1", - "@mongodb-js/compass-field-store": "^9.0.19", - "@mongodb-js/compass-find-in-page": "^4.22.1", - "@mongodb-js/compass-generative-ai": "^0.8.1", - "@mongodb-js/compass-import-export": "^7.23.1", - "@mongodb-js/compass-indexes": "^5.23.1", - "@mongodb-js/compass-intercom": "^0.2.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-query-bar": "^8.25.1", - "@mongodb-js/compass-saved-aggregations-queries": "^1.24.1", - "@mongodb-js/compass-schema": "^6.25.1", - "@mongodb-js/compass-schema-validation": "^6.24.1", - "@mongodb-js/compass-serverstats": "^16.23.1", - "@mongodb-js/compass-settings": "^0.26.1", - "@mongodb-js/compass-shell": "^3.23.1", - "@mongodb-js/compass-sidebar": "^5.24.1", - "@mongodb-js/compass-utils": "^0.6.1", - "@mongodb-js/compass-welcome": "^0.22.1", - "@mongodb-js/compass-workspaces": "^0.5.1", - "@mongodb-js/connection-info": "^0.1.5", - "@mongodb-js/connection-storage": "^0.8.1", - "@mongodb-js/eslint-config-compass": "^1.0.17", + "@mongodb-js/atlas-service": "^0.17.0", + "@mongodb-js/compass-aggregations": "^9.28.0", + "@mongodb-js/compass-app-stores": "^7.12.0", + "@mongodb-js/compass-collection": "^4.25.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connection-import-export": "^0.23.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-crud": "^13.26.0", + "@mongodb-js/compass-databases-collections": "^1.25.0", + "@mongodb-js/compass-explain-plan": "^6.26.0", + "@mongodb-js/compass-export-to-language": "^9.2.0", + "@mongodb-js/compass-field-store": "^9.2.0", + "@mongodb-js/compass-find-in-page": "^4.24.0", + "@mongodb-js/compass-generative-ai": "^0.10.0", + "@mongodb-js/compass-import-export": "^7.25.0", + "@mongodb-js/compass-indexes": "^5.25.0", + "@mongodb-js/compass-intercom": "^0.4.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-query-bar": "^8.27.0", + "@mongodb-js/compass-saved-aggregations-queries": "^1.26.0", + "@mongodb-js/compass-schema": "^6.27.0", + "@mongodb-js/compass-schema-validation": "^6.26.0", + "@mongodb-js/compass-serverstats": "^16.25.0", + "@mongodb-js/compass-settings": "^0.28.0", + "@mongodb-js/compass-shell": "^3.25.0", + "@mongodb-js/compass-sidebar": "^5.26.0", + "@mongodb-js/compass-utils": "^0.6.3", + "@mongodb-js/compass-welcome": "^0.24.0", + "@mongodb-js/compass-workspaces": "^0.7.0", + "@mongodb-js/connection-info": "^0.2.1", + "@mongodb-js/connection-storage": "^0.10.0", + "@mongodb-js/eslint-config-compass": "^1.1.1", "@mongodb-js/get-os-info": "^0.3.23", - "@mongodb-js/mocha-config-compass": "^1.3.7", + "@mongodb-js/mocha-config-compass": "^1.3.9", "@mongodb-js/mongodb-downloader": "^0.2.1", - "@mongodb-js/my-queries-storage": "^0.5.1", - "@mongodb-js/prettier-config-compass": "^1.0.1", + "@mongodb-js/my-queries-storage": "^0.7.0", + "@mongodb-js/prettier-config-compass": "^1.0.2", "@mongodb-js/sbom-tools": "^0.5.3", - "@mongodb-js/tsconfig-compass": "^1.0.3", - "@mongodb-js/webpack-config-compass": "^1.3.5", + "@mongodb-js/tsconfig-compass": "^1.0.4", + "@mongodb-js/webpack-config-compass": "^1.3.7", "@mongosh/node-runtime-worker-thread": "^2.2.3", "@segment/analytics-node": "^1.1.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "ampersand-view": "^9.0.0", - "bson": "^6.6.0", "chai": "^4.3.4", "chalk": "^4.1.2", "clean-stack": "^2.0.0", "clipboard": "^2.0.6", - "compass-preferences-model": "^2.18.1", + "compass-preferences-model": "^2.20.0", "debug": "^4.2.0", "depcheck": "^1.4.1", - "electron": "^28.2.10", + "electron": "^29.3.1", "electron-devtools-installer": "^3.2.0", "electron-dl": "^3.5.0", "electron-mocha": "^12.2.0", "electron-squirrel-startup": "^1.0.0", "ensure-error": "^3.0.1", "eslint": "^7.25.0", - "hadron-app-registry": "^9.1.8", - "hadron-build": "^25.4.9", - "hadron-ipc": "^3.2.12", + "hadron-app-registry": "^9.1.10", + "hadron-build": "^25.4.11", + "hadron-ipc": "^3.2.14", "kerberos": "^2.1.0", "keytar": "^7.9.0", "local-links": "^1.4.0", @@ -81026,8 +80288,8 @@ "mongodb-client-encryption": "^6.0.0", "mongodb-cloud-info": "^2.1.1", "mongodb-connection-string-url": "^2.6.0", - "mongodb-data-service": "^22.18.1", - "mongodb-instance-model": "^12.18.1", + "mongodb-data-service": "^22.19.1", + "mongodb-instance-model": "^12.20.0", "mongodb-log-writer": "^1.3.0", "mongodb-ns": "^2.4.0", "node-fetch": "^2.7.0", @@ -81093,19 +80355,18 @@ "mongodb-data-service": { "version": "file:packages/data-service", "requires": { - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-test-server": "^0.1.13", - "@mongodb-js/compass-utils": "^0.6.1", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-test-server": "^0.1.15", + "@mongodb-js/compass-utils": "^0.6.3", "@mongodb-js/devtools-connect": "^2.6.0", "@mongodb-js/devtools-docker-test-envs": "^1.3.2", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", "@mongodb-js/oidc-plugin": "^0.4.0", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/ssh-tunnel": "^2.1.13", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/ssh-tunnel": "^2.1.15", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/lodash": "^4.14.188", - "@types/uuid": "^8.3.1", "@types/whatwg-url": "^8.2.1", "bson": "^6.6.0", "chai": "^4.2.0", @@ -81124,8 +80385,7 @@ "prettier": "^2.7.1", "sinon": "^9.2.3", "socks": "^2.7.3", - "typescript": "^5.0.4", - "uuid": "^8.3.2" + "typescript": "^5.0.4" }, "dependencies": { "sinon": { @@ -81149,27 +80409,21 @@ "dev": true } } - }, - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true } } }, "mongodb-database-model": { "version": "file:packages/database-model", "requires": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/prettier-config-compass": "^1.0.1", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/prettier-config-compass": "^1.0.2", "ampersand-collection": "^2.0.2", "ampersand-model": "^8.0.1", "depcheck": "^1.4.1", "eslint": "^7.25.0", "mocha": "^10.2.0", - "mongodb-collection-model": "^5.18.1", - "mongodb-data-service": "^22.18.1" + "mongodb-collection-model": "^5.19.1", + "mongodb-data-service": "^22.19.1" } }, "mongodb-download-url": { @@ -81201,16 +80455,16 @@ "mongodb-instance-model": { "version": "file:packages/instance-model", "requires": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/prettier-config-compass": "^1.0.1", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/prettier-config-compass": "^1.0.2", "ampersand-model": "^8.0.1", "chai": "^4.3.4", "depcheck": "^1.4.1", "eslint": "^7.25.0", "mocha": "^10.2.0", - "mongodb-collection-model": "^5.18.1", - "mongodb-data-service": "^22.18.1", - "mongodb-database-model": "^2.18.1" + "mongodb-collection-model": "^5.19.1", + "mongodb-data-service": "^22.19.1", + "mongodb-database-model": "^2.19.1" } }, "mongodb-js-cli": { @@ -81501,10 +80755,10 @@ "mongodb-query-util": { "version": "file:packages/mongodb-query-util", "requires": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/sinon-chai": "^3.2.5", @@ -81879,9 +81133,9 @@ } }, "node-abi": { - "version": "3.57.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.57.0.tgz", - "integrity": "sha512-Dp+A9JWxRaKuHP35H77I4kCKesDy5HUDEmScia2FyncMTOXASMyg251F5PhFoDA5uqBrDDffiLpbqnrZmNXW+g==", + "version": "3.59.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.59.0.tgz", + "integrity": "sha512-HyyfzvTLCE8b1SX2nWimlra8cibEsypcSu/Az4SXMhWhtuctkwAX7qsEYNjUOIoYtPV884oN3wtYTN+iZKBtvw==", "requires": { "semver": "^7.3.5" } @@ -83135,6 +82389,12 @@ } } }, + "os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==", + "dev": true + }, "os-dns-native": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/os-dns-native/-/os-dns-native-1.2.1.tgz", @@ -83156,12 +82416,6 @@ } } }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "dev": true - }, "os-locale": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", @@ -83455,6 +82709,20 @@ "callsites": "^3.0.0" } }, + "parse-asn1": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.7.tgz", + "integrity": "sha512-CTM5kuWR3sx9IFamcl5ErfPl6ea/N8IYwiJ+vpeB2g+1iknv7zBl5uPwbMbRVznRVbrNY6lGuDoE5b30grmbqg==", + "dev": true, + "requires": { + "asn1.js": "^4.10.1", + "browserify-aes": "^1.2.0", + "evp_bytestokey": "^1.0.3", + "hash-base": "~3.0", + "pbkdf2": "^3.1.2", + "safe-buffer": "^5.2.1" + } + }, "parse-author": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/parse-author/-/parse-author-2.0.0.tgz", @@ -83666,6 +82934,19 @@ "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==" }, + "pbkdf2": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "dev": true, + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, "pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -83814,12 +83095,6 @@ "xmlbuilder": "^15.1.1" } }, - "pluralize": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz", - "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=", - "dev": true - }, "polished": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/polished/-/polished-4.2.2.tgz", @@ -85125,6 +84400,28 @@ "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" }, + "public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } + }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -85497,6 +84794,16 @@ "safe-buffer": "^5.1.0" } }, + "randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "requires": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, "range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -86069,43 +85376,6 @@ } } }, - "readline2": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", - "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "mute-stream": "0.0.5" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "mute-stream": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", - "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=", - "dev": true - } - } - }, - "rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", - "dev": true, - "requires": { - "resolve": "^1.1.6" - } - }, "redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -86322,39 +85592,6 @@ "integrity": "sha1-wR6XJ2tluOKSP3Xav1+y7ww4Qbk=", "dev": true }, - "require-uncached": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", - "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", - "dev": true, - "requires": { - "caller-path": "^0.1.0", - "resolve-from": "^1.0.0" - }, - "dependencies": { - "caller-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", - "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", - "dev": true, - "requires": { - "callsites": "^0.2.0" - } - }, - "callsites": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", - "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", - "dev": true - }, - "resolve-from": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", - "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", - "dev": true - } - } - }, "requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -86491,6 +85728,16 @@ "glob": "^7.1.3" } }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, "roarr": { "version": "2.15.4", "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", @@ -86544,12 +85791,6 @@ "queue-microtask": "^1.2.2" } }, - "rx-lite": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz", - "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=", - "dev": true - }, "rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", @@ -86829,6 +86070,16 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -86855,17 +86106,6 @@ "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==" }, - "shelljs": { - "version": "0.7.8", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz", - "integrity": "sha512-/YF5Uk8hcwi7ima04ppkbA4RaRMdPMBfwAvAf8sufYOxsJRtbdoBsT8vGvlb+799BrlGdYrd+oczIA2eN2JdWA==", - "dev": true, - "requires": { - "glob": "^7.0.0", - "interpret": "^1.0.0", - "rechoir": "^0.6.2" - } - }, "side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -87961,6 +87201,17 @@ "debug": "^4.1.0" } }, + "super-regex": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-0.2.0.tgz", + "integrity": "sha512-WZzIx3rC1CvbMDloLsVw0lkZVKJWbrkJ0k1ghKFmcnPrW1+jWbgTkTEWVtD9lMdmI4jZEz40+naBxl1dCUhXXw==", + "dev": true, + "requires": { + "clone-regexp": "^3.0.0", + "function-timeout": "^0.1.0", + "time-span": "^5.1.0" + } + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -88317,11 +87568,29 @@ "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" }, + "time-span": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/time-span/-/time-span-5.1.0.tgz", + "integrity": "sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==", + "dev": true, + "requires": { + "convert-hrtime": "^5.0.0" + } + }, "timed-out": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=" }, + "timers-browserify": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", + "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", + "dev": true, + "requires": { + "setimmediate": "^1.0.4" + } + }, "timers-ext": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", @@ -89135,21 +88404,36 @@ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==" }, - "user-home": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", - "integrity": "sha512-KMWqdlOcjCYdtIJpicDSFBQ8nFwS2i9sslAd6f4+CBGcU4gist2REnr2fxj2YocvJFxSF3ZOHLYLVZnUxv4BZQ==", - "dev": true, - "requires": { - "os-homedir": "^1.0.0" - } - }, "userhome": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/userhome/-/userhome-1.0.0.tgz", "integrity": "sha512-ayFKY3H+Pwfy4W98yPdtH1VqH4psDeyW8lYYFzfecR9d6hqLpqhecktvYR3SEEXt7vG0S1JEpciI3g94pMErig==", "dev": true }, + "utf8-bytes": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/utf8-bytes/-/utf8-bytes-0.0.1.tgz", + "integrity": "sha512-GifWmJAx2qAXT+lZLhbkWhBsy7pr6xWHiPWlVToDiELdWgZwt4Ogjf9tlgvKuALzTFR/d+EPQQI9ogJV3957Jg==", + "dev": true + }, + "utf8-codec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/utf8-codec/-/utf8-codec-1.0.0.tgz", + "integrity": "sha512-S/QSLezp3qvG4ld5PUfXiH7mCFxLKjSVZRFkB3DOjgwHuJPFDkInAXc/anf7BAbHt/D38ozDzL+QMZ6/7gsI6w==", + "dev": true + }, + "utf8-length": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/utf8-length/-/utf8-length-0.0.1.tgz", + "integrity": "sha512-j/XH2ftofBiobnyApxlN/J6j/ixwT89WEjDcjT66d2i0+GIn9RZfzt8lpEXXE4jUe4NsjBSUq70kS2euQ4nnMw==", + "dev": true + }, + "utf8-string-bytes": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/utf8-string-bytes/-/utf8-string-bytes-1.0.3.tgz", + "integrity": "sha512-i/I1Omf6lADjVBlwJpQifZOePV15snHny9w04+lc71+3t8PyWuLC/7clyoOSHOBNGXFe2PAGxmTiZ+Z4HWsPyw==", + "dev": true + }, "util": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", diff --git a/packages/atlas-service/.eslintrc.js b/packages/atlas-service/.eslintrc.js index e4cf824b6ac..9c3ab95632f 100644 --- a/packages/atlas-service/.eslintrc.js +++ b/packages/atlas-service/.eslintrc.js @@ -1,3 +1,4 @@ +'use strict'; module.exports = { root: true, extends: ['@mongodb-js/eslint-config-compass'], diff --git a/packages/atlas-service/.mocharc.js b/packages/atlas-service/.mocharc.js index a7e53abc444..61091edba73 100644 --- a/packages/atlas-service/.mocharc.js +++ b/packages/atlas-service/.mocharc.js @@ -1 +1,2 @@ +'use strict'; module.exports = require('@mongodb-js/mocha-config-compass/compass-plugin'); diff --git a/packages/atlas-service/main.js b/packages/atlas-service/main.js index 6c5bcb67ade..bf8a3d0b439 100644 --- a/packages/atlas-service/main.js +++ b/packages/atlas-service/main.js @@ -1 +1,2 @@ +'use strict'; module.exports = require('./dist/main'); diff --git a/packages/atlas-service/package.json b/packages/atlas-service/package.json index d43116c1529..d879595e047 100644 --- a/packages/atlas-service/package.json +++ b/packages/atlas-service/package.json @@ -13,7 +13,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "0.15.1", + "version": "0.17.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -55,10 +55,10 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", @@ -73,16 +73,16 @@ "typescript": "^5.0.4" }, "dependencies": { - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-user-data": "^0.1.17", - "@mongodb-js/compass-utils": "^0.6.1", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-user-data": "^0.1.19", + "@mongodb-js/compass-utils": "^0.6.3", "@mongodb-js/devtools-connect": "^2.6.0", "@mongodb-js/oidc-plugin": "^0.4.0", - "hadron-app-registry": "^9.1.8", - "compass-preferences-model": "^2.18.1", - "electron": "^28.2.10", - "hadron-ipc": "^3.2.12", + "hadron-app-registry": "^9.1.10", + "compass-preferences-model": "^2.20.0", + "electron": "^29.3.1", + "hadron-ipc": "^3.2.14", "lodash": "^4.17.21", "node-fetch": "^2.7.0", "react": "^17.0.2", diff --git a/packages/atlas-service/renderer.js b/packages/atlas-service/renderer.js index 42130de99ec..547cf94808d 100644 --- a/packages/atlas-service/renderer.js +++ b/packages/atlas-service/renderer.js @@ -1 +1,2 @@ +'use strict'; module.exports = require('./dist/renderer'); diff --git a/packages/atlas-service/src/atlas-service.ts b/packages/atlas-service/src/atlas-service.ts index 50c51479271..1709c2d08de 100644 --- a/packages/atlas-service/src/atlas-service.ts +++ b/packages/atlas-service/src/atlas-service.ts @@ -26,8 +26,10 @@ export class AtlasService { privateUnAuthEndpoint(path: string): string { return `${this.config.atlasApiUnauthBaseUrl}/${path}`; } - privateAtlasEndpoint(path: string): string { - return `${this.config.atlasApiBaseUrl}/${path}`; + privateAtlasEndpoint(path: string, requestId?: string): string { + return `${this.config.atlasApiBaseUrl}/${path}${ + requestId ? `?request_id=${requestId}` : '' + }`; } async fetch(url: RequestInfo, init?: RequestInit): Promise { throwIfNetworkTrafficDisabled(this.preferences); @@ -48,6 +50,16 @@ export class AtlasService { ...init?.headers, }, }); + this.logger.log.info( + this.logger.mongoLogId(1_001_000_309), + 'AtlasService', + 'Received API response', + { + url, + status: res.status, + statusText: res.statusText, + } + ); await throwIfNotOk(res); return res; } catch (err) { diff --git a/packages/atlas-service/src/components/ai-signin-modal.tsx b/packages/atlas-service/src/components/ai-signin-modal.tsx index 455d79c6c1e..464dc88a559 100644 --- a/packages/atlas-service/src/components/ai-signin-modal.tsx +++ b/packages/atlas-service/src/components/ai-signin-modal.tsx @@ -1,9 +1,7 @@ import React from 'react'; import { connect } from 'react-redux'; import { - Badge, Body, - Disclaimer, Icon, Link, MarketingModal, @@ -32,16 +30,8 @@ const titleStyles = css({ alignItems: 'center', }); -const paragraphStyles = css({ - marginBottom: spacing[200], -}); - const disclaimer = css({ - marginTop: spacing[400], -}); - -const previewBadgeStyles = css({ - marginBottom: spacing[400], + padding: `0 ${spacing[900]}px`, }); const AISignInModal: React.FunctionComponent = ({ @@ -55,12 +45,19 @@ const AISignInModal: React.FunctionComponent = ({ return ( + This is a feature powered by generative AI, and may give inaccurate + responses. Please see our{' '} + + FAQ + {' '} + for more information. + + } graphic={} title={
- - Preview - Use natural language to generate queries and pipelines
} @@ -93,21 +90,11 @@ const AISignInModal: React.FunctionComponent = ({ linkText="Not now" onLinkClick={onSignInModalClose} > -
- - Atlas users can now quickly create queries and aggregations with - MongoDB's  intelligent AI-powered feature, available today - in Compass. - - - This is a feature powered by generative AI, and may give inaccurate - responses. Please see our{' '} - - FAQ - {' '} - for more information. - -
+ + Atlas users can now quickly create queries and aggregations with + MongoDB's  intelligent AI-powered feature, available today in + Compass. +
); }; diff --git a/packages/atlas-service/src/provider.tsx b/packages/atlas-service/src/provider.tsx index 50642978d12..d2568231a07 100644 --- a/packages/atlas-service/src/provider.tsx +++ b/packages/atlas-service/src/provider.tsx @@ -63,3 +63,4 @@ export const atlasServiceLocator = createServiceLocator( export { AtlasAuthService } from './atlas-auth-service'; export type { AtlasService } from './atlas-service'; +export type { AtlasUserInfo } from './renderer'; diff --git a/packages/bson-transpilers/.eslintrc b/packages/bson-transpilers/.eslintrc index 6e1db518cb0..8763f308a26 100644 --- a/packages/bson-transpilers/.eslintrc +++ b/packages/bson-transpilers/.eslintrc @@ -5,9 +5,8 @@ "es6": true }, "rules": { - "camelcase": 1 + "camelcase": 1, + "filename-rules/match": 0 }, - "extends": [ - "mongodb-js/node" - ] + "extends": ["@mongodb-js/eslint-config-compass/plugin"] } diff --git a/packages/bson-transpilers/codegeneration/CodeGenerationVisitor.js b/packages/bson-transpilers/codegeneration/CodeGenerationVisitor.js index 6d760eb8fae..9b0618afe48 100644 --- a/packages/bson-transpilers/codegeneration/CodeGenerationVisitor.js +++ b/packages/bson-transpilers/codegeneration/CodeGenerationVisitor.js @@ -1,3 +1,4 @@ +'use strict'; /* eslint complexity: 0, camelcase: 0, "new-cap": 0 */ const { BsonTranspilersAttributeError, @@ -466,7 +467,7 @@ module.exports = (ANTLRVisitor) => class CodeGenerationVisitor extends ANTLRVisi type = this.Types[type]; } while (type !== null) { - if (!(type.attr.hasOwnProperty(rhs))) { + if (!(Object.prototype.hasOwnProperty.call(type.attr, rhs))) { if (type.id in this.BsonTypes && this.BsonTypes[type.id].id !== null) { // TODO: tell symbols vs types throw new BsonTranspilersAttributeError( `'${rhs}' not an attribute of ${type.id}` diff --git a/packages/bson-transpilers/codegeneration/DeclarationStore.js b/packages/bson-transpilers/codegeneration/DeclarationStore.js index b792ef1fb9f..5d9b373ab83 100644 --- a/packages/bson-transpilers/codegeneration/DeclarationStore.js +++ b/packages/bson-transpilers/codegeneration/DeclarationStore.js @@ -1,3 +1,4 @@ +'use strict'; /** * Stores declarations for use in the DriverTemplate * diff --git a/packages/bson-transpilers/codegeneration/ErrorListener.js b/packages/bson-transpilers/codegeneration/ErrorListener.js index 72e7109a65f..cd1be0a1fc1 100644 --- a/packages/bson-transpilers/codegeneration/ErrorListener.js +++ b/packages/bson-transpilers/codegeneration/ErrorListener.js @@ -1,3 +1,4 @@ +'use strict'; const antlr4 = require('antlr4'); const { BsonTranspilersSyntaxError } = require('../helper/error'); diff --git a/packages/bson-transpilers/codegeneration/csharp/Generator.js b/packages/bson-transpilers/codegeneration/csharp/Generator.js index d3f5fdf275d..3912173e440 100644 --- a/packages/bson-transpilers/codegeneration/csharp/Generator.js +++ b/packages/bson-transpilers/codegeneration/csharp/Generator.js @@ -1,3 +1,4 @@ +'use strict'; /* * Class for handling edge cases for csharp code generation. Defines "emit" methods. */ diff --git a/packages/bson-transpilers/codegeneration/go/Generator.js b/packages/bson-transpilers/codegeneration/go/Generator.js index 3240e910171..50258b54754 100644 --- a/packages/bson-transpilers/codegeneration/go/Generator.js +++ b/packages/bson-transpilers/codegeneration/go/Generator.js @@ -1,3 +1,4 @@ +'use strict'; /* * Class for handling edge cases for Go code generation. Defines "emit" methods. */ diff --git a/packages/bson-transpilers/codegeneration/java/Generator.js b/packages/bson-transpilers/codegeneration/java/Generator.js index 1be19cd8588..be2ad4b56fa 100644 --- a/packages/bson-transpilers/codegeneration/java/Generator.js +++ b/packages/bson-transpilers/codegeneration/java/Generator.js @@ -1,3 +1,4 @@ +'use strict'; /* eslint complexity: 0 */ const {doubleQuoteStringify, removeQuotes} = require('../../helper/format'); const { diff --git a/packages/bson-transpilers/codegeneration/javascript/Generator.js b/packages/bson-transpilers/codegeneration/javascript/Generator.js index 0bf05e5275b..90b6bb690f7 100644 --- a/packages/bson-transpilers/codegeneration/javascript/Generator.js +++ b/packages/bson-transpilers/codegeneration/javascript/Generator.js @@ -1,3 +1,4 @@ +'use strict'; /* * Class for handling edge cases for node code generation. Defines "emit" methods. */ diff --git a/packages/bson-transpilers/codegeneration/javascript/Visitor.js b/packages/bson-transpilers/codegeneration/javascript/Visitor.js index 53dc53b4469..e0403474d93 100644 --- a/packages/bson-transpilers/codegeneration/javascript/Visitor.js +++ b/packages/bson-transpilers/codegeneration/javascript/Visitor.js @@ -1,3 +1,4 @@ +'use strict'; /* eslint complexity: 0 */ const vm = require('vm'); const bson = require('bson'); diff --git a/packages/bson-transpilers/codegeneration/object/Generator.js b/packages/bson-transpilers/codegeneration/object/Generator.js index a7ff1424eeb..82d72ab4f55 100644 --- a/packages/bson-transpilers/codegeneration/object/Generator.js +++ b/packages/bson-transpilers/codegeneration/object/Generator.js @@ -1,3 +1,4 @@ +'use strict'; /* eslint new-cap: 0 camelcase: 0 */ const bson = require('bson'); const { diff --git a/packages/bson-transpilers/codegeneration/php/Generator.js b/packages/bson-transpilers/codegeneration/php/Generator.js index a13c73bedd6..595e27138e7 100644 --- a/packages/bson-transpilers/codegeneration/php/Generator.js +++ b/packages/bson-transpilers/codegeneration/php/Generator.js @@ -1,3 +1,4 @@ +'use strict'; /* * Class for handling edge cases for php code generation. Defines "emit" methods. */ diff --git a/packages/bson-transpilers/codegeneration/php/PHPUtils.js b/packages/bson-transpilers/codegeneration/php/PHPUtils.js index 791316d2b3d..50d7cf7aee1 100644 --- a/packages/bson-transpilers/codegeneration/php/PHPUtils.js +++ b/packages/bson-transpilers/codegeneration/php/PHPUtils.js @@ -1,3 +1,4 @@ +'use strict'; /** * Common functions to use in PHP templates * diff --git a/packages/bson-transpilers/codegeneration/python/Generator.js b/packages/bson-transpilers/codegeneration/python/Generator.js index 9cd47d0c146..7e87864aa1a 100644 --- a/packages/bson-transpilers/codegeneration/python/Generator.js +++ b/packages/bson-transpilers/codegeneration/python/Generator.js @@ -1,3 +1,4 @@ +'use strict'; /* * Class for handling edge cases for python code generation. Defines "emit" methods. */ diff --git a/packages/bson-transpilers/codegeneration/python/Visitor.js b/packages/bson-transpilers/codegeneration/python/Visitor.js index 44243abf032..5594af4262f 100644 --- a/packages/bson-transpilers/codegeneration/python/Visitor.js +++ b/packages/bson-transpilers/codegeneration/python/Visitor.js @@ -1,3 +1,4 @@ +'use strict'; /* eslint camelcase: 0 complexity: 0*/ const vm = require('vm'); const { diff --git a/packages/bson-transpilers/codegeneration/ruby/Generator.js b/packages/bson-transpilers/codegeneration/ruby/Generator.js index 786c2001d3e..f4765399bf2 100644 --- a/packages/bson-transpilers/codegeneration/ruby/Generator.js +++ b/packages/bson-transpilers/codegeneration/ruby/Generator.js @@ -1,3 +1,4 @@ +'use strict'; /* * Class for handling edge cases for ruby code generation. Defines "emit" methods. */ diff --git a/packages/bson-transpilers/codegeneration/rust/Generator.js b/packages/bson-transpilers/codegeneration/rust/Generator.js index c71ccfffca0..7904a87117b 100644 --- a/packages/bson-transpilers/codegeneration/rust/Generator.js +++ b/packages/bson-transpilers/codegeneration/rust/Generator.js @@ -1,3 +1,4 @@ +'use strict'; /* * Class for handling edge cases for rust code generation. Defines "emit" methods. */ diff --git a/packages/bson-transpilers/codegeneration/shell/Generator.js b/packages/bson-transpilers/codegeneration/shell/Generator.js index 09aadd7ff35..7784cc713b0 100644 --- a/packages/bson-transpilers/codegeneration/shell/Generator.js +++ b/packages/bson-transpilers/codegeneration/shell/Generator.js @@ -1,3 +1,4 @@ +'use strict'; /* * Class for handling edge cases for shell code generation. Defines "emit" methods. */ diff --git a/packages/bson-transpilers/codegeneration/shell/Visitor.js b/packages/bson-transpilers/codegeneration/shell/Visitor.js index 12a0e2d1f35..9ba45dd6b10 100644 --- a/packages/bson-transpilers/codegeneration/shell/Visitor.js +++ b/packages/bson-transpilers/codegeneration/shell/Visitor.js @@ -1,3 +1,4 @@ +'use strict'; /* eslint complexity: 0 */ const vm = require('vm'); const bson = require('bson'); diff --git a/packages/bson-transpilers/compile-symbol-table.js b/packages/bson-transpilers/compile-symbol-table.js index 3e50d9a0c34..bb957bd5183 100644 --- a/packages/bson-transpilers/compile-symbol-table.js +++ b/packages/bson-transpilers/compile-symbol-table.js @@ -1,3 +1,4 @@ +'use strict'; /* eslint-disable no-sync */ const path = require('path'); diff --git a/packages/bson-transpilers/download-antlr.js b/packages/bson-transpilers/download-antlr.js index 3618dfd4363..9f35302b77e 100644 --- a/packages/bson-transpilers/download-antlr.js +++ b/packages/bson-transpilers/download-antlr.js @@ -1,3 +1,4 @@ +'use strict'; /* eslint-disable no-console */ /* eslint-disable no-sync */ diff --git a/packages/bson-transpilers/helper/error.js b/packages/bson-transpilers/helper/error.js index b03960c3b18..73edf7e0f92 100644 --- a/packages/bson-transpilers/helper/error.js +++ b/packages/bson-transpilers/helper/error.js @@ -1,3 +1,4 @@ +'use strict'; const config = require('../config/error.json'); const errors = {}; diff --git a/packages/bson-transpilers/helper/format.js b/packages/bson-transpilers/helper/format.js index 035f604ac0c..09793cfb6d8 100644 --- a/packages/bson-transpilers/helper/format.js +++ b/packages/bson-transpilers/helper/format.js @@ -1,3 +1,4 @@ +'use strict'; /** * Takes in an identifier that may or may not be a string and returns a string * with double quotes. Replace any non-escaped double quotes with \" diff --git a/packages/bson-transpilers/index.js b/packages/bson-transpilers/index.js index 3a7211adcfa..800963245c4 100644 --- a/packages/bson-transpilers/index.js +++ b/packages/bson-transpilers/index.js @@ -1,3 +1,4 @@ +'use strict'; const antlr4 = require('antlr4'); const ECMAScriptLexer = require('./lib/antlr/ECMAScriptLexer.js'); const ECMAScriptParser = require('./lib/antlr/ECMAScriptParser.js'); diff --git a/packages/bson-transpilers/package.json b/packages/bson-transpilers/package.json index c6ca745056b..324f7ba7d2f 100644 --- a/packages/bson-transpilers/package.json +++ b/packages/bson-transpilers/package.json @@ -1,6 +1,6 @@ { "name": "bson-transpilers", - "version": "3.0.0", + "version": "3.0.2", "apiVersion": "0.0.1", "description": "Source to source compilers using ANTLR", "contributors": [ @@ -32,10 +32,10 @@ }, "license": "SSPL", "devDependencies": { + "@mongodb-js/eslint-config-compass": "^1.1.1", "chai": "^4.3.4", "depcheck": "^1.4.1", "eslint": "^7.25.0", - "eslint-config-mongodb-js": "^2.1.0", "mocha": "^10.2.0" }, "dependencies": { diff --git a/packages/bson-transpilers/printers/ECMAScriptListener.js b/packages/bson-transpilers/printers/ECMAScriptListener.js index 82164928f08..6acec5be5d1 100644 --- a/packages/bson-transpilers/printers/ECMAScriptListener.js +++ b/packages/bson-transpilers/printers/ECMAScriptListener.js @@ -1,3 +1,4 @@ +'use strict'; const antlr4 = require('antlr4'); const ECMAScriptListener = require('../lib/ECMAScriptListener.js'); diff --git a/packages/bson-transpilers/test/casting.test.js b/packages/bson-transpilers/test/casting.test.js index 60cf6ce5588..95bdecd6d30 100644 --- a/packages/bson-transpilers/test/casting.test.js +++ b/packages/bson-transpilers/test/casting.test.js @@ -1,3 +1,4 @@ +'use strict'; /* eslint new-cap: 0 no-sync: 0 */ const path = require('path'); const fs = require('fs'); @@ -29,7 +30,7 @@ const readYAML = (filename) => { return parseResult; }; -describe('Casting tests', () => { +describe('Casting tests', function() { if (modes.length > 0 && modes.indexOf('casting') === -1) { return; } @@ -63,8 +64,8 @@ describe('Casting tests', () => { doc.BsonSymbols, doc.NativeSymbols); transpiler.Syntax = doc.Syntax; transpiler.SYMBOL_TYPE = doc.SymbolTypes; - describe(`from ${input} to ${output}`, () => { - it(test.description, () => { + describe(`from ${input} to ${output}`, function() { + it(test.description, function() { if (test.input[input].args) { transpiler.Symbols.TestFunc.args = test.input[input].args.map((t) => { return t.map((k) => ( k !== null ? transpiler.Types[k] : k )); diff --git a/packages/bson-transpilers/test/declaration-store.test.js b/packages/bson-transpilers/test/declaration-store.test.js index be0e0fbe42c..c888a555ea6 100644 --- a/packages/bson-transpilers/test/declaration-store.test.js +++ b/packages/bson-transpilers/test/declaration-store.test.js @@ -1,14 +1,15 @@ +'use strict'; const assert = require('assert'); const DeclarationStore = require('../codegeneration/DeclarationStore'); -describe('DeclarationStore', () => { - it('adds data using #add', () => { +describe('DeclarationStore', function() { + it('adds data using #add', function() { const ds = new DeclarationStore(); ds.addVar('Temp', 'objectID', (varName) => { return `objectId${varName}`; }); assert.strictEqual(ds.length(), 1); }); - it('returns incremented variable names given the pre-incremented variable root-name', () => { + it('returns incremented variable names given the pre-incremented variable root-name', function() { const ds = new DeclarationStore(); ds.addVar('ForTemp', 'objectID', () => { return 1; }); @@ -20,7 +21,7 @@ describe('DeclarationStore', () => { ds.addVar('ForTemp', 'objectID', () => { return 3; }); assert.strictEqual(ds.next('ForTemp', 'objectID'), 'objectIDForTemp3'); }); - it('stringifies multiple variables declarations', () => { + it('stringifies multiple variables declarations', function() { const ds = new DeclarationStore(); const declaration1 = (varName) => { return [] @@ -55,7 +56,7 @@ describe('DeclarationStore', () => { .join('\n'); assert.strictEqual(ds.toString(), expected); }); - it('skips defining declarations for multiple of the exact same declaration (1)', () => { + it('skips defining declarations for multiple of the exact same declaration (1)', function() { const ds = new DeclarationStore(); const declaration1 = (varName) => { return [] @@ -101,7 +102,7 @@ describe('DeclarationStore', () => { .join('\n'); assert.strictEqual(ds.toString(), expected); }); - it('skips defining declarations for multiple of the exact same declaration (2)', () => { + it('skips defining declarations for multiple of the exact same declaration (2)', function() { const ds = new DeclarationStore(); const declaration1 = (varName) => { return [] @@ -147,7 +148,7 @@ describe('DeclarationStore', () => { .join('\n'); assert.strictEqual(ds.toString(), expected); }); - it('ignores duplications over different variables', () => { + it('ignores duplications over different variables', function() { const ds = new DeclarationStore(); const declaration1 = (varName) => { return [] @@ -198,7 +199,7 @@ describe('DeclarationStore', () => { .join('\n'); assert.strictEqual(ds.toString(), expected); }); - it('ignores duplications over different functions', () => { + it('ignores duplications over different functions', function() { const ds = new DeclarationStore(); const declaration1 = 'var x := func() {}'; const declaration2 = 'var x := func() {}'; @@ -215,7 +216,7 @@ describe('DeclarationStore', () => { .join('\n'); assert.strictEqual(ds.toString(), expected); }); - it('get length of sets', () => { + it('get length of sets', function() { const ds = new DeclarationStore(); const declaration1 = 'var x := func() {}'; const declaration2 = 'var x := func() {}'; diff --git a/packages/bson-transpilers/test/functions.test.js b/packages/bson-transpilers/test/functions.test.js index 15655c902f9..fdeda3f98cc 100644 --- a/packages/bson-transpilers/test/functions.test.js +++ b/packages/bson-transpilers/test/functions.test.js @@ -1,3 +1,4 @@ +'use strict'; const bsonTranspilers = require('..'); const assert = require('assert'); @@ -5,22 +6,22 @@ const { BsonTranspilersUnimplementedError } = require('../helper/error'); -describe('function expressions (shell)', () => { - it('compiles functions to javascript', () => { +describe('function expressions (shell)', function() { + it('compiles functions to javascript', function() { assert.strictEqual( bsonTranspilers.shell.javascript.compile('function(){ return this.x === 1 }'), 'function(){ return this.x === 1 }' ); }); - it('compiles functions to javascript (takes the right source range)', () => { + it('compiles functions to javascript (takes the right source range)', function() { assert.strictEqual( bsonTranspilers.shell.javascript.compile('1 + function(){ return this.x === 1 }'), '1 + function(){ return this.x === 1 }' ); }); - it('compiles functions to javascript (preserve new lines)', () => { + it('compiles functions to javascript (preserve new lines)', function() { assert.strictEqual( bsonTranspilers.shell.javascript.compile(`function(){ return this.x === 1 @@ -31,7 +32,7 @@ describe('function expressions (shell)', () => { ); }); - it('allows functions in pipeline stages', () => { + it('allows functions in pipeline stages', function() { assert.strictEqual( bsonTranspilers.shell.javascript.compile(`{ $match: { @@ -47,7 +48,7 @@ describe('function expressions (shell)', () => { }); ['object', 'csharp', 'java', 'python', 'ruby', 'rust', 'php'].forEach((language) => { - it(`throws an unsupported error compiling functions to ${language}`, () => { + it(`throws an unsupported error compiling functions to ${language}`, function() { assert.throws( () => { bsonTranspilers.shell[language].compile('function(){}'); diff --git a/packages/bson-transpilers/test/index.test.js b/packages/bson-transpilers/test/index.test.js index 3a624b8498d..8013ef43802 100644 --- a/packages/bson-transpilers/test/index.test.js +++ b/packages/bson-transpilers/test/index.test.js @@ -1,3 +1,4 @@ +'use strict'; const assert = require('assert'); const transpilers = require('../index'); diff --git a/packages/bson-transpilers/test/run-yaml.test.js b/packages/bson-transpilers/test/run-yaml.test.js index e5b3aa69440..b48422a6e9c 100644 --- a/packages/bson-transpilers/test/run-yaml.test.js +++ b/packages/bson-transpilers/test/run-yaml.test.js @@ -1,3 +1,4 @@ +'use strict'; /* eslint no-sync: 0 */ const path = require('path'); const fs = require('fs'); @@ -72,17 +73,17 @@ fs.readdirSync(testpath).map((file) => { if (modes.length > 0 && modes.indexOf(mode) === -1) { return; } - describe(mode, () => { + describe(mode, function() { const tests = readYAML(path.join(testpath, file)); for (const type of Object.keys(tests.tests)) { if (skipType.indexOf(type) !== -1) { continue; } - describe(`${type}`, () => { + describe(`${type}`, function() { for (const test of tests.tests[type]) { const description = test.description ? (d) => { - describe(`${test.description}`, () => (d())); + describe(`${test.description}`, function() { return d(); }); } : (d) => (d()); description(() => { @@ -96,7 +97,7 @@ fs.readdirSync(testpath).map((file) => { continue; } if (test.output && output === 'object') { // Can't import libraries from YAML, so un-stringify it first - it(`${input}: ${test.input[input]} => runnable object`, () => { + it(`${input}: ${test.input[input]} => runnable object`, function() { const expected = executeJavascript(test.output.object); const actual = transpiler[input].object.compile(test.input[input]); if (expected && typeof expected === 'object' && '_bsontype' in expected) { diff --git a/packages/collection-model/.eslintrc.js b/packages/collection-model/.eslintrc.js index f1cc35ced25..e291db82cd2 100644 --- a/packages/collection-model/.eslintrc.js +++ b/packages/collection-model/.eslintrc.js @@ -1,3 +1,4 @@ +'use strict'; module.exports = { root: true, extends: ['@mongodb-js/eslint-config-compass'] diff --git a/packages/collection-model/index.d.ts b/packages/collection-model/index.d.ts index 49ae97114c6..c0c415ef5a5 100644 --- a/packages/collection-model/index.d.ts +++ b/packages/collection-model/index.d.ts @@ -1,4 +1,3 @@ -import type toNS from 'mongodb-ns'; import type { DataService } from 'mongodb-data-service'; type CollectionMetadata = { @@ -99,6 +98,7 @@ interface Collection extends CollectionProps { toJSON(opts?: { derived: boolean }): CollectionProps; previousAttributes(): CollectionProps; set(val: Partial): this; + modelType: 'Collection'; } interface CollectionCollection extends Array { diff --git a/packages/collection-model/index.js b/packages/collection-model/index.js index 28ef0d75700..66e9bebbb33 100644 --- a/packages/collection-model/index.js +++ b/packages/collection-model/index.js @@ -1,3 +1,4 @@ +'use strict'; var ExtendedCollectionModel = require('./lib/model'); var ExtendedCollectionModelCollection = require('./lib/model').Collection; diff --git a/packages/collection-model/lib/collection-properties.js b/packages/collection-model/lib/collection-properties.js index 228ef115cc7..97382c2f4ec 100644 --- a/packages/collection-model/lib/collection-properties.js +++ b/packages/collection-model/lib/collection-properties.js @@ -1,3 +1,4 @@ +'use strict'; const PROPERTIES_COLLATION = 'collation'; const PROPERTIES_TIME_SERIES = 'timeseries'; const PROPERTIES_CAPPED = 'capped'; diff --git a/packages/collection-model/lib/model.js b/packages/collection-model/lib/model.js index 8b41db979f8..25f4357cfbd 100644 --- a/packages/collection-model/lib/model.js +++ b/packages/collection-model/lib/model.js @@ -1,3 +1,4 @@ +'use strict'; const AmpersandModel = require('ampersand-model'); const AmpersandCollection = require('ampersand-collection'); const toNs = require('mongodb-ns'); diff --git a/packages/collection-model/package.json b/packages/collection-model/package.json index 679c2cd7bba..24423fc65f1 100644 --- a/packages/collection-model/package.json +++ b/packages/collection-model/package.json @@ -2,7 +2,7 @@ "name": "mongodb-collection-model", "description": "MongoDB collection model", "author": "Lucas Hrabovsky ", - "version": "5.18.1", + "version": "5.19.1", "bugs": { "url": "https://jira.mongodb.org/projects/COMPASS/issues", "email": "compass@mongodb.com" @@ -31,12 +31,12 @@ "dependencies": { "ampersand-collection": "^2.0.2", "ampersand-model": "^8.0.1", - "mongodb-data-service": "^22.18.1", + "mongodb-data-service": "^22.19.1", "mongodb-ns": "^2.4.0" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/prettier-config-compass": "^1.0.1", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/prettier-config-compass": "^1.0.2", "depcheck": "^1.4.1", "electron-mocha": "^12.2.0", "eslint": "^7.25.0", diff --git a/packages/collection-model/test/index.test.js b/packages/collection-model/test/index.test.js index e743eced044..73e6370bf54 100644 --- a/packages/collection-model/test/index.test.js +++ b/packages/collection-model/test/index.test.js @@ -1,3 +1,4 @@ +'use strict'; var assert = require('assert'); var Collection = require('../'); var CollectionCollection = require('../').Collection; diff --git a/packages/compass-aggregations/.eslintrc.js b/packages/compass-aggregations/.eslintrc.js index eabb558d9c1..96c534ab2ea 100644 --- a/packages/compass-aggregations/.eslintrc.js +++ b/packages/compass-aggregations/.eslintrc.js @@ -1,3 +1,4 @@ +'use strict'; module.exports = { root: true, extends: ['@mongodb-js/eslint-config-compass/plugin'], diff --git a/packages/compass-aggregations/.mocharc.js b/packages/compass-aggregations/.mocharc.js index a7e53abc444..61091edba73 100644 --- a/packages/compass-aggregations/.mocharc.js +++ b/packages/compass-aggregations/.mocharc.js @@ -1 +1,2 @@ +'use strict'; module.exports = require('@mongodb-js/mocha-config-compass/compass-plugin'); diff --git a/packages/compass-aggregations/package.json b/packages/compass-aggregations/package.json index ef7ca61dafd..296ace31ce1 100644 --- a/packages/compass-aggregations/package.json +++ b/packages/compass-aggregations/package.json @@ -2,7 +2,7 @@ "name": "@mongodb-js/compass-aggregations", "description": "Compass Aggregation Pipeline Builder", "private": true, - "version": "9.26.1", + "version": "9.28.0", "main": "dist/index.js", "compass:main": "src/index.ts", "types": "dist/index.d.ts", @@ -32,10 +32,10 @@ }, "license": "SSPL", "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "@types/lodash": "^4.14.188", @@ -60,33 +60,33 @@ "@dnd-kit/core": "^6.0.7", "@dnd-kit/sortable": "^7.0.2", "@dnd-kit/utilities": "^3.2.1", - "@mongodb-js/atlas-service": "^0.15.1", - "@mongodb-js/compass-app-stores": "^7.10.1", - "@mongodb-js/compass-collection": "^4.23.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-crud": "^13.24.1", - "@mongodb-js/compass-editor": "^0.21.1", - "@mongodb-js/compass-field-store": "^9.0.19", - "@mongodb-js/compass-generative-ai": "^0.8.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-utils": "^0.6.1", - "@mongodb-js/compass-workspaces": "^0.5.1", - "@mongodb-js/explain-plan-helper": "^1.1.10", + "@mongodb-js/atlas-service": "^0.17.0", + "@mongodb-js/compass-app-stores": "^7.12.0", + "@mongodb-js/compass-collection": "^4.25.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-crud": "^13.26.0", + "@mongodb-js/compass-editor": "^0.23.0", + "@mongodb-js/compass-field-store": "^9.2.0", + "@mongodb-js/compass-generative-ai": "^0.10.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-utils": "^0.6.3", + "@mongodb-js/compass-workspaces": "^0.7.0", + "@mongodb-js/explain-plan-helper": "^1.1.12", "@mongodb-js/mongodb-constants": "^0.9.0", - "@mongodb-js/my-queries-storage": "^0.5.1", + "@mongodb-js/my-queries-storage": "^0.7.0", "bson": "^6.6.0", - "compass-preferences-model": "^2.18.1", + "compass-preferences-model": "^2.20.0", "ejson-shell-parser": "^2.0.1", - "hadron-app-registry": "^9.1.8", - "hadron-document": "^8.4.9", - "hadron-type-checker": "^7.1.2", + "hadron-app-registry": "^9.1.10", + "hadron-document": "^8.5.1", + "hadron-type-checker": "^7.2.1", "lodash": "^4.17.21", "mongodb": "^6.5.0", - "mongodb-collection-model": "^5.18.1", - "mongodb-data-service": "^22.18.1", - "mongodb-database-model": "^2.18.1", - "mongodb-instance-model": "^12.18.1", + "mongodb-collection-model": "^5.19.1", + "mongodb-data-service": "^22.19.1", + "mongodb-database-model": "^2.19.1", + "mongodb-instance-model": "^12.20.0", "mongodb-ns": "^2.4.0", "mongodb-query-parser": "^4.1.0", "mongodb-schema": "^12.1.0", diff --git a/packages/compass-aggregations/src/components/create-view-modal/create-view-modal.tsx b/packages/compass-aggregations/src/components/create-view-modal/create-view-modal.tsx index 5328c631ed2..fe73670ccf5 100644 --- a/packages/compass-aggregations/src/components/create-view-modal/create-view-modal.tsx +++ b/packages/compass-aggregations/src/components/create-view-modal/create-view-modal.tsx @@ -9,11 +9,7 @@ import { spacing, TextInput, } from '@mongodb-js/compass-components'; -import { - createView, - changeViewName, - toggleIsVisible, -} from '../../modules/create-view'; +import { createView, changeViewName, close } from '../../modules/create-view'; import type { LoggerAndTelemetry } from '@mongodb-js/compass-logging/provider'; import { withLoggerAndTelemetry } from '@mongodb-js/compass-logging/provider'; import type { CreateViewRootState } from '../../stores/create-view'; @@ -27,7 +23,7 @@ const progressContainerStyles = css({ type CreateViewModalProps = { createView: () => void; isVisible?: boolean; - toggleIsVisible: (newVal: boolean) => void; + closeModal: () => void; name?: string; changeViewName: (name: string) => void; isDuplicating?: boolean; @@ -63,7 +59,7 @@ class CreateViewModal extends PureComponent { }; onCancel = () => { - this.props.toggleIsVisible(false); + this.props.closeModal(); }; /** @@ -121,7 +117,7 @@ const MappedCreateViewModal = withLoggerAndTelemetry( connect(mapStateToProps, { createView, changeViewName, - toggleIsVisible, + closeModal: close, })(CreateViewModal), 'COMPASS-CREATE-VIEW-UI' ); diff --git a/packages/compass-aggregations/src/components/pipeline-builder-input-documents.tsx b/packages/compass-aggregations/src/components/pipeline-builder-input-documents.tsx index ffc424aca5b..8c1b436b152 100644 --- a/packages/compass-aggregations/src/components/pipeline-builder-input-documents.tsx +++ b/packages/compass-aggregations/src/components/pipeline-builder-input-documents.tsx @@ -20,6 +20,7 @@ import { toggleInputDocumentsCollapsed, } from '../modules/input-documents'; import LoadingOverlay from './loading-overlay'; +import type { CollectionStats } from '../modules/collection-stats'; const headerStyles = css({ display: 'flex', @@ -67,7 +68,7 @@ type InputProps = { documents: DocumentType[]; isExpanded: boolean; isLoading: boolean; - count: number; + count?: number; toggleInputDocumentsCollapsed: (arg0: boolean) => void; refreshInputDocuments: () => void; }; @@ -103,7 +104,7 @@ function PipelineBuilderInputDocuments({ - {count} Document{count === 1 ? '' : 's'} + {count ?? 'N/A'} Document{count === 1 ? '' : 's'} {' '} in the collection @@ -147,12 +148,18 @@ type InputDocuments = { }; export default connect( - ({ inputDocuments }: { inputDocuments: InputDocuments }) => { + ({ + inputDocuments, + collectionStats, + }: { + inputDocuments: InputDocuments; + collectionStats: CollectionStats; + }) => { return { documents: inputDocuments.documents, isExpanded: inputDocuments.isExpanded, isLoading: inputDocuments.isLoading, - count: inputDocuments.count, + count: collectionStats?.document_count, }; }, { diff --git a/packages/compass-aggregations/src/components/pipeline-toolbar/index.tsx b/packages/compass-aggregations/src/components/pipeline-toolbar/index.tsx index dfa7785e790..cf693d50641 100644 --- a/packages/compass-aggregations/src/components/pipeline-toolbar/index.tsx +++ b/packages/compass-aggregations/src/components/pipeline-toolbar/index.tsx @@ -67,6 +67,7 @@ export const PipelineToolbar: React.FunctionComponent = ({ darkMode && headerAndOptionsRowDarkStyles )} > + {isAIFeatureEnabled && isBuilderView && } setIsOptionsVisible(!isOptionsVisible)} @@ -79,7 +80,6 @@ export const PipelineToolbar: React.FunctionComponent = ({ )} - {isAIFeatureEnabled && isBuilderView && } {isBuilderView ? (
diff --git a/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-ai.spec.tsx b/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-ai.spec.tsx index c6c6bda9619..095ad9efcf6 100644 --- a/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-ai.spec.tsx +++ b/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-ai.spec.tsx @@ -133,6 +133,7 @@ describe('PipelineAI Component', function () { store.dispatch({ type: AIPipelineActionTypes.LoadGeneratedPipeline, pipelineText: '[{$group: {_id: "$price"}}]', + requestId: 'pineapple', pipeline: [{ $group: { _id: '$price' } }], syntaxErrors: [], stages: [], @@ -161,6 +162,7 @@ describe('PipelineAI Component', function () { event: 'PipelineAI Feedback', properties: { feedback: 'positive', + request_id: 'pineapple', text: 'this is the pipeline I was looking for', }, }, diff --git a/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-ai.tsx b/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-ai.tsx index e0f05236bbe..fa02099428c 100644 --- a/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-ai.tsx +++ b/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-ai.tsx @@ -14,7 +14,7 @@ import { import type { RootState } from '../../modules'; import { useLoggerAndTelemetry } from '@mongodb-js/compass-logging/provider'; -const useOnSubmitFeedback = () => { +const useOnSubmitFeedback = (lastAIPipelineRequestId: string | null) => { const logger = useLoggerAndTelemetry('AI-PIPELINE-UI'); return useCallback( (feedback: 'positive' | 'negative', text: string) => { @@ -25,12 +25,14 @@ const useOnSubmitFeedback = () => { 'AI pipeline feedback', { feedback, + requestId: lastAIPipelineRequestId, text, } ); track('PipelineAI Feedback', () => ({ feedback, + request_id: lastAIPipelineRequestId, text, })); @@ -40,7 +42,7 @@ const useOnSubmitFeedback = () => { timeout: 10_000, }); }, - [logger] + [logger, lastAIPipelineRequestId] ); }; @@ -56,6 +58,7 @@ type PipelineAIProps = { errorCode?: string; onCancelRequest(): void; isAggregationGeneratedFromQuery: boolean; + lastAIPipelineRequestId: string | null; onResetIsAggregationGeneratedFromQuery(): void; }; @@ -71,6 +74,7 @@ export const PipelineAI: React.FunctionComponent = ({ errorMessage, errorCode, isAggregationGeneratedFromQuery, + lastAIPipelineRequestId, onResetIsAggregationGeneratedFromQuery, }) => { // Don't show the feedback options if telemetry is disabled. @@ -84,7 +88,7 @@ export const PipelineAI: React.FunctionComponent = ({ onResetIsAggregationGeneratedFromQueryRef.current(); }; }, []); - const onSubmitFeedback = useOnSubmitFeedback(); + const onSubmitFeedback = useOnSubmitFeedback(lastAIPipelineRequestId); return ( { const isMergeOrOutPipeline = isOutputStage(lastStage); const hasSyntaxErrors = getIsPipelineInvalidFromBuilderState(state, false); const isBuilderView = state.workspace === 'builder'; + const isAIFetching = state.pipelineBuilder.aiPipeline.status === 'fetching'; return { - isRunButtonDisabled: hasSyntaxErrors, - isExplainButtonDisabled: hasSyntaxErrors, - isExportButtonDisabled: isMergeOrOutPipeline || hasSyntaxErrors, + isRunButtonDisabled: hasSyntaxErrors || isAIFetching, + isExplainButtonDisabled: hasSyntaxErrors || isAIFetching, + isExportButtonDisabled: + isMergeOrOutPipeline || hasSyntaxErrors || isAIFetching, showAIEntry: !state.pipelineBuilder.aiPipeline.isInputVisible && resultPipeline.length > 0 && isBuilderView, showUpdateViewButton: Boolean(state.editViewName), - isUpdateViewButtonDisabled: !state.isModified || hasSyntaxErrors, + isUpdateViewButtonDisabled: + !state.isModified || hasSyntaxErrors || isAIFetching, showCollectionScanInsight: state.insights.isCollectionScan, }; }; diff --git a/packages/compass-aggregations/src/index.ts b/packages/compass-aggregations/src/index.ts index 4fdbfe03962..aeb38aaf03b 100644 --- a/packages/compass-aggregations/src/index.ts +++ b/packages/compass-aggregations/src/index.ts @@ -6,6 +6,8 @@ import { activateCreateViewPlugin } from './stores/create-view'; import StageEditor from './components/stage-editor'; import CreateViewModal from './components/create-view-modal'; import { + connectionInfoAccessLocator, + connectionsManagerLocator, dataServiceLocator, type DataServiceLocator, } from '@mongodb-js/compass-connections/provider'; @@ -14,7 +16,10 @@ import type { OptionalDataServiceProps, RequiredDataServiceProps, } from './modules/data-service'; -import { mongoDBInstanceLocator } from '@mongodb-js/compass-app-stores/provider'; +import { + collectionModelLocator, + mongoDBInstanceLocator, +} from '@mongodb-js/compass-app-stores/provider'; import { workspacesServiceLocator } from '@mongodb-js/compass-workspaces/provider'; import { preferencesLocator } from 'compass-preferences-model/provider'; import { atlasAuthServiceLocator } from '@mongodb-js/atlas-service/provider'; @@ -39,6 +44,8 @@ export const CompassAggregationsHadronPlugin = registerHadronPlugin( atlasAuthService: atlasAuthServiceLocator, atlasAiService: atlasAiServiceLocator, pipelineStorage: pipelineStorageLocator, + connectionInfoAccess: connectionInfoAccessLocator, + collection: collectionModelLocator, } ); @@ -54,7 +61,7 @@ export const CreateViewPlugin = registerHadronPlugin( activate: activateCreateViewPlugin, }, { - dataService: dataServiceLocator as DataServiceLocator<'createView'>, + connectionsManager: connectionsManagerLocator, logger: createLoggerAndTelemetryLocator('COMPASS-CREATE-VIEW-UI'), workspaces: workspacesServiceLocator, } diff --git a/packages/compass-aggregations/src/modules/aggregation.ts b/packages/compass-aggregations/src/modules/aggregation.ts index 796507f01c4..2fec002eaf1 100644 --- a/packages/compass-aggregations/src/modules/aggregation.ts +++ b/packages/compass-aggregations/src/modules/aggregation.ts @@ -15,9 +15,30 @@ import { import { getDestinationNamespaceFromStage, getStageOperator, + isOutputStage, } from '../utils/stage'; import { fetchExplainForPipeline } from './insights'; import { isAction } from '../utils/is-action'; +import { + showConfirmation, + ConfirmationModalVariant, +} from '@mongodb-js/compass-components'; +import { runPipelineConfirmationDescription } from '../utils/modal-descriptions'; +import type { MongoDBInstance } from 'mongodb-instance-model'; +import type { DataService } from '../modules/data-service'; +import toNS from 'mongodb-ns'; + +const WRITE_STAGE_LINK = { + $merge: + 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/merge/', + $out: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/out/', +}; + +export const WriteOperation = { + Alter: 'altering', + Create: 'creating', + Overwrite: 'overwriting', +} as const; export enum ActionTypes { RunAggregation = 'compass-aggeregations/runAggregation', @@ -204,9 +225,85 @@ const reducer: Reducer = (state = INITIAL_STATE, action) => { return state; }; +const confirmWriteOperationIfNeeded = async ( + instance: MongoDBInstance, + dataService: DataService, + namespace: string, + pipeline: Document[] +) => { + const lastStageOperator = getStageOperator(pipeline[pipeline.length - 1]); + let typeOfWrite; + + if (!lastStageOperator || !isOutputStage(lastStageOperator)) { + return true; + } + + const destinationNamespace = getDestinationNamespaceFromStage( + namespace, + pipeline[pipeline.length - 1] + ); + + if (lastStageOperator === '$out') { + let isOverwritingCollection: boolean; + + if (destinationNamespace) { + const { database, collection } = toNS(destinationNamespace); + isOverwritingCollection = !!(await instance.getNamespace({ + dataService, + database, + collection, + })); + } else { + isOverwritingCollection = true; + } + + typeOfWrite = isOverwritingCollection + ? WriteOperation.Overwrite + : WriteOperation.Create; + } else { + typeOfWrite = WriteOperation.Alter; + } + + return await showConfirmation({ + variant: + typeOfWrite === WriteOperation.Overwrite + ? ConfirmationModalVariant.Danger + : ConfirmationModalVariant.Default, + title: 'A write operation will occur', + description: runPipelineConfirmationDescription({ + typeOfWrite, + stage: { + name: lastStageOperator, + link: WRITE_STAGE_LINK[ + lastStageOperator as keyof typeof WRITE_STAGE_LINK + ], + }, + ns: destinationNamespace, + }), + buttonText: 'Yes, run pipeline', + 'data-testid': `write-operation-confirmation-modal`, + }); +}; + export const runAggregation = (): PipelineBuilderThunkAction> => { - return (dispatch, getState, { pipelineBuilder, logger: { track } }) => { + return async ( + dispatch, + getState, + { pipelineBuilder, logger: { track }, instance, dataService } + ) => { const pipeline = getPipelineFromBuilderState(getState(), pipelineBuilder); + + if ( + !(await confirmWriteOperationIfNeeded( + instance, + dataService, + getState().namespace, + pipeline + )) + ) { + return; + } + void dispatch(fetchExplainForPipeline()); dispatch({ type: ActionTypes.RunAggregation, diff --git a/packages/compass-aggregations/src/modules/collection-stats.ts b/packages/compass-aggregations/src/modules/collection-stats.ts new file mode 100644 index 00000000000..a2524ee0429 --- /dev/null +++ b/packages/compass-aggregations/src/modules/collection-stats.ts @@ -0,0 +1,36 @@ +import type { Reducer } from 'redux'; +import type Collection from 'mongodb-collection-model'; + +export type CollectionStats = { + document_count?: number; +}; + +export const INITIAL_STATE: CollectionStats = { + document_count: undefined, +}; + +export function pickCollectionStats(collection: Collection): CollectionStats { + const { document_count } = collection.toJSON(); + return { + document_count, + }; +} + +enum CollectionStatsActions { + CollectionStatsFetched = 'compass-aggregations/collection-stats/CollectionStatsFetched', +} + +const reducer: Reducer = (state = INITIAL_STATE, action) => { + if (action.type === CollectionStatsActions.CollectionStatsFetched) { + return { + ...pickCollectionStats(action.collection), + }; + } + return state; +}; + +export const collectionStatsFetched = (collection: Collection) => { + return { type: CollectionStatsActions.CollectionStatsFetched, collection }; +}; + +export default reducer; diff --git a/packages/compass-aggregations/src/modules/create-view/index.spec.ts b/packages/compass-aggregations/src/modules/create-view/index.spec.ts index 36dd58e1b34..e89f2c0f5c8 100644 --- a/packages/compass-aggregations/src/modules/create-view/index.spec.ts +++ b/packages/compass-aggregations/src/modules/create-view/index.spec.ts @@ -1,16 +1,77 @@ -import reducer, { INITIAL_STATE, reset } from './'; +import reducer, { + INITIAL_STATE, + reset, + open, + close, + toggleIsRunning, + handleError, + clearError, + changeViewName, +} from './'; import { expect } from 'chai'; describe('create view module', function () { - describe('#reducer', function () { - describe('when an action is provided', function () { - describe('when the action is reset', function () { - it('returns the reset state', function () { - expect(reducer(INITIAL_STATE, reset())).to.deep.equal({ - ...INITIAL_STATE, - }); - }); - }); + it('handles the open and close actions', function () { + const stateAfterOpen = reducer( + INITIAL_STATE, + open({ + connectionId: 'TEST', + sourceNs: 'test.coll', + sourcePipeline: [], + duplicate: true, + }) + ); + expect(stateAfterOpen).to.deep.equal({ + ...INITIAL_STATE, + connectionId: 'TEST', + isRunning: false, + isVisible: true, + isDuplicating: true, + name: '', + source: 'test.coll', + pipeline: [], + }); + + expect(reducer(stateAfterOpen, close())).to.deep.equal(INITIAL_STATE); + }); + + it('handles the reset action', function () { + const isRunningState = reducer(INITIAL_STATE, toggleIsRunning(true)); + expect(reducer(isRunningState, reset())).to.deep.equal({ + ...INITIAL_STATE, + }); + }); + + it('handles the toggleIsRunning action', function () { + const isRunningState = reducer(INITIAL_STATE, toggleIsRunning(true)); + expect(isRunningState).to.deep.equal({ + ...INITIAL_STATE, + isRunning: true, + }); + expect(reducer(isRunningState, toggleIsRunning(false))).to.deep.equal({ + ...isRunningState, + isRunning: false, + }); + }); + + it('handles the handleError and clearError actions', function () { + const err = new Error('Oops!'); + const erroredState = reducer(INITIAL_STATE, handleError(err)); + expect(erroredState).to.deep.equal({ + ...INITIAL_STATE, + error: err, + }); + + expect(reducer(erroredState, clearError())).to.deep.equal({ + ...erroredState, + error: null, + }); + }); + + it('handles the changeViewName action', function () { + expect(reducer(INITIAL_STATE, changeViewName('Yikes'))).to.deep.equal({ + ...INITIAL_STATE, + name: 'Yikes', }); }); }); diff --git a/packages/compass-aggregations/src/modules/create-view/index.ts b/packages/compass-aggregations/src/modules/create-view/index.ts index 62ce5ef12b0..5472c5dba1f 100644 --- a/packages/compass-aggregations/src/modules/create-view/index.ts +++ b/packages/compass-aggregations/src/modules/create-view/index.ts @@ -4,119 +4,127 @@ import type { Document } from 'bson'; import type { CreateViewThunkAction } from '../../stores/create-view'; import { isAction } from '../../utils/is-action'; -export const TOGGLE_IS_RUNNING = - 'aggregations/create-view/is-running/TOGGLE_IS_RUNNING' as const; -interface ToggleIsRunningAction { - type: typeof TOGGLE_IS_RUNNING; +export type CreateViewState = { + connectionId: string; isRunning: boolean; -} + isVisible: boolean; + isDuplicating: boolean; + name: string; + error: Error | null; + source: string; + pipeline: unknown[]; +}; -export const toggleIsRunning = (isRunning: boolean): ToggleIsRunningAction => ({ - type: TOGGLE_IS_RUNNING, - isRunning: isRunning, -}); +export const INITIAL_STATE: CreateViewState = { + connectionId: '', + isRunning: false, + isVisible: false, + isDuplicating: false, + name: '', + error: null, + source: '', + pipeline: [], +}; -export const TOGGLE_IS_VISIBLE = - 'aggregations/create-view/is-visible/TOGGLE_IS_VISIBLE' as const; -interface ToggleIsVisibleAction { - type: typeof TOGGLE_IS_VISIBLE; - isVisible: boolean; +enum CreateViewActionTypes { + Open = 'aggregations/create-view/Open', + Close = 'aggregations/create-view/Close', + ToggleIsRunning = 'aggregations/create-view/is-running/ToggleIsRunning', + HandleError = 'aggregations/create-view/error/HandleError', + ClearError = 'aggregations/create-view/error/ClearError', + ChangeViewName = 'aggregations/create-view/name/ChangeName', + Reset = 'aggregations/create-view/reset', } -export const toggleIsVisible = (isVisible: boolean): ToggleIsVisibleAction => ({ - type: TOGGLE_IS_VISIBLE, - isVisible: isVisible, -}); +export type OpenAction = { + type: CreateViewActionTypes.Open; + connectionId: string; + source: string; + pipeline: unknown[]; + duplicate: boolean; +}; -/** - * Handle error action name. - */ -export const HANDLE_ERROR = - `aggregations/create-view/error/HANDLE_ERROR` as const; -interface HandleErrorAction { - type: typeof HANDLE_ERROR; +export type CloseAction = { + type: CreateViewActionTypes.Close; +}; + +export type ToggleIsRunningAction = { + type: CreateViewActionTypes.ToggleIsRunning; + isRunning: boolean; +}; + +export type HandleErrorAction = { + type: CreateViewActionTypes.HandleError; error: Error; -} +}; + +export type ClearErrorAction = { + type: CreateViewActionTypes.ClearError; +}; + +export type ChangeViewNameAction = { + type: CreateViewActionTypes.ChangeViewName; + name: string; +}; + +export type ResetAction = { + type: CreateViewActionTypes.Reset; +}; + +export type CreateViewAction = + | OpenAction + | CloseAction + | ResetAction + | ToggleIsRunningAction + | HandleErrorAction + | ClearErrorAction + | ChangeViewNameAction; + +export const open = ({ + connectionId, + sourceNs, + sourcePipeline, + duplicate, +}: { + connectionId: string; + sourceNs: string; + sourcePipeline: unknown[]; + duplicate: boolean; +}): OpenAction => ({ + type: CreateViewActionTypes.Open, + connectionId, + source: sourceNs, + pipeline: sourcePipeline, + duplicate: duplicate, +}); + +export const close = (): CloseAction => ({ + type: CreateViewActionTypes.Close, +}); + +export const toggleIsRunning = (isRunning: boolean): ToggleIsRunningAction => ({ + type: CreateViewActionTypes.ToggleIsRunning, + isRunning: isRunning, +}); -/** - * Handle error action creator. - */ export const handleError = (error: Error): HandleErrorAction => ({ - type: HANDLE_ERROR, + type: CreateViewActionTypes.HandleError, error: error, }); -export const CLEAR_ERROR = - `aggregations/create-view/error/CLEAR_ERROR` as const; -interface ClearErrorAction { - type: typeof CLEAR_ERROR; -} - export const clearError = (): ClearErrorAction => ({ - type: CLEAR_ERROR, + type: CreateViewActionTypes.ClearError, }); -export const CHANGE_VIEW_NAME = - 'aggregations/create-view/name/CHANGE_NAME' as const; -interface ChangeViewNameAction { - type: typeof CHANGE_VIEW_NAME; - name: string; -} - export const changeViewName = (name: string): ChangeViewNameAction => ({ - type: CHANGE_VIEW_NAME, + type: CreateViewActionTypes.ChangeViewName, name: name, }); -export const RESET = 'aggregations/create-view/reset' as const; -interface ResetAction { - type: typeof RESET; -} - export const reset = (): ResetAction => ({ - type: RESET, + type: CreateViewActionTypes.Reset, }); -/** - * Open action name. - */ -const OPEN = 'aggregations/create-view/OPEN' as const; -interface OpenAction { - type: typeof OPEN; - source: string; - pipeline: unknown[]; - duplicate: boolean; -} - -export type CreateViewAction = - | ToggleIsRunningAction - | ToggleIsVisibleAction - | HandleErrorAction - | ClearErrorAction - | ChangeViewNameAction - | ResetAction - | OpenAction; - -export type CreateViewState = { - isRunning: boolean; - isVisible: boolean; - isDuplicating: boolean; - name: string; - error: Error | null; - source: string; - pipeline: unknown[]; -}; - -export const INITIAL_STATE: CreateViewState = { - isRunning: false, - isVisible: false, - isDuplicating: false, - name: '', - error: null, - source: '', - pipeline: [], -}; - /** * The main reducer. */ @@ -124,44 +132,49 @@ const reducer: Reducer = ( state = INITIAL_STATE, action ) => { - if (isAction(action, RESET)) { + if ( + isAction(action, CreateViewActionTypes.Reset) || + isAction(action, CreateViewActionTypes.Close) + ) { return { ...INITIAL_STATE }; } - if (isAction(action, OPEN)) { + if (isAction(action, CreateViewActionTypes.Open)) { return { ...state, ...INITIAL_STATE, isVisible: true, + connectionId: action.connectionId, isDuplicating: action.duplicate, source: action.source, pipeline: action.pipeline, }; } - if (isAction(action, TOGGLE_IS_RUNNING)) { + if ( + isAction( + action, + CreateViewActionTypes.ToggleIsRunning + ) + ) { return { ...state, isRunning: action.isRunning, }; } - if (isAction(action, TOGGLE_IS_VISIBLE)) { - return { - ...state, - isVisible: action.isVisible, - }; - } - if (isAction(action, CHANGE_VIEW_NAME)) { + if ( + isAction(action, CreateViewActionTypes.ChangeViewName) + ) { return { ...state, name: action.name, }; } - if (isAction(action, HANDLE_ERROR)) { + if (isAction(action, CreateViewActionTypes.HandleError)) { return { ...state, error: action.error, }; } - if (isAction(action, CLEAR_ERROR)) { + if (isAction(action, CreateViewActionTypes.ClearError)) { return { ...state, error: null, @@ -182,20 +195,6 @@ const stopWithError = (err: Error): CreateViewThunkAction => { }; }; -/** - * Open create view action creator. - */ -export const open = ( - sourceNs: string, - sourcePipeline: unknown[], - duplicate: boolean -): OpenAction => ({ - type: OPEN, - source: sourceNs, - pipeline: sourcePipeline, - duplicate: duplicate, -}); - /** * The create view action. */ @@ -203,19 +202,28 @@ export const createView = (): CreateViewThunkAction> => { return async ( dispatch, getState, - { globalAppRegistry, dataService, logger: { track }, workspaces } + { globalAppRegistry, connectionsManager, logger: { track }, workspaces } ) => { - const state = getState(); - - const viewName = state.name; - const viewSource = state.source; - const { database } = parseNs(state.source); - const viewPipeline = state.pipeline; + const { + name: viewName, + source: viewSource, + pipeline: viewPipeline, + connectionId, + } = getState(); + const { database } = parseNs(viewSource); const options = {}; dispatch(clearError()); try { + const dataService = + connectionsManager.getDataServiceForConnection(connectionId); + if (!dataService) { + throw new Error( + `DataService for connectionId ${connectionId} not found` + ); + } + dispatch(toggleIsRunning(true)); await dataService.createView( viewName, @@ -225,8 +233,10 @@ export const createView = (): CreateViewThunkAction> => { ); const ns = `${database}.${viewName}`; track('Aggregation Saved As View', { num_stages: viewPipeline.length }); - globalAppRegistry.emit('view-created', ns); - workspaces.openCollectionWorkspace(ns, { newTab: true }); + globalAppRegistry.emit('view-created', ns, { + connectionId, + }); + workspaces.openCollectionWorkspace(connectionId, ns, { newTab: true }); dispatch(reset()); } catch (e) { dispatch(stopWithError(e as Error)); diff --git a/packages/compass-aggregations/src/modules/data-service.ts b/packages/compass-aggregations/src/modules/data-service.ts index 4f84fdec28b..f4e85e2f75a 100644 --- a/packages/compass-aggregations/src/modules/data-service.ts +++ b/packages/compass-aggregations/src/modules/data-service.ts @@ -5,7 +5,9 @@ export type RequiredDataServiceProps = | 'estimatedCount' | 'aggregate' | 'getConnectionString' - | 'updateCollection'; + | 'updateCollection' + | 'getCurrentTopologyType' + | 'instance'; export type OptionalDataServiceProps = | 'explainAggregate' | 'getSearchIndexes' diff --git a/packages/compass-aggregations/src/modules/index.ts b/packages/compass-aggregations/src/modules/index.ts index aa286d3c1d4..ae853c767dc 100644 --- a/packages/compass-aggregations/src/modules/index.ts +++ b/packages/compass-aggregations/src/modules/index.ts @@ -28,6 +28,7 @@ import countDocuments from './count-documents'; import isDataLake from './is-datalake'; import workspace from './workspace'; import aggregationWorkspaceId from './aggregation-workspace-id'; +import collectionStats from './collection-stats'; import type { ThunkAction, ThunkDispatch } from 'redux-thunk'; import type { PipelineBuilder } from './pipeline-builder/pipeline-builder'; import type { PipelineStorage } from '@mongodb-js/my-queries-storage/provider'; @@ -42,7 +43,9 @@ import type { LoggerAndTelemetry } from '@mongodb-js/compass-logging/provider'; import type AppRegistry from 'hadron-app-registry'; import type { AtlasAiService } from '@mongodb-js/compass-generative-ai/provider'; import type { AtlasAuthService } from '@mongodb-js/atlas-service/provider'; - +import type { MongoDBInstance } from 'mongodb-instance-model'; +import type { DataService } from '../modules/data-service'; +import type { ConnectionInfoAccess } from '@mongodb-js/compass-connections/provider'; /** * The main application reducer. * @@ -83,6 +86,7 @@ const rootReducer = combineReducers({ collectionsFields, insights, searchIndexes, + collectionStats, }); export type RootState = ReturnType; @@ -97,6 +101,9 @@ export type PipelineBuilderExtraArgs = { preferences: PreferencesAccess; logger: LoggerAndTelemetry; atlasAiService: AtlasAiService; + instance: MongoDBInstance; + dataService: DataService; + connectionInfoAccess: ConnectionInfoAccess; }; export type PipelineBuilderThunkDispatch = diff --git a/packages/compass-aggregations/src/modules/input-documents.spec.ts b/packages/compass-aggregations/src/modules/input-documents.spec.ts index 8ee50450568..faf305c8842 100644 --- a/packages/compass-aggregations/src/modules/input-documents.spec.ts +++ b/packages/compass-aggregations/src/modules/input-documents.spec.ts @@ -25,37 +25,14 @@ describe('input documents module', function () { describe('#updateInputDocuments', function () { it('returns the ActionTypes.DocumentsFetchFinished action', function () { - expect(updateInputDocuments(10, [], null)).to.deep.equal({ + expect(updateInputDocuments([], null)).to.deep.equal({ type: ActionTypes.DocumentsFetchFinished, - count: 10, documents: [], error: null, }); }); }); - describe('#refreshInputDocuments', function () { - context('when the data service is connected', function () { - context('when the count succeeds', function () { - context('when the aggregation succeeds', function () { - it('sets the count and documents in the state', function () {}); - }); - - context('when the aggregation fails', function () { - it('sets the error in the state', function () {}); - }); - }); - - context('when the count fails', function () { - it('sets the error in the state', function () {}); - }); - }); - - context('when the dataservice is not connected', function () { - it('sets the error in the state', function () {}); - }); - }); - describe('#reducer', function () { context( 'when the action is not toggle input documents collapsed', @@ -65,7 +42,6 @@ describe('input documents module', function () { documents: [], error: null, isExpanded: true, - count: null, isLoading: false, }); }); @@ -80,7 +56,6 @@ describe('input documents module', function () { documents: [], error: null, isExpanded: false, - count: null, isLoading: false, }); }); diff --git a/packages/compass-aggregations/src/modules/input-documents.ts b/packages/compass-aggregations/src/modules/input-documents.ts index 4d77c2d434f..592300c9085 100644 --- a/packages/compass-aggregations/src/modules/input-documents.ts +++ b/packages/compass-aggregations/src/modules/input-documents.ts @@ -20,7 +20,6 @@ type DocumentsFetchStartedAction = { type DocumentsFetchFinishedAction = { type: ActionTypes.DocumentsFetchFinished; - count: number | null; documents: HadronDocument[]; error: Error | null; }; @@ -31,7 +30,6 @@ export type InputDocumentsAction = | DocumentsFetchStartedAction; export type InputDocumentsState = { - count: number | null; documents: HadronDocument[]; error: Error | null; isExpanded: boolean; @@ -39,7 +37,6 @@ export type InputDocumentsState = { }; export const INITIAL_STATE: InputDocumentsState = { - count: null, documents: [], error: null, isExpanded: true, @@ -71,7 +68,6 @@ const reducer = ( ) { return { ...state, - count: action.count, documents: action.documents, error: action.error, isLoading: false, @@ -91,12 +87,10 @@ export const loadingInputDocuments = (): DocumentsFetchStartedAction => ({ }); export const updateInputDocuments = ( - count: number | null, documents: HadronDocument[], error: Error | null ): DocumentsFetchFinishedAction => ({ type: ActionTypes.DocumentsFetchFinished, - count, documents, error, }); @@ -122,38 +116,21 @@ export const refreshInputDocuments = (): PipelineBuilderThunkAction< | undefined, }; - // maxTimeMS defaults to null here because the aggregation options field's - // maxTimeMS defaults to empty and the preference defaults to undefined. - // We need a timeout on count because for timeseries estimatedCount() seems - // to just do a colscan and we need that timeout to be low compared to the - // aggregation one anyway. For aggregations it is less critical because we - // $limit to the first few records. - const countOptions = { ...options, maxTimeMS: 500 }; const aggregateOptions = { ...options }; - const exampleDocumentsPipeline = [{ $limit: sampleSize }]; dispatch(loadingInputDocuments()); try { - const data = await Promise.allSettled([ - dataService.estimatedCount(ns, countOptions), - dataService.aggregate(ns, exampleDocumentsPipeline, aggregateOptions), - ]); - - const count = data[0].status === 'fulfilled' ? data[0].value : null; - const docs = data[1].status === 'fulfilled' ? data[1].value : []; + const docs = await dataService.aggregate( + ns, + exampleDocumentsPipeline, + aggregateOptions + ); const hadronDocs = docs.map((doc: any) => new HadronDocument(doc)); - - const error = - data[0].status === 'rejected' - ? data[0].reason - : data[1].status === 'rejected' - ? data[1].reason - : null; - dispatch(updateInputDocuments(count, hadronDocs, error)); + dispatch(updateInputDocuments(hadronDocs, null)); } catch (error) { - dispatch(updateInputDocuments(null, [], error as Error)); + dispatch(updateInputDocuments([], error as Error)); } }; }; diff --git a/packages/compass-aggregations/src/modules/out-results-fn.ts b/packages/compass-aggregations/src/modules/out-results-fn.ts index fe684d04b0d..0d45827da5f 100644 --- a/packages/compass-aggregations/src/modules/out-results-fn.ts +++ b/packages/compass-aggregations/src/modules/out-results-fn.ts @@ -25,12 +25,16 @@ export default function reducer( export const gotoOutResults = ( namespace: string ): PipelineBuilderThunkAction => { - return (_dispatch, getState, { workspaces }) => { + return (_dispatch, getState, { workspaces, connectionInfoAccess }) => { const { outResultsFn } = getState(); + const { id: connectionId } = + connectionInfoAccess.getCurrentConnectionInfo(); if (outResultsFn) { outResultsFn(namespace); } else { - workspaces.openCollectionWorkspace(namespace, { newTab: true }); + workspaces.openCollectionWorkspace(connectionId, namespace, { + newTab: true, + }); } }; }; diff --git a/packages/compass-aggregations/src/modules/pipeline-builder/pipeline-ai.spec.ts b/packages/compass-aggregations/src/modules/pipeline-builder/pipeline-ai.spec.ts index a0d43ca81b2..cfd5c515fcf 100644 --- a/packages/compass-aggregations/src/modules/pipeline-builder/pipeline-ai.spec.ts +++ b/packages/compass-aggregations/src/modules/pipeline-builder/pipeline-ai.spec.ts @@ -1,5 +1,9 @@ import { expect } from 'chai'; import Sinon from 'sinon'; +import type { PreferencesAccess } from 'compass-preferences-model'; +import { createSandboxFromDefaultPreferences } from 'compass-preferences-model'; +import type { AtlasAiService } from '@mongodb-js/compass-generative-ai/provider'; +import type { DataService } from 'mongodb-data-service'; import configureReduxStore from '../../../test/configure-store'; import { @@ -10,24 +14,29 @@ import { } from './pipeline-ai'; import { toggleAutoPreview } from '../auto-preview'; import { MockAtlasAiService } from '../../../test/configure-store'; -import { createSandboxFromDefaultPreferences } from 'compass-preferences-model'; -import type { AtlasAiService } from '@mongodb-js/compass-generative-ai/provider'; describe('AIPipelineReducer', function () { const sandbox = Sinon.createSandbox(); + let preferences: PreferencesAccess; - afterEach(function () { - sandbox.reset(); - }); - - async function configureStore(aiService: Partial = {}) { - const preferences = await createSandboxFromDefaultPreferences(); + beforeEach(async function () { + preferences = await createSandboxFromDefaultPreferences(); await preferences.savePreferences({ enableGenAIFeatures: true, cloudFeatureRolloutAccess: { GEN_AI_COMPASS: true, }, }); + }); + + afterEach(function () { + sandbox.reset(); + }); + + function configureStore( + aiService: Partial = {}, + mockDataService?: Partial + ) { const atlasAiService = Object.assign(new MockAtlasAiService(), aiService); return configureReduxStore( { @@ -39,6 +48,8 @@ describe('AIPipelineReducer', function () { } as any, { atlasAiService: atlasAiService as any, + dataService: mockDataService as any, + preferences, } ); } @@ -49,7 +60,7 @@ describe('AIPipelineReducer', function () { const fetchJsonStub = sandbox.stub().resolves({ content: { aggregation: { pipeline: '[{ $match: { _id: 1 } }]' } }, }); - const store = await configureStore({ + const store = configureStore({ getAggregationFromUserInput: fetchJsonStub, }); @@ -72,8 +83,8 @@ describe('AIPipelineReducer', function () { expect(args).to.not.have.property('sampleDocuments'); expect( - store.getState().pipelineBuilder.aiPipeline.aiPipelineFetchId - ).to.equal(-1); + store.getState().pipelineBuilder.aiPipeline.aiPipelineRequestId + ).to.equal(null); expect( store.getState().pipelineBuilder.aiPipeline.errorMessage ).to.equal(undefined); @@ -85,7 +96,7 @@ describe('AIPipelineReducer', function () { describe('when there is an error', function () { it('sets the error on the store', async function () { - const store = await configureStore({ + const store = configureStore({ getAggregationFromUserInput: sandbox .stub() .rejects(new Error('500 Internal Server Error')), @@ -95,8 +106,8 @@ describe('AIPipelineReducer', function () { ).to.equal(undefined); await store.dispatch(runAIPipelineGeneration('testing prompt') as any); expect( - store.getState().pipelineBuilder.aiPipeline.aiPipelineFetchId - ).to.equal(-1); + store.getState().pipelineBuilder.aiPipeline.aiPipelineRequestId + ).to.equal(null); expect( store.getState().pipelineBuilder.aiPipeline.errorMessage ).to.equal('500 Internal Server Error'); @@ -108,7 +119,7 @@ describe('AIPipelineReducer', function () { it('resets the store if errs was caused by user being unauthorized', async function () { const authError = new Error('Unauthorized'); (authError as any).statusCode = 401; - const store = await configureStore({ + const store = configureStore({ getAggregationFromUserInput: sandbox.stub().rejects(authError), }); await store.dispatch(runAIPipelineGeneration('testing prompt') as any); @@ -118,37 +129,104 @@ describe('AIPipelineReducer', function () { errorMessage: undefined, errorCode: undefined, isInputVisible: false, - aiPipelineFetchId: -1, + aiPipelineRequestId: null, + lastAIPipelineRequestId: null, isAggregationGeneratedFromQuery: false, }); }); }); + + describe('when the sample documents setting is enabled', function () { + beforeEach(async function () { + await preferences.savePreferences({ + enableGenAISampleDocumentPassing: true, + }); + }); + + it('includes sample documents in the request', async function () { + const fetchJsonStub = sandbox.stub().resolves({ + content: { aggregation: { pipeline: '[{ $match: { _id: 1 } }]' } }, + }); + const mockDataService = { + sample: sandbox.stub().resolves([{ pineapple: 'turtle' }]), + }; + const store = configureStore( + { + getAggregationFromUserInput: fetchJsonStub, + }, + mockDataService + ); + + // Set autoPreview false so that it doesn't start the + // follow up async preview doc requests. + store.dispatch(toggleAutoPreview(false)); + expect(store.getState().pipelineBuilder.aiPipeline.status).to.equal( + 'ready' + ); + + await preferences.savePreferences({ + enableGenAISampleDocumentPassing: true, + }); + + await store.dispatch(runAIPipelineGeneration('testing prompt')); + + expect(fetchJsonStub).to.have.been.calledOnce; + const args = fetchJsonStub.getCall(0).firstArg; + expect(args).to.have.deep.property('sampleDocuments', [ + { pineapple: 'turtle' }, + ]); + }); + }); + + describe('when the sample documents setting is disabled', function () { + it('does not include sample documents in the request', async function () { + const fetchJsonStub = sandbox.stub().resolves({ + content: { aggregation: { pipeline: '[{ $match: { _id: 1 } }]' } }, + }); + const store = configureStore({ + getAggregationFromUserInput: fetchJsonStub, + }); + + // Set autoPreview false so that it doesn't start the + // follow up async preview doc requests. + store.dispatch(toggleAutoPreview(false)); + expect(store.getState().pipelineBuilder.aiPipeline.status).to.equal( + 'ready' + ); + + await store.dispatch(runAIPipelineGeneration('testing prompt')); + expect(fetchJsonStub).to.have.been.calledOnce; + + const args = fetchJsonStub.getCall(0).firstArg; + expect(args).to.not.have.property('sampleDocuments'); + }); + }); }); describe('cancelAIPipelineGeneration', function () { - it('should unset the fetching id and set the status on the store', async function () { - const store = await configureStore(); + it('should unset the fetching id and set the status on the store', function () { + const store = configureStore(); expect( - store.getState().pipelineBuilder.aiPipeline.aiPipelineFetchId - ).to.equal(-1); + store.getState().pipelineBuilder.aiPipeline.aiPipelineRequestId + ).to.equal(null); store.dispatch({ type: AIPipelineActionTypes.AIPipelineStarted, - fetchId: 1, + requestId: 'pineapples', }); expect(store.getState().pipelineBuilder.aiPipeline.status).to.equal( 'fetching' ); expect( - store.getState().pipelineBuilder.aiPipeline.aiPipelineFetchId - ).to.equal(1); + store.getState().pipelineBuilder.aiPipeline.aiPipelineRequestId + ).to.equal('pineapples'); store.dispatch(cancelAIPipelineGeneration()); expect( - store.getState().pipelineBuilder.aiPipeline.aiPipelineFetchId - ).to.equal(-1); + store.getState().pipelineBuilder.aiPipeline.aiPipelineRequestId + ).to.equal(null); expect(store.getState().pipelineBuilder.aiPipeline.status).to.equal( 'ready' ); @@ -156,8 +234,8 @@ describe('AIPipelineReducer', function () { }); describe('generateAggregationFromQuery', function () { - it('should create an aggregation pipeline', async function () { - const store = await configureStore({ + it('should create an aggregation pipeline', function () { + const store = configureStore({ getAggregationFromUserInput: sandbox.stub().resolves({ content: { aggregation: { pipeline: '[{ $group: { _id: "$price" } }]' }, @@ -178,12 +256,13 @@ describe('AIPipelineReducer', function () { aggregation: { pipeline: '[{ $group: { _id: "$price" } }]', }, + requestId: 'abc', }) ); expect( - store.getState().pipelineBuilder.aiPipeline.aiPipelineFetchId - ).to.equal(-1); + store.getState().pipelineBuilder.aiPipeline.aiPipelineRequestId + ).to.equal(null); expect(store.getState().pipelineBuilder.aiPipeline.errorMessage).to.equal( undefined ); @@ -196,6 +275,9 @@ describe('AIPipelineReducer', function () { expect( store.getState().pipelineBuilder.aiPipeline.isInputVisible ).to.equal(true); + expect( + store.getState().pipelineBuilder.aiPipeline.lastAIPipelineRequestId + ).to.equal('abc'); expect( store.getState().pipelineBuilder.aiPipeline .isAggregationGeneratedFromQuery diff --git a/packages/compass-aggregations/src/modules/pipeline-builder/pipeline-ai.ts b/packages/compass-aggregations/src/modules/pipeline-builder/pipeline-ai.ts index e440c7fffb5..3dbe2e93ca8 100644 --- a/packages/compass-aggregations/src/modules/pipeline-builder/pipeline-ai.ts +++ b/packages/compass-aggregations/src/modules/pipeline-builder/pipeline-ai.ts @@ -3,6 +3,7 @@ import { getSimplifiedSchema } from 'mongodb-schema'; import toNS from 'mongodb-ns'; import { openToast } from '@mongodb-js/compass-components'; import type { Document } from 'mongodb'; +import { UUID } from 'bson'; import type { PipelineBuilderThunkAction } from '../'; import { isAction } from '../../utils/is-action'; @@ -24,7 +25,8 @@ export type AIPipelineState = { isInputVisible: boolean; aiPromptText: string; status: AIPipelineStatus; - aiPipelineFetchId: number; // Maps to the AbortController of the current fetch (or -1). + aiPipelineRequestId: string | null; // Maps to the AbortController of the current fetch (or null). + lastAIPipelineRequestId: string | null; // We store the last request id so we can pass it when a user provides feedback. isAggregationGeneratedFromQuery: boolean; }; @@ -34,7 +36,8 @@ export const initialState: AIPipelineState = { errorMessage: undefined, errorCode: undefined, isInputVisible: false, - aiPipelineFetchId: -1, + aiPipelineRequestId: null, + lastAIPipelineRequestId: null, isAggregationGeneratedFromQuery: false, }; @@ -53,24 +56,22 @@ export const enum AIPipelineActionTypes { const NUM_DOCUMENTS_TO_SAMPLE = 4; -const AIPipelineAbortControllerMap = new Map(); - -let aiPipelineFetchId = 0; +const AIPipelineAbortControllerMap = new Map(); function getAbortSignal() { - const id = ++aiPipelineFetchId; + const id = new UUID().toString(); const controller = new AbortController(); AIPipelineAbortControllerMap.set(id, controller); return { id, signal: controller.signal }; } -function abort(id: number) { +function abort(id: string) { const controller = AIPipelineAbortControllerMap.get(id); controller?.abort(); return AIPipelineAbortControllerMap.delete(id); } -function cleanupAbortSignal(id: number) { +function cleanupAbortSignal(id: string) { return AIPipelineAbortControllerMap.delete(id); } @@ -98,14 +99,17 @@ export type LoadGeneratedPipelineAction = { pipeline: Document[] | null; syntaxErrors: PipelineParserError[]; stages: Stage[]; + requestId: string; }; export const generateAggregationFromQuery = ({ aggregation, userInput, + requestId, }: { aggregation: { pipeline: string }; userInput: string; + requestId: string; }): PipelineBuilderThunkAction => { return (dispatch, getState, { pipelineBuilder }) => { const pipelineText = String(aggregation?.pipeline); @@ -119,6 +123,7 @@ export const generateAggregationFromQuery = ({ pipeline: pipelineBuilder.pipeline, syntaxErrors: pipelineBuilder.syntaxError, text: userInput, + requestId, }); dispatch(updatePipelinePreview()); @@ -127,7 +132,7 @@ export const generateAggregationFromQuery = ({ type AIPipelineStartedAction = { type: AIPipelineActionTypes.AIPipelineStarted; - fetchId: number; + requestId: string; }; type AIPipelineFailedAction = { @@ -144,6 +149,7 @@ export type PipelineGeneratedFromQueryAction = { pipelineText: string; pipeline: Document[] | null; syntaxErrors: PipelineParserError[]; + requestId: string; }; type FailedResponseTrackMessage = { @@ -152,6 +158,7 @@ type FailedResponseTrackMessage = { errorMessage: string; errorName: string; errorCode?: string; + requestId: string; } & Pick; function trackAndLogFailed({ @@ -161,6 +168,7 @@ function trackAndLogFailed({ errorName, errorCode, log, + requestId, track, }: FailedResponseTrackMessage) { log.warn( @@ -172,6 +180,7 @@ function trackAndLogFailed({ errorMessage, errorName, errorCode, + requestId, } ); track('AI Response Failed', { @@ -179,6 +188,7 @@ function trackAndLogFailed({ error_code: errorCode || '', status_code: statusCode, error_name: errorName, + request_id: requestId, }); } @@ -200,30 +210,35 @@ export const runAIPipelineGeneration = ( ) => { const { pipelineBuilder: { - aiPipeline: { aiPipelineFetchId: existingFetchId }, + aiPipeline: { aiPipelineRequestId: existingRequestId }, pipelineMode, }, namespace, dataService: { dataService }, } = getState(); - const editor_view_type = pipelineMode === 'builder-ui' ? 'stages' : 'text'; - track('AI Prompt Submitted', () => ({ - editor_view_type, - user_input_length: userInput.length, - })); + const provideSampleDocuments = + preferences.getPreferences().enableGenAISampleDocumentPassing; - if (aiPipelineFetchId !== -1) { + const editor_view_type = pipelineMode === 'builder-ui' ? 'stages' : 'text'; + if (existingRequestId !== null) { // Cancel the active request as this one will override. - abort(existingFetchId); + abort(existingRequestId); } const abortController = new AbortController(); - const { id: fetchId, signal } = getAbortSignal(); + const { id: requestId, signal } = getAbortSignal(); + + track('AI Prompt Submitted', () => ({ + editor_view_type, + user_input_length: userInput.length, + request_id: requestId, + has_sample_documents: provideSampleDocuments, + })); dispatch({ type: AIPipelineActionTypes.AIPipelineStarted, - fetchId, + requestId, }); let jsonResponse; @@ -253,7 +268,13 @@ export const runAIPipelineGeneration = ( collectionName, databaseName, schema, - // sampleDocuments, // For now we are not passing sample documents to the ai. + // Provide sample documents when the user has opted in in their settings. + ...(provideSampleDocuments + ? { + sampleDocuments, + } + : undefined), + requestId, }); } catch (err: any) { if (signal.aborted) { @@ -268,6 +289,7 @@ export const runAIPipelineGeneration = ( errorName: 'request_error', track, log, + requestId, }); // We're going to reset input state with this error, show the error in the // toast instead @@ -289,14 +311,15 @@ export const runAIPipelineGeneration = ( } finally { // Remove the AbortController from the Map as we either finished // waiting for the fetch or cancelled at this point. - cleanupAbortSignal(fetchId); + cleanupAbortSignal(requestId); } if (signal.aborted) { log.info( mongoLogId(1_001_000_231), 'AIPipeline', - 'Cancelled ai pipeline request' + 'Cancelled ai pipeline request', + { requestId } ); return; } @@ -314,6 +337,7 @@ export const runAIPipelineGeneration = ( errorName: 'empty_pipeline_error', track, log, + requestId, }); dispatch({ type: AIPipelineActionTypes.AIPipelineFailed, @@ -332,6 +356,7 @@ export const runAIPipelineGeneration = ( editorViewType: editor_view_type, syntaxErrors: !!(pipelineBuilder.syntaxError?.length > 0), shape: pipelineBuilder.stages.map((stage) => stage.operator), + requestId, } ); @@ -339,6 +364,7 @@ export const runAIPipelineGeneration = ( editor_view_type, syntax_errors: !!(pipelineBuilder.syntaxError?.length > 0), query_shape: pipelineBuilder.stages.map((stage) => stage.operator), + request_id: requestId, })); dispatch({ @@ -347,6 +373,7 @@ export const runAIPipelineGeneration = ( pipelineText: pipelineBuilder.source, pipeline: pipelineBuilder.pipeline, syntaxErrors: pipelineBuilder.syntaxError, + requestId, }); dispatch(updatePipelinePreview()); @@ -363,7 +390,11 @@ export const cancelAIPipelineGeneration = (): PipelineBuilderThunkAction< > => { return (dispatch, getState) => { // Abort any ongoing op. - abort(getState().pipelineBuilder.aiPipeline.aiPipelineFetchId); + const existingRequestId = + getState().pipelineBuilder.aiPipeline.aiPipelineRequestId; + if (existingRequestId !== null) { + abort(existingRequestId); + } dispatch({ type: AIPipelineActionTypes.CancelAIPipelineGeneration, @@ -438,7 +469,7 @@ const aiPipelineReducer: Reducer = ( ...state, status: 'fetching', errorMessage: undefined, - aiPipelineFetchId: action.fetchId, + aiPipelineRequestId: action.requestId, }; } @@ -457,7 +488,7 @@ const aiPipelineReducer: Reducer = ( return { ...state, status: 'ready', - aiPipelineFetchId: -1, + aiPipelineRequestId: null, errorMessage: action.errorMessage, errorCode: action.errorCode, }; @@ -472,7 +503,8 @@ const aiPipelineReducer: Reducer = ( return { ...state, status: 'success', - aiPipelineFetchId: -1, + aiPipelineRequestId: null, + lastAIPipelineRequestId: action.requestId, }; } @@ -485,9 +517,10 @@ const aiPipelineReducer: Reducer = ( return { ...state, status: 'success', - aiPipelineFetchId: -1, + aiPipelineRequestId: null, isInputVisible: true, isAggregationGeneratedFromQuery: true, + lastAIPipelineRequestId: action.requestId, aiPromptText: action.text, }; } @@ -501,7 +534,7 @@ const aiPipelineReducer: Reducer = ( return { ...state, status: 'ready', - aiPipelineFetchId: -1, + aiPipelineRequestId: null, }; } diff --git a/packages/compass-aggregations/src/modules/pipeline-builder/pipeline-builder.spec.ts b/packages/compass-aggregations/src/modules/pipeline-builder/pipeline-builder.spec.ts index e55bb2b8677..5990ce56bfc 100644 --- a/packages/compass-aggregations/src/modules/pipeline-builder/pipeline-builder.spec.ts +++ b/packages/compass-aggregations/src/modules/pipeline-builder/pipeline-builder.spec.ts @@ -61,7 +61,7 @@ describe('PipelineBuilder', function () { expect(pipelineBuilder.stages.length).to.equal(1); const stage = pipelineBuilder.stages[0]; expect(stage.operator).to.equal('$match'); - expect(stage.value).to.equal('{\n name: /berlin/i,\n}'); + expect(stage.value).to.equal('{\n name: /berlin/i\n}'); expect(stage.disabled).to.equal(false); expect(stage.syntaxError).to.be.null; }); @@ -215,15 +215,15 @@ describe('PipelineBuilder', function () { expect(pipelineBuilder.source).to.eq(`[ { $match: { - _id: 1, - }, + _id: 1 + } }, { - $limit: 10, + $limit: 10 }, { - $out: "test-out", - }, + $out: "test-out" + } ]`); }); diff --git a/packages/compass-aggregations/src/modules/pipeline-builder/pipeline-parser/pipeline-parser.spec.ts b/packages/compass-aggregations/src/modules/pipeline-builder/pipeline-parser/pipeline-parser.spec.ts index a8eb489d8a7..1af20554db7 100644 --- a/packages/compass-aggregations/src/modules/pipeline-builder/pipeline-parser/pipeline-parser.spec.ts +++ b/packages/compass-aggregations/src/modules/pipeline-builder/pipeline-parser/pipeline-parser.spec.ts @@ -8,8 +8,8 @@ const pipelines = [ input: `[{$unwind: "users"}]`, output: `[ { - $unwind: "users", - }, + $unwind: "users" + } ]`, pipeline: [ { @@ -25,10 +25,10 @@ const pipelines = [ input: `[\n//{$unwind: "users"},\n // {$limit: 20}\n]`, output: `[ // { - // $unwind: "users", + // $unwind: "users" // } // { - // $limit: 20, + // $limit: 20 // } ]`, pipeline: [ @@ -51,16 +51,16 @@ const pipelines = [ input: `[{$unwind: "users"},\n // {$limit: 20},\n {$sort: {name: -1}}\n// trailing comment\n]`, output: `[ { - $unwind: "users", + $unwind: "users" }, // { - // $limit: 20, + // $limit: 20 // } { $sort: { - name: -1, - }, - }, + name: -1 + } + } // trailing comment ]`, pipeline: [ @@ -91,15 +91,15 @@ const pipelines = [ input: `[{$unwind: "users"},{$limit: 20},\n// {$sort: {name: -1}}\n]`, output: `[ { - $unwind: "users", + $unwind: "users" }, { - $limit: 20, - }, + $limit: 20 + } // { // $sort: { - // name: -1, - // }, + // name: -1 + // } // } ]`, pipeline: [ @@ -130,14 +130,14 @@ const pipelines = [ input: `[// {$match: {}}\n // {$unwind: "users"}, \n {$limit: 20}\n]`, output: `[ // { - // $match: {}, + // $match: {} // } // { - // $unwind: "users", + // $unwind: "users" // } { - $limit: 20, - }, + $limit: 20 + } ]`, pipeline: [ { @@ -175,77 +175,77 @@ const pipelines = [ // { // $match: { // name: { - // $in: [/ber/i, /bas/i], + // $in: [/ber/i, /bas/i] // }, // bathrooms: { - // $gte: 2, - // }, - // }, + // $gte: 2 + // } + // } // }, // { // where: { - // name: 'berlin', - // }, + // name: 'berlin' + // } // } { $project: { _id: 1, name: 1, - bathrooms: 1, - }, + bathrooms: 1 + } }, // Another comment { // Fixed the bug $sort: { - bathrooms: -1, - }, + bathrooms: -1 + } }, { - $skip: 1, + $skip: 1 }, { // This should not go away! - $limit: 8, - }, + $limit: 8 + } ]`, output: `[ // { // $match: { // name: { - // $in: [/ber/i, /bas/i], + // $in: [/ber/i, /bas/i] // }, // bathrooms: { - // $gte: 2, - // }, - // }, + // $gte: 2 + // } + // } // } // { // where: { - // name: 'berlin', - // }, + // name: 'berlin' + // } // } { $project: { _id: 1, name: 1, - bathrooms: 1, - }, + bathrooms: 1 + } }, // Another comment { // Fixed the bug $sort: { - bathrooms: -1, - }, + bathrooms: -1 + } }, { - $skip: 1, + $skip: 1 }, { // This should not go away! - $limit: 8, - }, + $limit: 8 + } ]`, pipeline: [ { @@ -340,12 +340,12 @@ const pipelines = [ // { // $match: { // name: { - // $in: [/ber/i, /bas/i], + // $in: [/ber/i, /bas/i] // }, // bathrooms: { - // $gte: 2, - // }, - // }, + // $gte: 2 + // } + // } // } // { // where: { @@ -356,23 +356,23 @@ const pipelines = [ // $project: { // _id: 1, // name: 1, - // bathrooms: 1, - // }, + // bathrooms: 1 + // } // } // Another comment { // Fixed the bug $sort: { - bathrooms: -1, - }, + bathrooms: -1 + } }, { - $skip: 1, + $skip: 1 }, { // This should not go away! - $limit: 8, - }, + $limit: 8 + } ]`, pipeline: [ { @@ -514,15 +514,15 @@ describe('PipelineParser', function () { expect(newPipeline).to.equal(`[ // { // $match: { - // name: /berlin/i, - // }, + // name: /berlin/i + // } // } // { - // $unwind: "users", + // $unwind: "users" // } { - $limit: 20, - }, + $limit: 20 + } ]`); }); it('stage with leading comments', function () { @@ -542,12 +542,12 @@ describe('PipelineParser', function () { // Some comment that should be preserved. // { // $match: { - // name: /berlin/i, - // }, + // name: /berlin/i + // } // } { - $skip: 10, - }, + $skip: 10 + } ]`); }); it('stage with trailing comments', function () { @@ -568,15 +568,15 @@ describe('PipelineParser', function () { expect(newPipeline).to.equal(`[ // { // $match: { - // name: /berlin/i, - // }, + // name: /berlin/i + // } // } // Some comment that should be preserved. // Followed by something. // Followed by something else. { - $skip: 10, - }, + $skip: 10 + } ]`); }); it('stage with leading and trailing comments', function () { @@ -598,14 +598,14 @@ describe('PipelineParser', function () { // Some comment that should be preserved. // { // $match: { - // name: /berlin/i, - // }, + // name: /berlin/i + // } // } // Followed by something. // Followed by something else. { - $skip: 10, - }, + $skip: 10 + } ]`); }); it('stages with leading and trailing comments', function () { @@ -628,17 +628,17 @@ describe('PipelineParser', function () { // Some comment that should be preserved. // { // $match: { - // name: /berlin/i, - // }, + // name: /berlin/i + // } // } // Followed by something. // { - // $limit: 20, + // $limit: 20 // } // Followed by something else. { - $skip: 10, - }, + $skip: 10 + } ]`); }); }); diff --git a/packages/compass-aggregations/src/modules/pipeline-builder/pipeline-parser/stage-parser.spec.ts b/packages/compass-aggregations/src/modules/pipeline-builder/pipeline-parser/stage-parser.spec.ts index 1fd36349bdf..e12254bb431 100644 --- a/packages/compass-aggregations/src/modules/pipeline-builder/pipeline-parser/stage-parser.spec.ts +++ b/packages/compass-aggregations/src/modules/pipeline-builder/pipeline-parser/stage-parser.spec.ts @@ -19,7 +19,7 @@ describe('StageParser', function () { usecase: 'stage with object value', expression: `{$match: {name: /berlin/i}}`, operator: '$match', - value: `{\n name: /berlin/i,\n}`, + value: `{\n name: /berlin/i\n}`, isValid: true, }, { @@ -57,7 +57,7 @@ describe('StageParser', function () { operator: '$match', value: `{ // $match filters data - name: /berlin/i, + name: /berlin/i } /* $match corresponds to sql where */`, isValid: true, @@ -176,9 +176,7 @@ describe('StageParser', function () { comments.forEach(({ type }) => expect(type).to.equal('CommentLine')); const value = comments.map(({ value }) => value).join('\n'); expect(value).to.equal( - [' {', ' $match: {', ' name: /berlin/i,', ' },', ' }'].join( - '\n' - ) + [' {', ' $match: {', ' name: /berlin/i', ' }', ' }'].join('\n') ); }); }); @@ -222,7 +220,7 @@ describe('StageParser', function () { [ '{', ' // stage comment', - ' name: "berlin",', + ' name: "berlin"', ' // filters data on berlin', '}', ].join('\n') @@ -246,13 +244,9 @@ describe('StageParser', function () { expect(getStageOperatorFromNode(stage)).to.equal('$concatArrays'); expect(getStageValueFromNode(stage)).to.equal( - [ - '[', - ' // concat tags together', - ' "tags1",', - ' "tags2",', - ']', - ].join('\n') + ['[', ' // concat tags together', ' "tags1",', ' "tags2"', ']'].join( + '\n' + ) ); }); @@ -328,7 +322,7 @@ describe('StageParser', function () { [ '{', ' /* stage comment */', - ' name: "berlin",', + ' name: "berlin"', ' /**', ' * filters data on berlin', ' **/', @@ -350,7 +344,7 @@ describe('StageParser', function () { expect(getStageOperatorFromNode(stage)).to.equal('$match'); expect(getStageValueFromNode(stage)).to.equal( - ['{', ' name: "berlin",', '}'].join('\n') + ['{', ' name: "berlin"', '}'].join('\n') ); stage = stageParser.push(lines[3]) as StageLike; @@ -378,7 +372,7 @@ describe('StageParser', function () { expect(getStageOperatorFromNode(stage)).to.equal('$match'); expect(getStageValueFromNode(stage)).to.equal( - ['{', ' name: "berlin",', '}'].join('\n') + ['{', ' name: "berlin"', '}'].join('\n') ); let isStage = stageParser.push(lines[3]); @@ -412,8 +406,8 @@ describe('StageParser', function () { // }, { $match: { - _id: 1, - }, + _id: 1 + } }`); }); @@ -437,12 +431,12 @@ describe('StageParser', function () { expect(generate(stages[0])).to.eq(`{ $match: { - _id: 1, - }, + _id: 1 + } }`); expect(generate(stages[1])).to.eq(`{ - $limit: 10, + $limit: 10 }`); }); }); diff --git a/packages/compass-aggregations/src/modules/pipeline-builder/pipeline-parser/utils.spec.ts b/packages/compass-aggregations/src/modules/pipeline-builder/pipeline-parser/utils.spec.ts index 7bb1ab59e6f..db1f46fbb56 100644 --- a/packages/compass-aggregations/src/modules/pipeline-builder/pipeline-parser/utils.spec.ts +++ b/packages/compass-aggregations/src/modules/pipeline-builder/pipeline-parser/utils.spec.ts @@ -16,9 +16,9 @@ describe('PipelineParser Utils', function () { ' {', ' $match: {', ' name: /berlin/i,', - ' country: "Germany",', - ' },', - ' },', + ' country: "Germany"', + ' }', + ' }', ']', ].join('\n') ); diff --git a/packages/compass-aggregations/src/modules/pipeline-builder/stage-editor.spec.ts b/packages/compass-aggregations/src/modules/pipeline-builder/stage-editor.spec.ts index 50fa18cf28b..4fe69844be5 100644 --- a/packages/compass-aggregations/src/modules/pipeline-builder/stage-editor.spec.ts +++ b/packages/compass-aggregations/src/modules/pipeline-builder/stage-editor.spec.ts @@ -29,6 +29,7 @@ import { getId } from './stage-ids'; import { defaultPreferencesInstance } from 'compass-preferences-model'; import { createNoopLoggerAndTelemetry } from '@mongodb-js/compass-logging/provider'; import AppRegistry from 'hadron-app-registry'; +import { TEST_CONNECTION_INFO } from '@mongodb-js/compass-connections/provider'; const MATCH_STAGE: StoreStage = mapBuilderStageToStoreStage( { @@ -122,6 +123,12 @@ function createStore({ workspaces: {} as any, preferences, logger: createNoopLoggerAndTelemetry(), + dataService: {} as any, + connectionInfoAccess: { + getCurrentConnectionInfo() { + return TEST_CONNECTION_INFO; + }, + }, }) ) ); diff --git a/packages/compass-aggregations/src/modules/pipeline-builder/stage-editor.ts b/packages/compass-aggregations/src/modules/pipeline-builder/stage-editor.ts index 145a5bc4c5e..730812ced69 100644 --- a/packages/compass-aggregations/src/modules/pipeline-builder/stage-editor.ts +++ b/packages/compass-aggregations/src/modules/pipeline-builder/stage-editor.ts @@ -287,7 +287,7 @@ export const loadStagePreview = ( collationString, limit, largeLimit, - inputDocuments, + collectionStats, } = getState(); const options: PreviewOptions = { @@ -295,7 +295,7 @@ export const loadStagePreview = ( collation: collationString.value ?? undefined, sampleSize: largeLimit ?? DEFAULT_SAMPLE_SIZE, previewSize: limit ?? DEFAULT_PREVIEW_LIMIT, - totalDocumentCount: inputDocuments.count ?? undefined, + totalDocumentCount: collectionStats?.document_count, }; const previewDocs = await pipelineBuilder.getPreviewForStage( diff --git a/packages/compass-aggregations/src/modules/pipeline-builder/stage.spec.ts b/packages/compass-aggregations/src/modules/pipeline-builder/stage.spec.ts index 4899f8bca87..52176b81aa9 100644 --- a/packages/compass-aggregations/src/modules/pipeline-builder/stage.spec.ts +++ b/packages/compass-aggregations/src/modules/pipeline-builder/stage.spec.ts @@ -67,17 +67,17 @@ describe('Stage', function () { const stage = new Stage(ast); expect(stage.value).to.eq(`{ - _id: 1, + _id: 1 } /* trailing comment */`); stage.changeValue(`{ - _id: 1, + _id: 1 } /* new comment */`); expect(stage.toString()).to.eq(`{ $match: { - _id: 1, - } /* new comment */, + _id: 1 + } /* new comment */ }`); }); diff --git a/packages/compass-aggregations/src/modules/pipeline-builder/stage.ts b/packages/compass-aggregations/src/modules/pipeline-builder/stage.ts index cba94ccf771..ecac6f55b7c 100644 --- a/packages/compass-aggregations/src/modules/pipeline-builder/stage.ts +++ b/packages/compass-aggregations/src/modules/pipeline-builder/stage.ts @@ -150,10 +150,7 @@ export default class Stage { this.node.properties[0].trailingComments; } - str = generate(template, { - // To avoid trailing comma after the stage value placeholder - trailingComma: 'none', - }) + str = generate(template) .replace('$$_STAGE_OPERATOR', this.operator ?? '') .replace('$$_STAGE_VALUE', this.value ?? ''); } diff --git a/packages/compass-aggregations/src/modules/pipeline-builder/text-editor-pipeline.ts b/packages/compass-aggregations/src/modules/pipeline-builder/text-editor-pipeline.ts index 6ea8e964868..d9f0e6056be 100644 --- a/packages/compass-aggregations/src/modules/pipeline-builder/text-editor-pipeline.ts +++ b/packages/compass-aggregations/src/modules/pipeline-builder/text-editor-pipeline.ts @@ -219,7 +219,7 @@ export const loadPreviewForPipeline = (): PipelineBuilderThunkAction< collationString, limit, largeLimit, - inputDocuments, + collectionStats, dataService, pipelineBuilder: { textEditor: { @@ -256,7 +256,7 @@ export const loadPreviewForPipeline = (): PipelineBuilderThunkAction< collation: collationString.value ?? undefined, sampleSize: largeLimit ?? DEFAULT_SAMPLE_SIZE, previewSize: limit ?? DEFAULT_PREVIEW_LIMIT, - totalDocumentCount: inputDocuments.count ?? undefined, + totalDocumentCount: collectionStats?.document_count, }; const previewDocs = await pipelineBuilder.getPreviewForPipeline( diff --git a/packages/compass-aggregations/src/modules/saving-pipeline.ts b/packages/compass-aggregations/src/modules/saving-pipeline.ts index 206092de3e0..4bc3c5a85b0 100644 --- a/packages/compass-aggregations/src/modules/saving-pipeline.ts +++ b/packages/compass-aggregations/src/modules/saving-pipeline.ts @@ -163,7 +163,11 @@ export const savingPipelineOpen = ({ * @see create-view src/stores/create-view.js */ export const openCreateView = (): PipelineBuilderThunkAction => { - return (_dispatch, getState, { pipelineBuilder, globalAppRegistry }) => { + return ( + _dispatch, + getState, + { pipelineBuilder, globalAppRegistry, connectionInfoAccess } + ) => { const state = getState(); const sourceNs = state.namespace; const sourcePipeline = getPipelineFromBuilderState( @@ -176,6 +180,9 @@ export const openCreateView = (): PipelineBuilderThunkAction => { pipeline: sourcePipeline, }; - globalAppRegistry.emit('open-create-view', meta); + const { id: connectionId } = + connectionInfoAccess.getCurrentConnectionInfo(); + + globalAppRegistry.emit('open-create-view', meta, { connectionId }); }; }; diff --git a/packages/compass-aggregations/src/modules/update-view.spec.ts b/packages/compass-aggregations/src/modules/update-view.spec.ts index 19e9948f6e5..7706a694d52 100644 --- a/packages/compass-aggregations/src/modules/update-view.spec.ts +++ b/packages/compass-aggregations/src/modules/update-view.spec.ts @@ -3,6 +3,7 @@ import { expect } from 'chai'; import { ERROR_UPDATING_VIEW, updateView } from './update-view'; import { createNoopLoggerAndTelemetry } from '@mongodb-js/compass-logging/provider'; import AppRegistry from 'hadron-app-registry'; +import { TEST_CONNECTION_INFO } from '@mongodb-js/compass-connections/provider'; describe('update-view module', function () { const thunkArg = { @@ -20,6 +21,11 @@ describe('update-view module', function () { openCollectionWorkspace() {}, }, logger: createNoopLoggerAndTelemetry(), + connectionInfoAccess: { + getCurrentConnectionInfo() { + return TEST_CONNECTION_INFO; + }, + }, }; describe('#updateView', function () { diff --git a/packages/compass-aggregations/src/modules/update-view.ts b/packages/compass-aggregations/src/modules/update-view.ts index 5869c67faff..52381ac9039 100644 --- a/packages/compass-aggregations/src/modules/update-view.ts +++ b/packages/compass-aggregations/src/modules/update-view.ts @@ -82,7 +82,13 @@ export const updateView = (): PipelineBuilderThunkAction> => { return async ( dispatch, getState, - { pipelineBuilder, workspaces, logger: { track, debug }, globalAppRegistry } + { + pipelineBuilder, + workspaces, + logger: { track, debug }, + globalAppRegistry, + connectionInfoAccess, + } ) => { dispatch(dismissViewError()); @@ -94,6 +100,9 @@ export const updateView = (): PipelineBuilderThunkAction> => { return; } + const { id: connectionId } = + connectionInfoAccess.getCurrentConnectionInfo(); + const viewPipeline = getPipelineFromBuilderState( getState(), pipelineBuilder @@ -111,7 +120,7 @@ export const updateView = (): PipelineBuilderThunkAction> => { }); debug('selecting namespace', viewNamespace); globalAppRegistry.emit('view-edited', viewNamespace); - workspaces.openCollectionWorkspace(viewNamespace); + workspaces.openCollectionWorkspace(connectionId, viewNamespace); } catch (e: any) { debug('Unexpected error updating view', e); dispatch(updateViewErrorOccured(e)); diff --git a/packages/compass-aggregations/src/stores/create-view.spec.ts b/packages/compass-aggregations/src/stores/create-view.spec.ts index ba306eb3fbb..2bf76363427 100644 --- a/packages/compass-aggregations/src/stores/create-view.spec.ts +++ b/packages/compass-aggregations/src/stores/create-view.spec.ts @@ -1,6 +1,13 @@ -import AppRegistry from 'hadron-app-registry'; +import AppRegistry, { createActivateHelpers } from 'hadron-app-registry'; import { activateCreateViewPlugin } from './create-view'; import { expect } from 'chai'; +import { + ConnectionsManager, + type DataService, +} from '@mongodb-js/compass-connections/provider'; +import { changeViewName, createView } from '../modules/create-view'; +import Sinon from 'sinon'; +import type { WorkspacesService } from '@mongodb-js/compass-workspaces/provider'; describe('CreateViewStore [Store]', function () { if ( @@ -14,49 +21,105 @@ describe('CreateViewStore [Store]', function () { let store: any; let deactivate: any; - const globalAppRegistry = new AppRegistry(); - const ds = 'data-service' as any; - const logger = {} as any; + let globalAppRegistry: AppRegistry; + let appRegistryEmitSpy: Sinon.SinonSpy; + const logger = { track() {} } as any; + const createViewStub = Sinon.stub(); + const dataService = { + createView: createViewStub, + } as unknown as DataService; + const connectionsManager = new ConnectionsManager({ logger }); + const openCollectionWorkspaceStub = Sinon.stub(); + const workspaces = { + openCollectionWorkspace: openCollectionWorkspaceStub, + } as unknown as WorkspacesService; beforeEach(function () { + globalAppRegistry = new AppRegistry(); + appRegistryEmitSpy = Sinon.spy(globalAppRegistry, 'emit'); + Sinon.stub(connectionsManager, 'getDataServiceForConnection').returns( + dataService + ); ({ store, deactivate } = activateCreateViewPlugin( {}, { globalAppRegistry, - dataService: ds, + connectionsManager, logger, - workspaces: {} as any, - } + workspaces, + }, + createActivateHelpers() )); }); afterEach(function () { store = null; + Sinon.restore(); deactivate(); }); describe('#configureStore', function () { describe('when open create view is emitted', function () { - beforeEach(function () { - globalAppRegistry.emit('open-create-view', { - source: 'dataService.test', - pipeline: [{ $project: { a: 1 } }], - }); + it('throws an error when the action is emitted without connection meta', function () { + expect(() => { + globalAppRegistry.emit('open-create-view', { + source: 'dataService.test', + pipeline: [{ $project: { a: 1 } }], + }); + }).to.throw; }); - - it('dispatches the toggle action', function () { + it('dispatches the open action and sets the correct state', function () { + globalAppRegistry.emit( + 'open-create-view', + { + source: 'dataService.test', + pipeline: [{ $project: { a: 1 } }], + }, + { + connectionId: 'TEST', + } + ); expect(store.getState().isVisible).to.equal(true); - }); - - it('sets the pipeline', function () { expect(store.getState().pipeline).to.deep.equal([ { $project: { a: 1 } }, ]); - }); - - it('sets the source', function () { expect(store.getState().source).to.equal('dataService.test'); + expect(store.getState().connectionId).to.equal('TEST'); }); }); + + it('handles createView action and notifies the rest of the app', async function () { + globalAppRegistry.emit( + 'open-create-view', + { + source: 'dataService.test', + pipeline: [{ $project: { a: 1 } }], + }, + { + connectionId: 'TEST', + } + ); + store.dispatch(changeViewName('TestView')); + await store.dispatch(createView()); + + expect(createViewStub).to.be.calledWithExactly( + 'TestView', + 'dataService.test', + [{ $project: { a: 1 } }], + {} + ); + + expect(appRegistryEmitSpy.lastCall).to.be.calledWithExactly( + 'view-created', + 'dataService.TestView', + { connectionId: 'TEST' } + ); + + expect(openCollectionWorkspaceStub).to.be.calledWithExactly( + 'TEST', + 'dataService.TestView', + { newTab: true } + ); + }); }); }); diff --git a/packages/compass-aggregations/src/stores/create-view.ts b/packages/compass-aggregations/src/stores/create-view.ts index 70b9eb693fb..a09e3a086b7 100644 --- a/packages/compass-aggregations/src/stores/create-view.ts +++ b/packages/compass-aggregations/src/stores/create-view.ts @@ -5,13 +5,14 @@ import thunk from 'redux-thunk'; import type { CreateViewAction } from '../modules/create-view'; import reducer, { open } from '../modules/create-view'; import type AppRegistry from 'hadron-app-registry'; -import type { DataService } from 'mongodb-data-service'; import type { LoggerAndTelemetry } from '@mongodb-js/compass-logging/provider'; import type { WorkspacesService } from '@mongodb-js/compass-workspaces/provider'; +import type { ConnectionsManager } from '@mongodb-js/compass-connections/provider'; +import type { ActivateHelpers } from 'hadron-app-registry'; type CreateViewServices = { globalAppRegistry: AppRegistry; - dataService: Pick; + connectionsManager: ConnectionsManager; logger: LoggerAndTelemetry; workspaces: WorkspacesService; }; @@ -32,31 +33,57 @@ export type CreateViewThunkAction< A extends Action = CreateViewAction > = ThunkAction; +type OpenCreateViewEventParams = { + source: string; + pipeline: any[]; + duplicate?: boolean; +}; + +type ConnectionMeta = { + connectionId?: string; +}; + export function activateCreateViewPlugin( _: unknown, - { globalAppRegistry, dataService, logger, workspaces }: CreateViewServices + { + globalAppRegistry, + connectionsManager, + logger, + workspaces, + }: CreateViewServices, + { on, cleanup }: ActivateHelpers ) { const store = configureStore({ globalAppRegistry, - dataService, + connectionsManager, logger, workspaces, }); - const onOpenCreateView = (meta: { - source: string; - pipeline: any[]; - duplicate?: boolean; - }) => { - store.dispatch(open(meta.source, meta.pipeline, meta.duplicate ?? false)); - }; + on( + globalAppRegistry, + 'open-create-view', + function ( + eventParams: OpenCreateViewEventParams, + connectionMeta: ConnectionMeta = {} + ) { + if (!connectionMeta.connectionId) { + throw new Error('Cannot open CreateViewModal without a connectionId'); + } - globalAppRegistry.on('open-create-view', onOpenCreateView); + store.dispatch( + open({ + connectionId: connectionMeta.connectionId, + sourceNs: eventParams.source, + sourcePipeline: eventParams.pipeline, + duplicate: eventParams.duplicate ?? false, + }) + ); + } + ); return { store, - deactivate(this: void) { - globalAppRegistry.removeListener('open-create-view', onOpenCreateView); - }, + deactivate: cleanup, }; } diff --git a/packages/compass-aggregations/src/stores/store.spec.ts b/packages/compass-aggregations/src/stores/store.spec.ts index 3681862598d..dd74ad895e1 100644 --- a/packages/compass-aggregations/src/stores/store.spec.ts +++ b/packages/compass-aggregations/src/stores/store.spec.ts @@ -99,6 +99,7 @@ describe('Aggregation Store', function () { isDataLake: INITIAL_STATE.isDataLake, pipelineBuilder: INITIAL_STATE.pipelineBuilder, focusMode: INITIAL_STATE.focusMode, + collectionStats: INITIAL_STATE.collectionStats, collectionsFields: INITIAL_STATE.collectionsFields, searchIndexes: INITIAL_STATE.searchIndexes, }); @@ -124,12 +125,13 @@ describe('Aggregation Store', function () { const unsubscribe = store.subscribe(() => { unsubscribe(); expect(store.getState().pipelineBuilder.aiPipeline).to.deep.equal({ - aiPipelineFetchId: -1, + aiPipelineRequestId: null, aiPromptText: 'group by price', errorMessage: undefined, errorCode: undefined, isAggregationGeneratedFromQuery: true, isInputVisible: true, + lastAIPipelineRequestId: 'abc', status: 'success', }); done(); @@ -140,6 +142,7 @@ describe('Aggregation Store', function () { aggregation: { pipeline: '[{ $group: { _id: "$price" } }]', }, + requestId: 'abc', }); }); }); diff --git a/packages/compass-aggregations/src/stores/store.ts b/packages/compass-aggregations/src/stores/store.ts index 6031ee55d6d..2ee706ccc3b 100644 --- a/packages/compass-aggregations/src/stores/store.ts +++ b/packages/compass-aggregations/src/stores/store.ts @@ -36,6 +36,12 @@ import type { AtlasAiService } from '@mongodb-js/compass-generative-ai/provider' import type { AtlasAuthService } from '@mongodb-js/atlas-service/provider'; import type { PipelineStorage } from '@mongodb-js/my-queries-storage/provider'; import { maxTimeMSChanged } from '../modules/max-time-ms'; +import type { ConnectionInfoAccess } from '@mongodb-js/compass-connections/provider'; +import type { Collection } from '@mongodb-js/compass-app-stores/provider'; +import { + pickCollectionStats, + collectionStatsFetched, +} from '../modules/collection-stats'; export type ConfigureStoreOptions = CollectionTabPluginMetadata & Partial<{ @@ -72,6 +78,8 @@ export type AggregationsPluginServices = { atlasAuthService: AtlasAuthService; atlasAiService: AtlasAiService; pipelineStorage?: PipelineStorage; + connectionInfoAccess: ConnectionInfoAccess; + collection: Collection; }; export function activateAggregationsPlugin( @@ -87,6 +95,8 @@ export function activateAggregationsPlugin( atlasAiService, atlasAuthService, pipelineStorage, + connectionInfoAccess, + collection: collectionModel, }: AggregationsPluginServices, { on, cleanup, addCleanup }: ActivateHelpers ) { @@ -158,6 +168,7 @@ export function activateAggregationsPlugin( // should be 'hidden'. localStorage.getItem(INITIAL_PANEL_OPEN_LOCAL_STORAGE_KEY) === 'true', }, + collectionStats: pickCollectionStats(collectionModel), }, applyMiddleware( thunk.withExtraArgument({ @@ -171,6 +182,7 @@ export function activateAggregationsPlugin( preferences, logger, atlasAiService, + connectionInfoAccess, }) ) ); @@ -197,6 +209,12 @@ export function activateAggregationsPlugin( } }); + on(collectionModel, 'change:status', (model: Collection, status: string) => { + if (status === 'ready') { + store.dispatch(collectionStatsFetched(model)); + } + }); + // If stored pipeline was passed through options and we are not editing, // restore pipeline if (!editingView && options.aggregation) { diff --git a/packages/compass-aggregations/src/utils/modal-descriptions.tsx b/packages/compass-aggregations/src/utils/modal-descriptions.tsx new file mode 100644 index 00000000000..62d8abb5f78 --- /dev/null +++ b/packages/compass-aggregations/src/utils/modal-descriptions.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { Link } from '@mongodb-js/compass-components'; + +export const runPipelineConfirmationDescription = ({ + typeOfWrite, + stage, + ns, +}: { + typeOfWrite: string; + stage: { name: string; link: string }; + ns: string | null; +}) => { + return ( +
+ This pipeline will execute a{' '} + + {stage.name} + {' '} + operation, {typeOfWrite}{' '} + {ns ? ( + <> + {typeOfWrite} "{ns}" + + ) : ( + <>that may alter or overwrite a collectio + )}{' '} + "{ns}". Do you wish to proceed? +
+ ); +}; diff --git a/packages/compass-aggregations/test/configure-store.ts b/packages/compass-aggregations/test/configure-store.ts index b3a42168cfe..f091f26f025 100644 --- a/packages/compass-aggregations/test/configure-store.ts +++ b/packages/compass-aggregations/test/configure-store.ts @@ -9,6 +9,7 @@ import type { DataService } from '../src/modules/data-service'; import { ReadOnlyPreferenceAccess } from 'compass-preferences-model/provider'; import { createNoopLoggerAndTelemetry } from '@mongodb-js/compass-logging/provider'; import { AtlasAuthService } from '@mongodb-js/atlas-service/provider'; +import { TEST_CONNECTION_INFO } from '@mongodb-js/compass-connections/provider'; export class MockAtlasAuthService extends AtlasAuthService { isAuthenticated() { @@ -71,6 +72,15 @@ export default function configureStore( logger, atlasAiService: atlasAiService as any, atlasAuthService, + connectionInfoAccess: { + getCurrentConnectionInfo() { + return TEST_CONNECTION_INFO; + }, + }, + collection: { + toJSON: () => ({}), + on: () => {}, + } as any, ...services, }, createActivateHelpers() diff --git a/packages/compass-app-stores/.eslintrc.js b/packages/compass-app-stores/.eslintrc.js index f06f8fc6013..b54d314ab18 100644 --- a/packages/compass-app-stores/.eslintrc.js +++ b/packages/compass-app-stores/.eslintrc.js @@ -1,3 +1,4 @@ +'use strict'; module.exports = { root: true, extends: ['@mongodb-js/eslint-config-compass/plugin'], diff --git a/packages/compass-app-stores/.mocharc.js b/packages/compass-app-stores/.mocharc.js index a7e53abc444..61091edba73 100644 --- a/packages/compass-app-stores/.mocharc.js +++ b/packages/compass-app-stores/.mocharc.js @@ -1 +1,2 @@ +'use strict'; module.exports = require('@mongodb-js/mocha-config-compass/compass-plugin'); diff --git a/packages/compass-app-stores/package.json b/packages/compass-app-stores/package.json index 7cc728dc1e9..a8246ca71c2 100644 --- a/packages/compass-app-stores/package.json +++ b/packages/compass-app-stores/package.json @@ -11,7 +11,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "7.10.1", + "version": "7.12.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -30,7 +30,7 @@ }, "compass:exports": { ".": "./src/index.ts", - "./provider": "./src/provider.ts" + "./provider": "./src/provider.tsx" }, "types": "./dist/index.d.ts", "scripts": { @@ -53,10 +53,12 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", + "@testing-library/dom": "8.20.1", + "@testing-library/react": "12.1.5", "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/sinon-chai": "^3.2.5", @@ -72,13 +74,14 @@ "xvfb-maybe": "^0.2.1" }, "dependencies": { - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/connection-info": "^0.1.5", - "@mongodb-js/connection-storage": "^0.8.1", - "hadron-app-registry": "^9.1.8", - "mongodb-instance-model": "^12.18.1", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/connection-info": "^0.2.1", + "hadron-app-registry": "^9.1.10", + "mongodb-collection-model": "^5.19.1", + "mongodb-database-model": "^2.19.1", + "mongodb-instance-model": "^12.20.0", "mongodb-ns": "^2.4.0", "react": "^17.0.2" }, diff --git a/packages/compass-app-stores/provider.js b/packages/compass-app-stores/provider.js index a9d6fd5cfc3..5b56677514c 100644 --- a/packages/compass-app-stores/provider.js +++ b/packages/compass-app-stores/provider.js @@ -1 +1,2 @@ +'use strict'; module.exports = require('./dist/provider'); diff --git a/packages/compass-app-stores/src/instances-manager.spec.ts b/packages/compass-app-stores/src/instances-manager.spec.ts new file mode 100644 index 00000000000..dbf1a89a7fd --- /dev/null +++ b/packages/compass-app-stores/src/instances-manager.spec.ts @@ -0,0 +1,147 @@ +import { expect } from 'chai'; +import sinon from 'sinon'; +import { + MongoDBInstancesManager, + MongoDBInstancesManagerEvents, +} from './instances-manager'; +import { TEST_CONNECTION_INFO } from '@mongodb-js/compass-connections/provider'; +import { MongoDBInstance } from 'mongodb-instance-model'; + +describe('InstancesManager', function () { + let instancesManager: MongoDBInstancesManager; + beforeEach(function () { + instancesManager = new MongoDBInstancesManager(); + }); + + it('should be able to create and return a MongoDB instance', function () { + const instance = instancesManager.createMongoDBInstanceForConnection( + TEST_CONNECTION_INFO.id, + { + _id: 'test.com', + hostname: 'test.com', + port: 2000, + topologyDescription: { + type: '', + servers: [], + setName: '', + }, + } + ); + expect(instance).to.be.instanceOf(MongoDBInstance); + }); + + it('should be able to list all the MongoDB instances', function () { + instancesManager.createMongoDBInstanceForConnection( + TEST_CONNECTION_INFO.id, + { + _id: 'test.com', + hostname: 'test.com', + port: 2000, + topologyDescription: { + type: '', + servers: [], + setName: '', + }, + } + ); + expect(instancesManager.listMongoDBInstances()).to.have.lengthOf(1); + }); + + it('should emit instance created event when an instance is created', function () { + const onInstanceCreatedStub = sinon.stub(); + instancesManager.on( + MongoDBInstancesManagerEvents.InstanceCreated, + onInstanceCreatedStub + ); + const instance = instancesManager.createMongoDBInstanceForConnection( + TEST_CONNECTION_INFO.id, + { + _id: 'test.com', + hostname: 'test.com', + port: 2000, + topologyDescription: { + type: '', + servers: [], + setName: '', + }, + } + ); + expect(onInstanceCreatedStub).to.be.calledOnceWithExactly( + TEST_CONNECTION_INFO.id, + instance + ); + }); + + it('should be able return a MongoDBInstance if available', function () { + const nonExistentInstance = + instancesManager.getMongoDBInstanceForConnection('1234'); // non-existent + expect(nonExistentInstance).to.be.undefined; + instancesManager.createMongoDBInstanceForConnection( + TEST_CONNECTION_INFO.id, + { + _id: 'test.com', + hostname: 'test.com', + port: 2000, + topologyDescription: { + type: '', + servers: [], + setName: '', + }, + } + ); + const existingInstance = instancesManager.getMongoDBInstanceForConnection( + TEST_CONNECTION_INFO.id + ); + expect(existingInstance).to.not.be.undefined; + }); + + it('should be able to remove MongoDBInstance for a connection', function () { + instancesManager.createMongoDBInstanceForConnection( + TEST_CONNECTION_INFO.id, + { + _id: 'test.com', + hostname: 'test.com', + port: 2000, + topologyDescription: { + type: '', + servers: [], + setName: '', + }, + } + ); + expect( + instancesManager.getMongoDBInstanceForConnection(TEST_CONNECTION_INFO.id) + ).to.not.be.undefined; + instancesManager.removeMongoDBInstanceForConnection( + TEST_CONNECTION_INFO.id + ); + expect( + instancesManager.getMongoDBInstanceForConnection(TEST_CONNECTION_INFO.id) + ).to.be.undefined; + }); + + it('should emit instances removed event when an instance is removed', function () { + const onRemovedStub = sinon.stub(); + instancesManager.on( + MongoDBInstancesManagerEvents.InstanceRemoved, + onRemovedStub + ); + instancesManager.createMongoDBInstanceForConnection( + TEST_CONNECTION_INFO.id, + { + _id: 'test.com', + hostname: 'test.com', + port: 2000, + topologyDescription: { + type: '', + servers: [], + setName: '', + }, + } + ); + instancesManager.removeMongoDBInstanceForConnection( + TEST_CONNECTION_INFO.id + ); + expect(onRemovedStub).to.be.calledWithExactly(TEST_CONNECTION_INFO.id); + }); +}); diff --git a/packages/compass-app-stores/src/instances-manager.ts b/packages/compass-app-stores/src/instances-manager.ts new file mode 100644 index 00000000000..788533114d2 --- /dev/null +++ b/packages/compass-app-stores/src/instances-manager.ts @@ -0,0 +1,97 @@ +import { EventEmitter } from 'events'; +import { type ConnectionInfo } from '@mongodb-js/connection-info'; +import { + MongoDBInstance, + type MongoDBInstanceProps, +} from 'mongodb-instance-model'; + +export const MongoDBInstancesManagerEvents = { + InstanceCreated: 'instance-started', + InstanceRemoved: 'instance-removed', +} as const; + +type MongoDBInstancesManagerEvent = + typeof MongoDBInstancesManagerEvents[keyof typeof MongoDBInstancesManagerEvents]; + +export type MongoDBInstancesManagerEventListeners = { + [MongoDBInstancesManagerEvents.InstanceCreated]: ( + connectionInfoId: ConnectionInfo['id'], + instance: MongoDBInstance + ) => void; + + [MongoDBInstancesManagerEvents.InstanceRemoved]: ( + connectionInfoId: ConnectionInfo['id'] + ) => void; +}; + +export class MongoDBInstancesManager extends EventEmitter { + private readonly instances = new Map(); + + createMongoDBInstanceForConnection( + connectionInfoId: ConnectionInfo['id'], + instanceProps: Pick< + MongoDBInstanceProps, + '_id' | 'hostname' | 'port' | 'topologyDescription' + > & + Partial + ) { + const instance = new MongoDBInstance(instanceProps as MongoDBInstanceProps); + this.instances.set(connectionInfoId, instance); + this.emit( + MongoDBInstancesManagerEvents.InstanceCreated, + connectionInfoId, + instance + ); + return instance; + } + + // TODO(COMPASS-7831): Remove this method and its usage once the linked + // ticket's dependencies are resolved. + listMongoDBInstances() { + return new Map(this.instances); + } + + getMongoDBInstanceForConnection(connectionInfoId: ConnectionInfo['id']) { + return this.instances.get(connectionInfoId); + } + + removeMongoDBInstanceForConnection(connectionInfoId: ConnectionInfo['id']) { + this.instances.delete(connectionInfoId); + this.emit(MongoDBInstancesManagerEvents.InstanceRemoved, connectionInfoId); + } + + on( + eventName: T, + listener: MongoDBInstancesManagerEventListeners[T] + ): this { + return super.on(eventName, listener); + } + + off( + eventName: T, + listener: MongoDBInstancesManagerEventListeners[T] + ): this { + return super.off(eventName, listener); + } + + once( + eventName: T, + listener: MongoDBInstancesManagerEventListeners[T] + ): this { + return super.once(eventName, listener); + } + + removeListener( + eventName: T, + listener: MongoDBInstancesManagerEventListeners[T] + ): this { + return super.removeListener(eventName, listener); + } + + emit( + eventName: T, + ...args: Parameters + ): boolean { + return super.emit(eventName, ...args); + } +} diff --git a/packages/compass-app-stores/src/plugin.tsx b/packages/compass-app-stores/src/plugin.tsx index 3d2b7c4bbee..c30a0e5b28d 100644 --- a/packages/compass-app-stores/src/plugin.tsx +++ b/packages/compass-app-stores/src/plugin.tsx @@ -4,28 +4,27 @@ import { createLoggerAndTelemetryLocator } from '@mongodb-js/compass-logging/pro import type AppRegistry from 'hadron-app-registry'; import type { ActivateHelpers } from 'hadron-app-registry'; import { registerHadronPlugin } from 'hadron-app-registry'; -import type { MongoDBInstance } from 'mongodb-instance-model'; -import { InstancesContext } from './provider'; +import { MongoDBInstancesManagerContext } from './provider'; import { createInstancesStore } from './stores'; import { connectionsManagerLocator, type ConnectionsManager, } from '@mongodb-js/compass-connections/provider'; -import type { ConnectionInfo } from '@mongodb-js/connection-info'; +import { type MongoDBInstancesManager } from './instances-manager'; interface MongoDBInstancesProviderProps { children: React.ReactNode; - instances: Record; + instancesManager: MongoDBInstancesManager; } -function MongoDBInstancesProvider({ +function MongoDBInstancesManagerProvider({ children, - instances, + instancesManager, }: MongoDBInstancesProviderProps) { return ( - + {children} - + ); } @@ -38,8 +37,8 @@ export const CompassInstanceStorePlugin = registerHadronPlugin< >( { name: 'CompassInstanceStore', - component: MongoDBInstancesProvider as React.FunctionComponent< - Omit + component: MongoDBInstancesManagerProvider as React.FunctionComponent< + Omit >, activate( _: unknown, diff --git a/packages/compass-app-stores/src/provider.spec.tsx b/packages/compass-app-stores/src/provider.spec.tsx new file mode 100644 index 00000000000..15070433863 --- /dev/null +++ b/packages/compass-app-stores/src/provider.spec.tsx @@ -0,0 +1,112 @@ +import React from 'react'; +import { + NamespaceProvider, + MongoDBInstancesManagerProvider, + TestMongoDBInstanceManager, +} from './provider'; +import { render, screen, cleanup } from '@testing-library/react'; +import { expect } from 'chai'; +import { waitFor } from '@testing-library/dom'; +import Sinon from 'sinon'; + +describe('NamespaceProvider', function () { + const sandbox = Sinon.createSandbox(); + + afterEach(function () { + cleanup(); + sandbox.reset(); + }); + + it('should immediately render content if database exists', function () { + const instanceManager = new TestMongoDBInstanceManager({ + databases: [{ _id: 'foo' }] as any, + }); + render( + + hello + + ); + expect(screen.getByText('hello')).to.exist; + }); + + it('should immediately render content if collection exists', function () { + const instanceManager = new TestMongoDBInstanceManager({ + databases: [{ _id: 'foo', collections: [{ _id: 'foo.bar' }] }] as any, + }); + render( + + hello + + ); + expect(screen.getByText('hello')).to.exist; + }); + + it("should not render content when namespace doesn't exist", function () { + const instanceManager = new TestMongoDBInstanceManager(); + render( + + hello + + ); + expect(screen.queryByText('hello')).to.not.exist; + }); + + it('should render content eventually if namespace is resolved async', async function () { + const instanceManager = new TestMongoDBInstanceManager(); + const instance = instanceManager.getMongoDBInstanceForConnection(); + sandbox.stub(instance, 'fetchDatabases').callsFake(() => { + instance.databases.add({ _id: 'foo' }); + return Promise.resolve(); + }); + + render( + + hello + + ); + + expect(screen.queryByText('hello')).to.not.exist; + + await waitFor(function () { + expect(screen.queryByText('hello')).to.exist; + }); + }); + + it("should call onNamespaceFallbackSelect with database namespace if collection doesn't exist but database does", async function () { + const onNamespaceFallbackSelect = sandbox.spy(); + const instanceManager = new TestMongoDBInstanceManager({ + databases: [{ _id: 'foo' }] as any, + }); + render( + + + hello + + + ); + await waitFor(() => { + expect(onNamespaceFallbackSelect).to.be.calledOnceWithExactly('foo'); + }); + }); + + it('should call onNamespaceFallbackSelect with `null` if namespace is not found', async function () { + const onNamespaceFallbackSelect = sandbox.spy(); + const instanceManager = new TestMongoDBInstanceManager(); + render( + + + hello + + + ); + await waitFor(() => { + expect(onNamespaceFallbackSelect).to.be.calledOnceWithExactly(null); + }); + }); +}); diff --git a/packages/compass-app-stores/src/provider.ts b/packages/compass-app-stores/src/provider.ts deleted file mode 100644 index f6acb7c9973..00000000000 --- a/packages/compass-app-stores/src/provider.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { useConnectionInfo } from '@mongodb-js/connection-storage/provider'; -import type { ConnectionInfo } from '@mongodb-js/connection-info'; -import { createServiceLocator } from 'hadron-app-registry'; -import type { MongoDBInstance } from 'mongodb-instance-model'; -import { createContext, useContext } from 'react'; - -export const InstancesContext = createContext< - Record ->({}); - -export const MongoDBInstanceProvider = InstancesContext.Provider; - -export const mongoDBInstanceLocator = createServiceLocator( - function mongoDBInstanceLocator(): MongoDBInstance { - const connectionInfo = useConnectionInfo(); - const instances = useContext(InstancesContext); - const instance = instances[connectionInfo.id]; - if (!instance) { - throw new Error('No MongoDBInstance available in this context'); - } - return instance; - } -); - -export type { MongoDBInstance }; diff --git a/packages/compass-app-stores/src/provider.tsx b/packages/compass-app-stores/src/provider.tsx new file mode 100644 index 00000000000..c9bd65886d4 --- /dev/null +++ b/packages/compass-app-stores/src/provider.tsx @@ -0,0 +1,213 @@ +import { + dataServiceLocator, + useConnectionInfo, +} from '@mongodb-js/compass-connections/provider'; +import { + createServiceLocator, + createServiceProvider, +} from 'hadron-app-registry'; +import type { MongoDBInstanceProps } from 'mongodb-instance-model'; +import { MongoDBInstance } from 'mongodb-instance-model'; +import React, { + createContext, + useContext, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; +import { MongoDBInstancesManager } from './instances-manager'; +import toNS from 'mongodb-ns'; +import type Collection from 'mongodb-collection-model'; +import type Database from 'mongodb-database-model'; + +export { + MongoDBInstancesManagerEvents, + type MongoDBInstancesManager, +} from './instances-manager'; + +/** + * Exported only for testing purposes. Allows to set up a MongoDBInstanceManager + * with preconfigured MongoDBInstance + * + * @internal + */ +export class TestMongoDBInstanceManager extends MongoDBInstancesManager { + private _instance: MongoDBInstance; + constructor(instanceProps = {} as Partial) { + super(); + this._instance = new MongoDBInstance(instanceProps as MongoDBInstanceProps); + } + getMongoDBInstanceForConnection() { + return this._instance; + } + createMongoDBInstanceForConnection() { + return this._instance; + } +} + +export const MongoDBInstancesManagerContext = + createContext( + process.env.NODE_ENV === 'test' + ? (new TestMongoDBInstanceManager() as unknown as MongoDBInstancesManager) + : null + ); + +export const MongoDBInstancesManagerProvider = + MongoDBInstancesManagerContext.Provider; + +export const mongoDBInstancesManagerLocator = createServiceLocator( + function useMongoDBInstanceManager(): MongoDBInstancesManager { + const instancesManager = useContext(MongoDBInstancesManagerContext); + if (!instancesManager) { + throw new Error( + 'No MongoDBInstancesManager available in this context, provider was not setup correctly' + ); + } + return instancesManager; + }, + 'mongoDBInstancesManagerLocator' +); + +export const mongoDBInstanceLocator = createServiceLocator( + function useMongoDBInstance(): MongoDBInstance { + const connectionInfo = useConnectionInfo(); + const instancesManager = mongoDBInstancesManagerLocator(); + const instance = instancesManager.getMongoDBInstanceForConnection( + connectionInfo.id + ); + if (!instance) { + throw new Error('No MongoDBInstance available in this context'); + } + return instance; + }, + 'mongoDBInstanceLocator' +); + +const NamespaceModelContext = React.createContext( + null +); + +export const databaseModelLocator = createServiceLocator( + function useDatabaseModel(this: void) { + const model = useContext(NamespaceModelContext); + if (!model || model.modelType !== 'Database') { + const got = model ? 'Collection model' : 'undefined'; + throw new Error( + `Tried to locate Database model, but got ${got} instead. Are you trying to use databaseModelLocator outside of database namespace scope?` + ); + } + return model; + }, + 'databaseModelLocator' +); + +export const collectionModelLocator = createServiceLocator( + function useCollectionModel(this: void) { + const model = useContext(NamespaceModelContext); + if (!model || model.modelType !== 'Collection') { + const got = model ? 'Database model' : 'undefined'; + throw new Error( + `Tried to locate Collection model, but got ${got} instead. Are you trying to use collectionModelLocator outside of collection namespace scope?` + ); + } + return model; + }, + 'collectionModelLocator' +); + +/** + * + */ +export const NamespaceProvider = createServiceProvider( + function NamespaceProvider({ + children, + namespace, + onNamespaceFallbackSelect, + }: { + children?: React.ReactNode; + namespace: string; + onNamespaceFallbackSelect?(namespace: string | null): void; + }) { + const onNamespaceFallbackSelectRef = useRef(onNamespaceFallbackSelect); + onNamespaceFallbackSelectRef.current = onNamespaceFallbackSelect; + const dataService = dataServiceLocator(); + const mongoDBInstance = mongoDBInstanceLocator(); + const ns = useMemo(() => { + return toNS(namespace); + }, [namespace]); + const [namespaceModel, setNamespaceModel] = useState< + Database | Collection | null + >(() => { + const model = ns.collection + ? mongoDBInstance.databases.get(ns.database)?.collections.get(ns.ns) + : mongoDBInstance.databases.get(ns.database); + return model ?? null; + }); + + useEffect(() => { + let cancelled = false; + void (async () => { + if (namespaceModel && namespaceModel._id === ns.ns) { + return; + } + + setNamespaceModel(null); + + await mongoDBInstance.fetchDatabases({ dataService }).catch(() => { + // We don't care if it failed here, for us it's similar to not finding + // the namespace + }); + const db = mongoDBInstance.databases.get(ns.database); + + if (!db) { + onNamespaceFallbackSelectRef.current?.(null); + return; + } + + if (!ns.collection) { + if (!cancelled) { + setNamespaceModel(db); + } + return; + } + + await db.fetchCollections({ dataService }).catch(() => { + // See above + }); + const coll = db.collections.get(ns.ns); + + if (!coll) { + onNamespaceFallbackSelectRef.current?.(db._id); + return; + } + + if (!cancelled) { + setNamespaceModel(coll); + } + })(); + return () => { + cancelled = true; + }; + }, [ + dataService, + mongoDBInstance, + namespaceModel, + ns.collection, + ns.database, + ns.ns, + ]); + + if (!namespaceModel) { + return null; + } + + return ( + + {children} + + ); + } +); + +export type { MongoDBInstance, Collection, Database }; diff --git a/packages/compass-app-stores/src/stores/instance-store.spec.ts b/packages/compass-app-stores/src/stores/instance-store.spec.ts index f0aacc8ace4..7933a66054e 100644 --- a/packages/compass-app-stores/src/stores/instance-store.spec.ts +++ b/packages/compass-app-stores/src/stores/instance-store.spec.ts @@ -9,7 +9,7 @@ import { ConnectionsManager, ConnectionsManagerEvents, } from '@mongodb-js/compass-connections/provider'; -import type { ConnectionInfo } from '@mongodb-js/connection-info'; +import { type MongoDBInstancesManager } from '../instances-manager'; class FakeDataService extends EventEmitter { instanceInfo: any; @@ -51,10 +51,8 @@ describe('InstanceStore [Store]', function () { let dataService: any; let connectionsManager: ConnectionsManager; let store: ReturnType; - let instances: Record; - let connectedInstance: MongoDBInstance; + let instancesManager: MongoDBInstancesManager; - let initialInstanceRefreshedPromise: Promise; let sandbox: sinon.SinonSandbox; function waitForInstanceRefresh(instance: MongoDBInstance): Promise { @@ -89,6 +87,7 @@ describe('InstanceStore [Store]', function () { }, createActivateHelpers() ); + instancesManager = store.getState().instancesManager; }); afterEach(function () { @@ -97,7 +96,7 @@ describe('InstanceStore [Store]', function () { }); it('should not have any MongoDBInstance if no connection is established', function () { - expect(Object.keys(store.getState().instances)).to.be.of.length(0); + expect(instancesManager.listMongoDBInstances()).to.be.of.length(0); }); it('should have a MongodbInstance for each of the connected connection', function () { @@ -109,18 +108,28 @@ describe('InstanceStore [Store]', function () { ); } - expect(store.getState().instances).to.have.keys(['1', '2', '3']); + expect(instancesManager.getMongoDBInstanceForConnection('1')).to.not.be + .undefined; + expect(instancesManager.getMongoDBInstanceForConnection('2')).to.not.be + .undefined; + expect(instancesManager.getMongoDBInstanceForConnection('3')).to.not.be + .undefined; }); context('when connected', function () { + let connectedInstance: MongoDBInstance; + let initialInstanceRefreshedPromise: Promise; beforeEach(function () { connectionsManager.emit( ConnectionsManagerEvents.ConnectionAttemptSuccessful, connectedConnectionInfoId, dataService ); - instances = store.state.instances; - connectedInstance = instances[connectedConnectionInfoId]; + const instance = instancesManager.getMongoDBInstanceForConnection( + connectedConnectionInfoId + ); + expect(instance).to.not.be.undefined; + connectedInstance = instance as MongoDBInstance; initialInstanceRefreshedPromise = waitForInstanceRefresh(connectedInstance); }); @@ -131,14 +140,16 @@ describe('InstanceStore [Store]', function () { .stub(dataService, 'instance') .returns({ build: { version: '3.2.1' } }); await initialInstanceRefreshedPromise; - const instance = store.getState().instances[connectedConnectionInfoId]; + const instance = instancesManager.getMongoDBInstanceForConnection( + connectedConnectionInfoId + ); expect(instance).to.have.nested.property('build.version', '1.2.3'); globalAppRegistry.emit('refresh-data'); - await waitForInstanceRefresh(instance); + await waitForInstanceRefresh(instance as MongoDBInstance); }); it('calls instance model fetch', function () { - const instance = store.getState().instances['1']; + const instance = instancesManager.getMongoDBInstanceForConnection('1'); expect(instance).to.have.nested.property('build.version', '3.2.1'); }); }); @@ -166,8 +177,35 @@ describe('InstanceStore [Store]', function () { }); context(`on 'collection-dropped' event`, function () { - it('should remove collection from the database collections', function () { + it('should not respond when the connectionId does not matches the connectionId for the instance', function () { + // we only start with 2 collections; + expect( + connectedInstance.databases.get('foo')?.collections.length + ).to.equal(2); + + // emit the event without connectionId globalAppRegistry.emit('collection-dropped', 'foo.bar'); + + // should still be 2 + expect( + connectedInstance.databases.get('foo')?.collections.length + ).to.equal(2); + + // emit the event with a different connectionId + globalAppRegistry.emit('collection-dropped', 'foo.bar', { + connectionId: '2', + }); + + // should still be 2 + expect( + connectedInstance.databases.get('foo')?.collections.length + ).to.equal(2); + }); + + it('should remove collection from the database collections', function () { + globalAppRegistry.emit('collection-dropped', 'foo.bar', { + connectionId: '1', + }); expect( connectedInstance.databases.get('foo')?.collections.get('foo.bar') ).not.to.exist; @@ -179,21 +217,48 @@ describe('InstanceStore [Store]', function () { ?.collections.get('foo.bar', '_id'); coll?.on('change', () => {}); expect((coll as any)._events.change).to.have.lengthOf(1); - globalAppRegistry.emit('collection-dropped', 'foo.bar'); + globalAppRegistry.emit('collection-dropped', 'foo.bar', { + connectionId: '1', + }); expect((coll as any)._events).to.not.exist; }); it('should remove database if last collection was removed', function () { - globalAppRegistry.emit('collection-dropped', 'foo.bar'); - globalAppRegistry.emit('collection-dropped', 'foo.buz'); + globalAppRegistry.emit('collection-dropped', 'foo.bar', { + connectionId: '1', + }); + globalAppRegistry.emit('collection-dropped', 'foo.buz', { + connectionId: '1', + }); expect(connectedInstance.databases).to.have.lengthOf(0); expect(connectedInstance.databases.get('foo')).not.to.exist; }); }); context(`on 'database-dropped' event`, function () { - it('should remove database from instance databases', function () { + it('should not respond when the connectionId does not matches the connectionId for the instance', function () { + // we only start with 1 database; + expect(connectedInstance.databases.length).to.equal(1); + + // emit the event without connectionId globalAppRegistry.emit('database-dropped', 'foo'); + + // should still be 1 + expect(connectedInstance.databases.length).to.equal(1); + + // emit the event with a different connectionId + globalAppRegistry.emit('database-dropped', 'foo', { + connectionId: '2', + }); + + // should still be 1 + expect(connectedInstance.databases.length).to.equal(1); + }); + + it('should remove database from instance databases', function () { + globalAppRegistry.emit('database-dropped', 'foo', { + connectionId: '1', + }); expect(connectedInstance.databases).to.have.lengthOf(0); expect(connectedInstance.databases.get('foo')).not.to.exist; }); @@ -202,16 +267,69 @@ describe('InstanceStore [Store]', function () { const db = connectedInstance.databases.get('foo'); db?.on('change', () => {}); expect((db as any)._events.change).to.have.lengthOf(1); - globalAppRegistry.emit('database-dropped', 'foo'); + globalAppRegistry.emit('database-dropped', 'foo', { + connectionId: '1', + }); expect((db as any)._events).to.not.exist; }); }); - const createdEvents = [ - 'collection-created', - 'view-created', - 'agg-pipeline-out-executed', - ]; + context(`on 'view-created' event`, function () { + it('should not respond when the connectionId does not matches the connectionId for the instance', function () { + // we only start with 2 collections; + expect( + connectedInstance.databases.get('foo')?.collections.length + ).to.equal(2); + + // emit the event without connectionId + globalAppRegistry.emit('view-created', 'foo.qux'); + + // should still be 2 + expect( + connectedInstance.databases.get('foo')?.collections.length + ).to.equal(2); + + // emit the event with a different connectionId + globalAppRegistry.emit('view-created', 'foo.qux', { + connectionId: '2', + }); + + // should still be 2 + expect( + connectedInstance.databases.get('foo')?.collections.length + ).to.equal(2); + }); + + it('should add collection to the databases collections', function () { + globalAppRegistry.emit('view-created', 'foo.qux', { + connectionId: connectedConnectionInfoId, + }); + expect( + connectedInstance.databases.get('foo')?.collections + ).to.have.lengthOf(3); + expect( + connectedInstance.databases + .get('foo') + ?.collections.get('foo.qux', '_id') + ).to.exist; + }); + + it("should add new database and add collection to its collections if database doesn't exist yet", function () { + globalAppRegistry.emit('view-created', 'bar.qux', { + connectionId: connectedConnectionInfoId, + }); + expect(connectedInstance.databases).to.have.lengthOf(2); + expect(connectedInstance.databases.get('bar')).to.exist; + expect( + connectedInstance.databases.get('bar')?.collections + ).to.have.lengthOf(1); + expect( + connectedInstance.databases.get('bar')?.collections.get('bar.qux') + ).to.exist; + }); + }); + + const createdEvents = ['agg-pipeline-out-executed']; for (const evt of createdEvents) { context(`on '${evt}' event`, function () { @@ -241,12 +359,98 @@ describe('InstanceStore [Store]', function () { }); } + context(`on 'collection-created' event`, function () { + it('should not respond when the connectionId does not matches the connectionId for the instance', function () { + // we only start with 2 collections; + expect( + connectedInstance.databases.get('foo')?.collections.length + ).to.equal(2); + + // emit the event without connectionId + globalAppRegistry.emit('collection-created', 'foo.qux'); + + // should still be 2 + expect( + connectedInstance.databases.get('foo')?.collections.length + ).to.equal(2); + + // emit the event with a different connectionId + globalAppRegistry.emit('collection-created', 'foo.qux', { + connectionId: '2', + }); + + // should still be 2 + expect( + connectedInstance.databases.get('foo')?.collections.length + ).to.equal(2); + }); + + it('should add collection to the databases collections', function () { + globalAppRegistry.emit('collection-created', 'foo.qux', { + connectionId: connectedConnectionInfoId, + }); + expect( + connectedInstance.databases.get('foo')?.collections + ).to.have.lengthOf(3); + expect( + connectedInstance.databases + .get('foo') + ?.collections.get('foo.qux', '_id') + ).to.exist; + }); + + it("should add new database and add collection to its collections if database doesn't exist yet", function () { + globalAppRegistry.emit('collection-created', 'bar.qux', { + connectionId: connectedConnectionInfoId, + }); + expect(connectedInstance.databases).to.have.lengthOf(2); + expect(connectedInstance.databases.get('bar')).to.exist; + expect( + connectedInstance.databases.get('bar')?.collections + ).to.have.lengthOf(1); + expect( + connectedInstance.databases.get('bar')?.collections.get('bar.qux') + ).to.exist; + }); + }); + context(`on 'collection-renamed' event`, function () { - it('should update collection _id', function () { - globalAppRegistry.emit('collection-renamed', { - from: 'foo.bar', - to: 'foo.qux', + it('should not respond when the connectionId does not matches the connectionId for the instance', function () { + // we only start with 2 collections; + expect( + connectedInstance.databases.get('foo')?.collections.length + ).to.equal(2); + + // emit the event without connectionId + globalAppRegistry.emit('collection-renamed', 'foo.qux'); + + // should still be 2 + expect( + connectedInstance.databases.get('foo')?.collections.length + ).to.equal(2); + + // emit the event with a different connectionId + globalAppRegistry.emit('collection-renamed', 'foo.qux', { + connectionId: '2', }); + + // should still be 2 + expect( + connectedInstance.databases.get('foo')?.collections.length + ).to.equal(2); + }); + + it('should update collection _id', function () { + globalAppRegistry.emit( + 'collection-renamed', + { + from: 'foo.bar', + to: 'foo.qux', + }, + { + connectionId: connectedConnectionInfoId, + } + ); expect( connectedInstance.databases.get('foo')?.collections ).to.have.lengthOf(2); diff --git a/packages/compass-app-stores/src/stores/instance-store.ts b/packages/compass-app-stores/src/stores/instance-store.ts index af89ebf25e2..92d44bdc182 100644 --- a/packages/compass-app-stores/src/stores/instance-store.ts +++ b/packages/compass-app-stores/src/stores/instance-store.ts @@ -9,7 +9,7 @@ import { ConnectionsManagerEvents, type ConnectionsManager, } from '@mongodb-js/compass-connections/provider'; -import type { ConnectionInfo } from '@mongodb-js/connection-info'; +import { MongoDBInstancesManager } from '../instances-manager'; function serversArray( serversMap: NonNullable< @@ -52,10 +52,19 @@ export function createInstancesStore( }, { on, cleanup, addCleanup }: ActivateHelpers ) { - const instances: Record = {}; - connectionsManager.on( + const instancesManager = new MongoDBInstancesManager(); + on( + connectionsManager, + ConnectionsManagerEvents.ConnectionDisconnected, + function (connectionInfoId: string) { + instancesManager.removeMongoDBInstanceForConnection(connectionInfoId); + } + ); + + on( + connectionsManager, ConnectionsManagerEvents.ConnectionAttemptSuccessful, - (connectionInfoId: string, dataService: DataService) => { + function (instanceConnectionId: string, dataService: DataService) { async function refreshInstance( refreshOptions: Omit< Parameters[0], @@ -187,10 +196,10 @@ export function createInstancesStore( dataService.getLastSeenTopology() ), }; - const instance = new MongoDBInstance( + const instance = instancesManager.createMongoDBInstanceForConnection( + instanceConnectionId, initialInstanceProps as MongoDBInstanceProps ); - instances[connectionInfoId] = instance; addCleanup(() => { instance.removeAllListeners(); @@ -229,43 +238,69 @@ export function createInstancesStore( on(globalAppRegistry, 'refresh-data', refreshInstance); - on(globalAppRegistry, 'database-dropped', (dbName: string) => { - const db = instance.databases.remove(dbName); - if (db) { - MongoDBInstance.removeAllListeners(db); + on( + globalAppRegistry, + 'database-dropped', + (dbName: string, { connectionId }: { connectionId?: string } = {}) => { + if (connectionId !== instanceConnectionId) { + return; + } + + const db = instance.databases.remove(dbName); + if (db) { + MongoDBInstance.removeAllListeners(db); + } } - }); + ); - on(globalAppRegistry, 'collection-dropped', (namespace: string) => { - const { database } = toNS(namespace); - const db = instance.databases.get(database); - const coll = db?.collections.get(namespace, '_id'); + on( + globalAppRegistry, + 'collection-dropped', + ( + namespace: string, + { connectionId }: { connectionId?: string } = {} + ) => { + if (connectionId !== instanceConnectionId) { + return; + } - if (!db || !coll) { - return; - } + const { database } = toNS(namespace); + const db = instance.databases.get(database); + const coll = db?.collections.get(namespace, '_id'); - const isLastCollection = db.collections.length === 1; - - if (isLastCollection) { - instance.databases.remove(db); - MongoDBInstance.removeAllListeners(db); - } else { - db.collections.remove(coll); - MongoDBInstance.removeAllListeners(coll); - // Update db stats to account for db stats affected by collection stats - void db?.fetch({ dataService, force: true }).catch(() => { - // noop, we ignore stats fetching failures - }); + if (!db || !coll) { + return; + } + + const isLastCollection = db.collections.length === 1; + + if (isLastCollection) { + instance.databases.remove(db); + MongoDBInstance.removeAllListeners(db); + } else { + db.collections.remove(coll); + MongoDBInstance.removeAllListeners(coll); + // Update db stats to account for db stats affected by collection stats + void db?.fetch({ dataService, force: true }).catch(() => { + // noop, we ignore stats fetching failures + }); + } } - }); + ); on(globalAppRegistry, 'refresh-databases', refreshDatabases); on( globalAppRegistry, 'collection-renamed', - ({ from, to }: { from: string; to: string }) => { + ( + { from, to }: { from: string; to: string }, + { connectionId }: { connectionId?: string } = {} + ) => { + if (connectionId !== instanceConnectionId) { + return; + } + const { database, collection } = toNS(from); instance.databases .get(database) @@ -281,10 +316,22 @@ export function createInstancesStore( on( globalAppRegistry, 'collection-created', - maybeAddAndRefreshCollectionModel + (ns: string, { connectionId }: { connectionId?: string } = {}) => { + if (connectionId === instanceConnectionId) { + void maybeAddAndRefreshCollectionModel(ns); + } + } ); - on(globalAppRegistry, 'view-created', maybeAddAndRefreshCollectionModel); + on( + globalAppRegistry, + 'view-created', + (ns: string, { connectionId }: { connectionId?: string } = {}) => { + if (connectionId === instanceConnectionId) { + void maybeAddAndRefreshCollectionModel(ns); + } + } + ); on( globalAppRegistry, @@ -310,9 +357,9 @@ export function createInstancesStore( ); return { - state: { instances }, // Using LegacyRefluxProvider here to pass state to provider + state: { instancesManager }, // Using LegacyRefluxProvider here to pass state to provider getState() { - return { instances }; + return { instancesManager }; }, deactivate: cleanup, }; diff --git a/packages/compass-collection/.eslintrc.js b/packages/compass-collection/.eslintrc.js index f06f8fc6013..b54d314ab18 100644 --- a/packages/compass-collection/.eslintrc.js +++ b/packages/compass-collection/.eslintrc.js @@ -1,3 +1,4 @@ +'use strict'; module.exports = { root: true, extends: ['@mongodb-js/eslint-config-compass/plugin'], diff --git a/packages/compass-collection/.mocharc.js b/packages/compass-collection/.mocharc.js index a7e53abc444..61091edba73 100644 --- a/packages/compass-collection/.mocharc.js +++ b/packages/compass-collection/.mocharc.js @@ -1 +1,2 @@ +'use strict'; module.exports = require('@mongodb-js/mocha-config-compass/compass-plugin'); diff --git a/packages/compass-collection/package.json b/packages/compass-collection/package.json index 3738e3b301c..a50dd5537f4 100644 --- a/packages/compass-collection/package.json +++ b/packages/compass-collection/package.json @@ -11,7 +11,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "4.23.1", + "version": "4.25.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -48,15 +48,15 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "dependencies": { - "@mongodb-js/compass-app-stores": "^7.10.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-workspaces": "^0.5.1", - "compass-preferences-model": "^2.18.1", - "hadron-app-registry": "^9.1.8", - "mongodb-collection-model": "^5.18.1", - "mongodb-instance-model": "^12.18.1", + "@mongodb-js/compass-app-stores": "^7.12.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/connection-info": "^0.2.1", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-workspaces": "^0.7.0", + "compass-preferences-model": "^2.20.0", + "hadron-app-registry": "^9.1.10", + "mongodb-collection-model": "^5.19.1", "mongodb-ns": "^2.4.0", "numeral": "^2.0.6", "react": "^17.0.2", @@ -65,10 +65,10 @@ "redux-thunk": "^2.4.2" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "@types/chai": "^4.2.21", diff --git a/packages/compass-collection/src/components/collection-header-actions/collection-header-actions.spec.tsx b/packages/compass-collection/src/components/collection-header-actions/collection-header-actions.spec.tsx index 25fcbfdc61f..b5b6751de98 100644 --- a/packages/compass-collection/src/components/collection-header-actions/collection-header-actions.spec.tsx +++ b/packages/compass-collection/src/components/collection-header-actions/collection-header-actions.spec.tsx @@ -83,6 +83,7 @@ describe('CollectionHeaderActions [Component]', function () { ); button.click(); expect(openEditViewWorkspaceStub).to.have.been.calledOnceWith( + 'TEST', 'db.coll2', { sourceName: 'db.someSource', @@ -122,6 +123,7 @@ describe('CollectionHeaderActions [Component]', function () { ); button.click(); expect(openCollectionWorkspaceStub).to.have.been.calledOnceWith( + 'TEST', 'db.editing' ); }); diff --git a/packages/compass-collection/src/components/collection-header-actions/collection-header-actions.tsx b/packages/compass-collection/src/components/collection-header-actions/collection-header-actions.tsx index f6ab15efe72..8c03532ced2 100644 --- a/packages/compass-collection/src/components/collection-header-actions/collection-header-actions.tsx +++ b/packages/compass-collection/src/components/collection-header-actions/collection-header-actions.tsx @@ -5,6 +5,7 @@ import { css, spacing, } from '@mongodb-js/compass-components'; +import { useConnectionInfo } from '@mongodb-js/compass-connections/provider'; import { useOpenWorkspace } from '@mongodb-js/compass-workspaces/provider'; import React from 'react'; @@ -32,6 +33,7 @@ const CollectionHeaderActions: React.FunctionComponent< sourceName, sourcePipeline, }: CollectionHeaderActionsProps) => { + const { id: connectionId } = useConnectionInfo(); const { openCollectionWorkspace, openEditViewWorkspace } = useOpenWorkspace(); return (
{ if (sourceName && sourcePipeline) { - openEditViewWorkspace(namespace, { + openEditViewWorkspace(connectionId, namespace, { sourceName, sourcePipeline, }); @@ -61,7 +63,7 @@ const CollectionHeaderActions: React.FunctionComponent< size={ButtonSize.Small} onClick={() => { if (editViewName) { - openCollectionWorkspace(editViewName); + openCollectionWorkspace(connectionId, editViewName); } }} > diff --git a/packages/compass-collection/src/components/collection-header/collection-header.spec.tsx b/packages/compass-collection/src/components/collection-header/collection-header.spec.tsx index d6c9601ab90..e95c6cece94 100644 --- a/packages/compass-collection/src/components/collection-header/collection-header.spec.tsx +++ b/packages/compass-collection/src/components/collection-header/collection-header.spec.tsx @@ -188,7 +188,7 @@ describe('CollectionHeader [Component]', function () { crumbs.push(item.textContent); }); expect(crumbs.filter(Boolean).join('.').toLowerCase()).to.equal( - items.join('.').toLowerCase() + `localhost:27020.${items.join('.').toLowerCase()}` ); } @@ -247,9 +247,10 @@ describe('CollectionHeader [Component]', function () { expect(openCollectionsWorkspaceStub.called).to.be.false; userEvent.click(item); expect(openCollectionsWorkspaceStub.calledOnce).to.be.true; - expect(openCollectionsWorkspaceStub.firstCall.firstArg).to.deep.equal( - 'db' - ); + expect(openCollectionsWorkspaceStub.firstCall.args).to.deep.equal([ + 'TEST', + 'db', + ]); }); it('for a view, opens source collection', function () { @@ -268,9 +269,10 @@ describe('CollectionHeader [Component]', function () { expect(openCollectionWorkspaceStub.called).to.be.false; userEvent.click(item); expect(openCollectionWorkspaceStub.calledOnce).to.be.true; - expect(openCollectionWorkspaceStub.firstCall.firstArg).to.deep.equal( - 'db.coll2' - ); + expect(openCollectionWorkspaceStub.firstCall.args).to.deep.equal([ + 'TEST', + 'db.coll2', + ]); }); }); }); diff --git a/packages/compass-collection/src/components/collection-header/collection-header.tsx b/packages/compass-collection/src/components/collection-header/collection-header.tsx index ea521cb7f05..80a79eb299f 100644 --- a/packages/compass-collection/src/components/collection-header/collection-header.tsx +++ b/packages/compass-collection/src/components/collection-header/collection-header.tsx @@ -18,6 +18,8 @@ import type { CollectionState } from '../../modules/collection-tab'; import { CollectionBadge } from './badges'; import { useOpenWorkspace } from '@mongodb-js/compass-workspaces/provider'; import { connect } from 'react-redux'; +import { useConnectionInfo } from '@mongodb-js/compass-connections/provider'; +import { getConnectionTitle } from '@mongodb-js/connection-info'; const collectionHeaderStyles = css({ padding: spacing[3], @@ -97,38 +99,51 @@ export const CollectionHeader: React.FunctionComponent< }) => { const darkMode = useDarkMode(); const showInsights = usePreference('showInsights'); - const { openCollectionWorkspace, openCollectionsWorkspace } = - useOpenWorkspace(); + const { + openCollectionWorkspace, + openCollectionsWorkspace, + openDatabasesWorkspace, + } = useOpenWorkspace(); + const connectionInfo = useConnectionInfo(); + const connectionId = connectionInfo.id; + const connectionName = getConnectionTitle(connectionInfo); const breadcrumbItems = useMemo(() => { return [ - // TODO (COMPASS-7684): add connection name + { + name: connectionName, + onClick: () => openDatabasesWorkspace(connectionId), + }, { name: toNS(namespace).database, - onClick: () => openCollectionsWorkspace(toNS(namespace).database), + onClick: () => + openCollectionsWorkspace(connectionId, toNS(namespace).database), }, // When viewing a view, show the source namespace first sourceName && { name: toNS(sourceName).collection, - onClick: () => openCollectionWorkspace(sourceName), + onClick: () => openCollectionWorkspace(connectionId, sourceName), }, // Show the current namespace { name: toNS(namespace).collection, - onClick: () => openCollectionWorkspace(namespace), + onClick: () => openCollectionWorkspace(connectionId, namespace), }, // When editing a view, show the view namespace last editViewName && { name: toNS(editViewName).collection, - onClick: () => openCollectionWorkspace(editViewName), + onClick: () => openCollectionWorkspace(connectionId, editViewName), }, ].filter(Boolean) as BreadcrumbItem[]; }, [ + connectionId, + connectionName, namespace, sourceName, editViewName, openCollectionsWorkspace, openCollectionWorkspace, + openDatabasesWorkspace, ]); const insights = diff --git a/packages/compass-collection/src/index.ts b/packages/compass-collection/src/index.ts index 8a0e9a8d336..966639c078c 100644 --- a/packages/compass-collection/src/index.ts +++ b/packages/compass-collection/src/index.ts @@ -6,7 +6,7 @@ import { type DataServiceLocator, type DataService, } from '@mongodb-js/compass-connections/provider'; -import { mongoDBInstanceLocator } from '@mongodb-js/compass-app-stores/provider'; +import { collectionModelLocator } from '@mongodb-js/compass-app-stores/provider'; import type { WorkspaceComponent } from '@mongodb-js/compass-workspaces'; import { workspacesServiceLocator } from '@mongodb-js/compass-workspaces/provider'; @@ -18,7 +18,7 @@ export const CollectionTabPlugin = registerHadronPlugin( }, { dataService: dataServiceLocator as DataServiceLocator, - instance: mongoDBInstanceLocator, + collection: collectionModelLocator, workspaces: workspacesServiceLocator, } ); diff --git a/packages/compass-collection/src/stores/collection-tab.spec.ts b/packages/compass-collection/src/stores/collection-tab.spec.ts index 2b3a8cf77f5..1c4af935309 100644 --- a/packages/compass-collection/src/stores/collection-tab.spec.ts +++ b/packages/compass-collection/src/stores/collection-tab.spec.ts @@ -37,22 +37,6 @@ describe('Collection Tab Content store', function () { const localAppRegistry = sandbox.spy(new AppRegistry()); const dataService = {} as any; - const instance = { - databases: { - get() { - return { - collections: { - get() { - return mockCollection; - }, - }, - }; - }, - }, - dataLake: {}, - build: {}, - removeListener() {}, - } as any; let store: ReturnType['store']; let deactivate: ReturnType['deactivate']; @@ -68,7 +52,7 @@ describe('Collection Tab Content store', function () { { dataService, localAppRegistry, - instance, + collection: mockCollection as any, workspaces: workspaces as any, }, { on() {}, cleanup() {} } as any diff --git a/packages/compass-collection/src/stores/collection-tab.ts b/packages/compass-collection/src/stores/collection-tab.ts index 51242de3815..3d623709437 100644 --- a/packages/compass-collection/src/stores/collection-tab.ts +++ b/packages/compass-collection/src/stores/collection-tab.ts @@ -8,9 +8,7 @@ import reducer, { pickCollectionStats, selectTab, } from '../modules/collection-tab'; -import type Collection from 'mongodb-collection-model'; -import toNs from 'mongodb-ns'; -import type { MongoDBInstance } from 'mongodb-instance-model'; +import type { Collection } from '@mongodb-js/compass-app-stores/provider'; import type { ActivateHelpers } from 'hadron-app-registry'; import type { workspacesServiceLocator } from '@mongodb-js/compass-workspaces/provider'; @@ -32,7 +30,7 @@ export type CollectionTabOptions = { export type CollectionTabServices = { dataService: DataService; - instance: MongoDBInstance; + collection: Collection; localAppRegistry: AppRegistry; workspaces: ReturnType; }; @@ -42,13 +40,12 @@ export function activatePlugin( services: CollectionTabServices, { on, cleanup }: ActivateHelpers ) { - const { dataService, instance, localAppRegistry, workspaces } = services; - - const { database, collection } = toNs(namespace); - - const collectionModel = instance.databases - .get(database) - ?.collections.get(collection, 'name'); + const { + dataService, + collection: collectionModel, + localAppRegistry, + workspaces, + } = services; if (!collectionModel) { throw new Error( diff --git a/packages/compass-components/.eslintrc.js b/packages/compass-components/.eslintrc.js index e4cf824b6ac..9c3ab95632f 100644 --- a/packages/compass-components/.eslintrc.js +++ b/packages/compass-components/.eslintrc.js @@ -1,3 +1,4 @@ +'use strict'; module.exports = { root: true, extends: ['@mongodb-js/eslint-config-compass'], diff --git a/packages/compass-components/.mocharc.js b/packages/compass-components/.mocharc.js index 30aecfb78c3..5a33f216327 100644 --- a/packages/compass-components/.mocharc.js +++ b/packages/compass-components/.mocharc.js @@ -1 +1,2 @@ +'use strict'; module.exports = require('@mongodb-js/mocha-config-compass/react'); diff --git a/packages/compass-components/package.json b/packages/compass-components/package.json index b2007a182b9..88b55d84a85 100644 --- a/packages/compass-components/package.json +++ b/packages/compass-components/package.json @@ -1,6 +1,6 @@ { "name": "@mongodb-js/compass-components", - "version": "1.22.1", + "version": "1.24.0", "description": "React Components used in Compass", "license": "SSPL", "main": "lib/index.js", @@ -80,8 +80,8 @@ "@react-stately/tooltip": "^3.0.5", "bson": "^6.6.0", "focus-trap-react": "^9.0.2", - "hadron-document": "^8.4.9", - "hadron-type-checker": "^7.1.2", + "hadron-document": "^8.5.1", + "hadron-type-checker": "^7.2.1", "is-electron-renderer": "^2.0.1", "lodash": "^4.17.21", "polished": "^4.2.2", @@ -93,10 +93,10 @@ }, "devDependencies": { "@emotion/css": "^11.11.2", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/dom": "^8.11.1", "@testing-library/react": "^12.1.4", "@testing-library/react-hooks": "^7.0.2", diff --git a/packages/compass-sidebar/src/components/multiple-connections/icons/server-icon.tsx b/packages/compass-components/src/components/icons/server-icon.tsx similarity index 90% rename from packages/compass-sidebar/src/components/multiple-connections/icons/server-icon.tsx rename to packages/compass-components/src/components/icons/server-icon.tsx index bb48db8fdd7..3d140135fa2 100644 --- a/packages/compass-sidebar/src/components/multiple-connections/icons/server-icon.tsx +++ b/packages/compass-components/src/components/icons/server-icon.tsx @@ -1,5 +1,7 @@ import React from 'react'; -import { css, palette, useDarkMode } from '@mongodb-js/compass-components'; +import { useDarkMode } from '../../hooks/use-theme'; +import { palette } from '@leafygreen-ui/palette'; +import { css } from '@leafygreen-ui/emotion'; const iconStyles = css({ flex: 'none', @@ -56,4 +58,4 @@ const ServerIcon = () => { ); }; -export default ServerIcon; +export { ServerIcon }; diff --git a/packages/compass-components/src/components/placeholder.tsx b/packages/compass-components/src/components/placeholder.tsx index db4448b321f..56172ed2c8c 100644 --- a/packages/compass-components/src/components/placeholder.tsx +++ b/packages/compass-components/src/components/placeholder.tsx @@ -21,8 +21,6 @@ const move = keyframes({ }); const placeholder = css({ - '--gradient-start': palette.gray.light2, - '--gradient-end': palette.gray.light3, alignSelf: 'center', borderRadius: 3, maxWidth: '80%', @@ -39,11 +37,6 @@ const placeholder = css({ animation: `${move} ${scale}s infinite linear`, }); -const placeholderDarkMode = css({ - '--gradient-start': palette.gray.dark2, - '--gradient-end': palette.gray.dark4, -}); - function getBoundRandom(min: number, max: number) { return Math.random() * (max - min) + min; } @@ -57,6 +50,9 @@ const Placeholder: React.FunctionComponent< maxChar?: number; width?: CSSProperties['width']; height?: CSSProperties['height']; + gradientStart?: CSSProperties['color']; + gradientEnd?: CSSProperties['color']; + style?: CSSProperties; 'data-testid'?: string; } > = ({ @@ -65,6 +61,9 @@ const Placeholder: React.FunctionComponent< maxChar = 15, width: propsWidth, height: propsHeight = spacing[3], + gradientStart, + gradientEnd, + style: propsStyle, ...props }) => { const darkMode = useDarkMode(); @@ -73,13 +72,26 @@ const Placeholder: React.FunctionComponent< return propsWidth || `${Math.round(getBoundRandom(minChar, maxChar))}ch`; }, [minChar, maxChar, propsWidth]); + const style = useMemo( + () => ({ + width, + height: propsHeight, + '--gradient-start': + gradientStart || (darkMode ? palette.gray.dark2 : palette.gray.light2), + '--gradient-end': + gradientEnd || (darkMode ? palette.gray.dark4 : palette.gray.light3), + ...propsStyle, + }), + [darkMode, width, propsHeight, gradientStart, gradientEnd, propsStyle] + ); + return (
); }; diff --git a/packages/compass-components/src/components/resizeable-sidebar.tsx b/packages/compass-components/src/components/resizeable-sidebar.tsx index d34bc704ea1..d4df95e9e92 100644 --- a/packages/compass-components/src/components/resizeable-sidebar.tsx +++ b/packages/compass-components/src/components/resizeable-sidebar.tsx @@ -63,14 +63,24 @@ const ResizableSidebar = ({ children, className, style, + useNewTheme, ...props }: { initialWidth?: number; minWidth?: number; children: JSX.Element; + useNewTheme?: boolean; } & React.HTMLProps): JSX.Element => { const darkMode = useDarkMode(); const [width, setWidth] = useState(initialWidth); + const newThemeStyles = useNewTheme + ? { + '--item-color-active': darkMode ? palette.white : palette.gray.dark3, + '--item-bg-color-active': darkMode + ? palette.gray.dark2 + : palette.gray.light2, + } + : {}; const getMaxSidebarWidth = useCallback(() => { return Math.max(minWidth, 600); @@ -101,6 +111,7 @@ const ResizableSidebar = ({ minWidth, width: renderedWidth, flex: 'none', + ...newThemeStyles, }} {...props} > diff --git a/packages/compass-components/src/components/workspace-tabs/tab.tsx b/packages/compass-components/src/components/workspace-tabs/tab.tsx index 3eb5c9ffa2b..fb08a9e0c47 100644 --- a/packages/compass-components/src/components/workspace-tabs/tab.tsx +++ b/packages/compass-components/src/components/workspace-tabs/tab.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useMemo } from 'react'; import { css, cx } from '@leafygreen-ui/emotion'; import { palette } from '@leafygreen-ui/palette'; import { spacing } from '@leafygreen-ui/tokens'; @@ -60,6 +60,18 @@ const animatedSubtitleStyles = css({ }, }); +export type TabTheme = { + '--workspace-tab-background-color': string; + '--workspace-tab-selected-background-color': string; + '--workspace-tab-border-color': string; + '--workspace-tab-color': string; + '--workspace-tab-selected-color': string; + '&:focus-visible': { + '--workspace-tab-selected-color': string; + '--workspace-tab-border-color': string; + }; +}; + const tabLightThemeStyles = css({ '--workspace-tab-background-color': palette.gray.light3, '--workspace-tab-selected-background-color': palette.white, @@ -164,6 +176,7 @@ type TabProps = { iconGlyph: IconGlyph; tabContentId: string; subtitle?: string; + tabTheme?: TabTheme; }; function Tab({ @@ -175,6 +188,7 @@ function Tab({ tabContentId, iconGlyph, subtitle, + tabTheme, ...props }: TabProps & React.HTMLProps) { const darkMode = useDarkMode(); @@ -189,6 +203,14 @@ function Tab({ props ); + const themeClass = useMemo(() => { + if (!tabTheme) { + return darkMode ? tabDarkThemeStyles : tabLightThemeStyles; + } + + return css(tabTheme); + }, [tabTheme, darkMode]); + const style = { transform: cssDndKit.Transform.toString(transform), transition, @@ -204,7 +226,7 @@ function Tab({ style={style} className={cx( tabStyles, - darkMode ? tabDarkThemeStyles : tabLightThemeStyles, + themeClass, isSelected && selectedTabStyles, isDragging && draggingTabStyles, subtitle && animatedSubtitleStyles diff --git a/packages/compass-components/src/components/workspace-tabs/workspace-tabs.tsx b/packages/compass-components/src/components/workspace-tabs/workspace-tabs.tsx index aadf5b2e268..113d4f646a4 100644 --- a/packages/compass-components/src/components/workspace-tabs/workspace-tabs.tsx +++ b/packages/compass-components/src/components/workspace-tabs/workspace-tabs.tsx @@ -172,6 +172,7 @@ export type TabProps = { id: string; title: string; subtitle?: string; + connectionId?: string; iconGlyph: Extract; } & Omit, 'id' | 'title' | 'subtitle'>; diff --git a/packages/compass-components/src/hooks/use-confirmation.spec.tsx b/packages/compass-components/src/hooks/use-confirmation.spec.tsx index 107ea9d6b58..3144e310255 100644 --- a/packages/compass-components/src/hooks/use-confirmation.spec.tsx +++ b/packages/compass-components/src/hooks/use-confirmation.spec.tsx @@ -1,12 +1,12 @@ import { cleanup, - fireEvent, render, screen, waitFor, waitForElementToBeRemoved, within, } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { expect } from 'chai'; import React from 'react'; @@ -46,7 +46,7 @@ describe('use-confirmation', function () { ); - fireEvent.click(screen.getByText('Open Modal')); + userEvent.click(screen.getByText('Open Modal')); modal = screen.getByTestId('confirmation-modal'); expect(modal).to.exist; }); @@ -61,14 +61,14 @@ describe('use-confirmation', function () { }); it('handles cancel action', async function () { - fireEvent.click(within(modal).getByText('Cancel')); + userEvent.click(within(modal).getByText('Cancel')); await waitForElementToBeRemoved(() => screen.getByTestId('confirmation-modal') ); }); it('handles confirm action', async function () { - fireEvent.click(within(modal).getByText('Yes')); + userEvent.click(within(modal).getByText('Yes')); await waitForElementToBeRemoved(() => screen.getByTestId('confirmation-modal') ); @@ -104,15 +104,49 @@ describe('use-confirmation', function () { }); it('handles cancel action', async function () { - fireEvent.click(within(modal).getByText('Cancel')); + userEvent.click(within(modal).getByText('Cancel')); const confirmed = await response; expect(confirmed).to.be.false; }); it('handles confirm action', async function () { - fireEvent.click(within(modal).getByText('Yes')); + userEvent.click(within(modal).getByText('Yes')); const confirmed = await response; expect(confirmed).to.be.true; }); }); + + context( + 'when asking for confirmation multiple times with the same required input', + function () { + it('should always require to enter a confirmation input before confirming', async function () { + render(); + + // Run the confirmation flow with the same required input multiple times + // to make sure that buttons are initially disabled + for (let i = 0; i < 3; i++) { + const response = showConfirmation({ + requiredInputText: 'Yes', + }); + + expect( + screen.getByRole('button', { name: 'Confirm' }) + ).to.have.attribute('aria-disabled', 'true'); + + userEvent.type( + screen.getByRole('textbox', { name: /Type "Yes"/ }), + 'Yes' + ); + + expect( + screen.getByRole('button', { name: 'Confirm' }) + ).to.have.attribute('aria-disabled', 'false'); + + userEvent.click(screen.getByRole('button', { name: 'Confirm' })); + + expect(await response).to.eq(true); + } + }); + } + ); }); diff --git a/packages/compass-components/src/hooks/use-confirmation.tsx b/packages/compass-components/src/hooks/use-confirmation.tsx index bf9dc63e9df..74865ddf7b7 100644 --- a/packages/compass-components/src/hooks/use-confirmation.tsx +++ b/packages/compass-components/src/hooks/use-confirmation.tsx @@ -25,7 +25,7 @@ interface ConfirmationModalContextData { } type ShowConfirmationEventDetail = { - props: ConfirmationProperties; + props: ConfirmationProperties & { confirmationId: number }; resolve: ConfirmationCallback; reject: (err?: any) => void; }; @@ -53,18 +53,25 @@ interface GlobalConfirmation extends EventTarget { ): void; } +let confirmationId = 0; + class GlobalConfirmation extends EventTarget { showConfirmation(props: ConfirmationProperties) { return new Promise((resolve, reject) => { this.dispatchEvent( - new CustomEvent('show-confirmation', { - detail: { props, resolve, reject }, + new CustomEvent('show-confirmation', { + detail: { + props: { ...props, confirmationId: ++confirmationId }, + resolve, + reject, + }, }) ); }); } } const globalConfirmation = new GlobalConfirmation(); + export const showConfirmation = globalConfirmation.showConfirmation.bind(globalConfirmation); @@ -74,9 +81,9 @@ const ConfirmationModalContext = showConfirmation, }); -type ConfirmationModalAreaProps = Partial & { - open: boolean; -}; +type ConfirmationModalAreaProps = Partial< + ShowConfirmationEventDetail['props'] +> & { open: boolean }; export const ConfirmationModalArea: React.FC = ({ children }) => { const hasParentContext = useContext(ConfirmationModalContext).isMounted; @@ -84,6 +91,7 @@ export const ConfirmationModalArea: React.FC = ({ children }) => { const [confirmationProps, setConfirmationProps] = useState({ open: false, + confirmationId: -1, }); const callbackRef = useRef(); @@ -142,6 +150,11 @@ export const ConfirmationModalArea: React.FC = ({ children }) => { {children} { + onClose: (e: React.EventHandler) => { this.closeToast(id); + props.onClose?.(e); }, }; this.toasts.set(id, toastProps); diff --git a/packages/compass-components/src/index.ts b/packages/compass-components/src/index.ts index d11fde754e9..1280ca68cce 100644 --- a/packages/compass-components/src/index.ts +++ b/packages/compass-components/src/index.ts @@ -37,6 +37,7 @@ export { import { ResizeHandle, ResizeDirection } from './components/resize-handle'; import { Accordion } from './components/accordion'; import { CollapsibleFieldSet } from './components/collapsible-field-set'; +export { type TabTheme } from './components/workspace-tabs/tab'; import { WorkspaceTabs } from './components/workspace-tabs/workspace-tabs'; import ResizableSidebar, { defaultSidebarWidth, @@ -54,6 +55,7 @@ import { } from './components/item-action-controls'; export { DocumentIcon } from './components/icons/document-icon'; export { FavoriteIcon } from './components/icons/favorite-icon'; +export { ServerIcon } from './components/icons/server-icon'; export { NoSavedItemsIcon } from './components/icons/no-saved-items-icon'; export { GuideCue as LGGuideCue } from '@leafygreen-ui/guide-cue'; export { Variant as BadgeVariant } from '@leafygreen-ui/badge'; diff --git a/packages/compass-connection-import-export/.eslintrc.js b/packages/compass-connection-import-export/.eslintrc.js index f06f8fc6013..b54d314ab18 100644 --- a/packages/compass-connection-import-export/.eslintrc.js +++ b/packages/compass-connection-import-export/.eslintrc.js @@ -1,3 +1,4 @@ +'use strict'; module.exports = { root: true, extends: ['@mongodb-js/eslint-config-compass/plugin'], diff --git a/packages/compass-connection-import-export/.mocharc.js b/packages/compass-connection-import-export/.mocharc.js index 30aecfb78c3..5a33f216327 100644 --- a/packages/compass-connection-import-export/.mocharc.js +++ b/packages/compass-connection-import-export/.mocharc.js @@ -1 +1,2 @@ +'use strict'; module.exports = require('@mongodb-js/mocha-config-compass/react'); diff --git a/packages/compass-connection-import-export/package.json b/packages/compass-connection-import-export/package.json index eaf0491b14e..5c98b75b209 100644 --- a/packages/compass-connection-import-export/package.json +++ b/packages/compass-connection-import-export/package.json @@ -14,7 +14,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "0.21.1", + "version": "0.23.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -51,17 +51,18 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "dependencies": { - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/connection-storage": "^0.8.1", - "compass-preferences-model": "^2.18.1", - "hadron-ipc": "^3.2.12", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/connection-storage": "^0.10.0", + "compass-preferences-model": "^2.20.0", + "hadron-ipc": "^3.2.14", "react": "^17.0.2" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/react-hooks": "^7.0.2", "@types/chai": "^4.2.21", diff --git a/packages/compass-connection-import-export/src/components/export-modal.tsx b/packages/compass-connection-import-export/src/components/export-modal.tsx index 192bf6a4343..d0a323022cc 100644 --- a/packages/compass-connection-import-export/src/components/export-modal.tsx +++ b/packages/compass-connection-import-export/src/components/export-modal.tsx @@ -13,10 +13,6 @@ import { SelectTable } from './select-table'; import type { ImportExportResult } from '../hooks/common'; import { useOpenModalThroughIpc } from '../hooks/common'; import { useExportConnections } from '../hooks/use-export'; -import type { - ConnectionInfo, - ConnectionStorage, -} from '@mongodb-js/connection-storage/renderer'; import { usePreference } from 'compass-preferences-model/provider'; const TOAST_TIMEOUT_MS = 5000; @@ -30,17 +26,13 @@ const selectTableColumns = [['name', 'Connection Name']] as const; export function ExportConnectionsModal({ open, setOpen, - favoriteConnections, afterExport, trackingProps, - connectionStorage, }: { open: boolean; setOpen: (newOpen: boolean) => void; - favoriteConnections: Pick[]; afterExport?: () => void; trackingProps?: Record; - connectionStorage?: typeof ConnectionStorage; }): React.ReactElement { const { openToast } = useToast('compass-connection-import-export'); const finish = useCallback( @@ -77,15 +69,11 @@ export function ExportConnectionsModal({ removeSecrets, passphrase, }, - } = useExportConnections( - { - finish, - open, - favoriteConnections, - trackingProps, - }, - connectionStorage?.exportConnections.bind(connectionStorage) - ); + } = useExportConnections({ + finish, + open, + trackingProps, + }); return ( void; - favoriteConnections: Pick[]; trackingProps?: Record; - connectionStorage?: typeof ConnectionStorage; }): React.ReactElement { const { openToast } = useToast('compass-connection-import-export'); const finish = useCallback( @@ -77,16 +69,11 @@ export function ImportConnectionsModal({ filename, passphrase, }, - } = useImportConnections( - { - finish, - open, - favoriteConnections, - trackingProps, - }, - connectionStorage?.importConnections.bind(connectionStorage), - connectionStorage?.deserializeConnections.bind(connectionStorage) - ); + } = useImportConnections({ + finish, + open, + trackingProps, + }); const [displayConnectionList, hasSelectedDuplicates] = useMemo(() => { return [ diff --git a/packages/compass-connection-import-export/src/hooks/use-export.spec.ts b/packages/compass-connection-import-export/src/hooks/use-export.spec.ts index 388f35e3b38..4e7439c3090 100644 --- a/packages/compass-connection-import-export/src/hooks/use-export.spec.ts +++ b/packages/compass-connection-import-export/src/hooks/use-export.spec.ts @@ -1,3 +1,4 @@ +import type React from 'react'; import { expect } from 'chai'; import sinon from 'sinon'; import type { @@ -13,42 +14,64 @@ import { promises as fs } from 'fs'; import { PreferencesProvider } from 'compass-preferences-model/provider'; import { createSandboxFromDefaultPreferences } from 'compass-preferences-model'; import { createElement } from 'react'; +import { + ConnectionStorageProvider, + type ConnectionStorage, + InMemoryConnectionStorage, +} from '@mongodb-js/connection-storage/provider'; +import { useConnectionRepository } from '@mongodb-js/compass-connections/provider'; type UseExportConnectionsProps = Parameters[0]; type UseExportConnectionsResult = ReturnType; +type UseConnectionRepositoryResult = ReturnType; +type HookResults = { + connectionRepository: UseConnectionRepositoryResult; + exportConnections: UseExportConnectionsResult; +}; describe('useExportConnections', function () { let sandbox: sinon.SinonSandbox; let finish: sinon.SinonStub; let finishedPromise: Promise; - let exportConnections: sinon.SinonStub; let defaultProps: UseExportConnectionsProps; let renderHookResult: RenderHookResult< Partial, - UseExportConnectionsResult + HookResults >; - let result: RenderResult; + let result: RenderResult; let rerender: (props: Partial) => void; let tmpdir: string; + let connectionStorage: ConnectionStorage; beforeEach(async function () { sandbox = sinon.createSandbox(); finishedPromise = new Promise((resolve) => { finish = sinon.stub().callsFake(resolve); }); - exportConnections = sinon.stub(); defaultProps = { finish, - favoriteConnections: [], open: true, trackingProps: { context: 'Tests' }, }; + connectionStorage = new InMemoryConnectionStorage(); + const wrapper: React.FC = ({ children }) => + createElement(ConnectionStorageProvider, { + value: connectionStorage, + children, + }); + renderHookResult = renderHook( (props: Partial = {}) => { - return useExportConnections( - { ...defaultProps, ...props }, - exportConnections - ); + return { + connectionRepository: useConnectionRepository(), + exportConnections: useExportConnections({ + ...defaultProps, + ...props, + }), + }; + }, + { + wrapper, } ); ({ result, rerender } = renderHookResult); @@ -67,19 +90,21 @@ describe('useExportConnections', function () { }); it('sets removeSecrets if protectConnectionStrings is set', async function () { - expect(result.current.state.removeSecrets).to.equal(false); + expect(result.current.exportConnections.state.removeSecrets).to.equal( + false + ); act(() => { - result.current.onChangeRemoveSecrets({ + result.current.exportConnections.onChangeRemoveSecrets({ target: { checked: true }, } as any); }); - expect(result.current.state.removeSecrets).to.equal(true); + expect(result.current.exportConnections.state.removeSecrets).to.equal(true); const preferences = await createSandboxFromDefaultPreferences(); await preferences.savePreferences({ protectConnectionStrings: true }); const resultInProtectedMode = renderHook( () => { - return useExportConnections(defaultProps, exportConnections); + return useExportConnections(defaultProps); }, { wrapper: ({ children }) => @@ -96,54 +121,93 @@ describe('useExportConnections', function () { expect(resultInProtectedMode.current.state.removeSecrets).to.equal(true); }); - it('responds to changes in the connectionList specified via props', function () { - expect(result.current.state.connectionList).to.deep.equal([]); + it('responds to changes in the connectionList', async function () { + expect(result.current.exportConnections.state.connectionList).to.deep.equal( + [] + ); - rerender({ - favoriteConnections: [{ id: 'id1', favorite: { name: 'name1' } }], + await act(async () => { + await result.current.connectionRepository.saveConnection({ + id: 'id1', + connectionOptions: { connectionString: 'mongodb://localhost:2020' }, + favorite: { + name: 'name1', + }, + savedConnectionType: 'favorite', + }); }); - expect(result.current.state.connectionList).to.deep.equal([ - { id: 'id1', name: 'name1', selected: true }, - ]); + rerender({}); + expect(result.current.exportConnections.state.connectionList).to.deep.equal( + [{ id: 'id1', name: 'name1', selected: true }] + ); act(() => { - result.current.onChangeConnectionList([ + result.current.exportConnections.onChangeConnectionList([ { id: 'id1', name: 'name1', selected: false }, ]); }); - expect(result.current.state.connectionList).to.deep.equal([ - { id: 'id1', name: 'name1', selected: false }, - ]); - - rerender({ - favoriteConnections: [ - { id: 'id1', favorite: { name: 'name1' } }, - { id: 'id2', favorite: { name: 'name2' } }, - ], + expect(result.current.exportConnections.state.connectionList).to.deep.equal( + [{ id: 'id1', name: 'name1', selected: false }] + ); + + await act(async () => { + await result.current.connectionRepository.saveConnection({ + id: 'id2', + connectionOptions: { connectionString: 'mongodb://localhost:2020' }, + favorite: { + name: 'name2', + }, + savedConnectionType: 'favorite', + }); }); - expect(result.current.state.connectionList).to.deep.equal([ - { id: 'id1', name: 'name1', selected: false }, - { id: 'id2', name: 'name2', selected: true }, - ]); + expect(result.current.exportConnections.state.connectionList).to.deep.equal( + [ + { id: 'id1', name: 'name1', selected: false }, + { id: 'id2', name: 'name2', selected: true }, + ] + ); }); it('updates filename if changed', function () { act(() => { - result.current.onChangeFilename('filename1234'); + result.current.exportConnections.onChangeFilename('filename1234'); }); - expect(result.current.state.filename).to.equal('filename1234'); + expect(result.current.exportConnections.state.filename).to.equal( + 'filename1234' + ); }); it('handles actual export', async function () { - rerender({ - favoriteConnections: [ - { id: 'id1', favorite: { name: 'name1' } }, - { id: 'id2', favorite: { name: 'name2' } }, - ], + await act(async () => { + await connectionStorage.save?.({ + connectionInfo: { + id: 'id1', + connectionOptions: { + connectionString: 'mongodb://localhost:2020', + }, + savedConnectionType: 'favorite', + favorite: { + name: 'name1', + }, + }, + }); + await connectionStorage.save?.({ + connectionInfo: { + id: 'id2', + connectionOptions: { + connectionString: 'mongodb://localhost:2021', + }, + savedConnectionType: 'favorite', + favorite: { + name: 'name1', + }, + }, + }); }); + rerender({}); act(() => { - result.current.onChangeConnectionList([ + result.current.exportConnections.onChangeConnectionList([ { id: 'id1', name: 'name1', selected: false }, { id: 'id2', name: 'name2', selected: true }, ]); @@ -151,55 +215,59 @@ describe('useExportConnections', function () { const filename = path.join(tmpdir, 'connections.json'); const fileContents = '{"connections":[1,2,3]}'; - exportConnections.resolves(fileContents); + const exportConnectionStub = sandbox + .stub(connectionStorage, 'exportConnections') + .resolves(fileContents); act(() => { - result.current.onChangeFilename(filename); - result.current.onChangePassphrase('s3cr3t'); + result.current.exportConnections.onChangeFilename(filename); + result.current.exportConnections.onChangePassphrase('s3cr3t'); }); act(() => { - result.current.onSubmit(); + result.current.exportConnections.onSubmit(); }); expect(await finishedPromise).to.equal('succeeded'); expect(await fs.readFile(filename, 'utf8')).to.equal(fileContents); - expect(exportConnections).to.have.been.calledOnce; - const arg = exportConnections.firstCall.args[0]; - expect(arg.options.passphrase).to.equal('s3cr3t'); - expect(arg.options.filterConnectionIds).to.deep.equal(['id2']); - expect(arg.options.trackingProps).to.deep.equal({ context: 'Tests' }); - expect(arg.options.removeSecrets).to.equal(false); + expect(exportConnectionStub).to.have.been.calledOnce; + const arg = exportConnectionStub.firstCall.args[0]; + expect(arg?.options?.passphrase).to.equal('s3cr3t'); + expect(arg?.options?.filterConnectionIds).to.deep.equal(['id2']); + expect(arg?.options?.trackingProps).to.deep.equal({ context: 'Tests' }); + expect(arg?.options?.removeSecrets).to.equal(false); }); it('resets errors if filename changes', async function () { const filename = path.join(tmpdir, 'nonexistent', 'connections.json'); - exportConnections.resolves(''); + const exportConnectionsStub = sandbox + .stub(connectionStorage, 'exportConnections') + .resolves(''); act(() => { - result.current.onChangeFilename(filename); + result.current.exportConnections.onChangeFilename(filename); }); - expect(result.current.state.inProgress).to.equal(false); + expect(result.current.exportConnections.state.inProgress).to.equal(false); act(() => { - result.current.onSubmit(); + result.current.exportConnections.onSubmit(); }); - expect(result.current.state.inProgress).to.equal(true); - expect(result.current.state.error).to.equal(''); + expect(result.current.exportConnections.state.inProgress).to.equal(true); + expect(result.current.exportConnections.state.error).to.equal(''); await renderHookResult.waitForValueToChange( - () => result.current.state.inProgress + () => result.current.exportConnections.state.inProgress ); - expect(result.current.state.inProgress).to.equal(false); - expect(result.current.state.error).to.include('ENOENT'); + expect(result.current.exportConnections.state.inProgress).to.equal(false); + expect(result.current.exportConnections.state.error).to.include('ENOENT'); - expect(exportConnections).to.have.been.calledOnce; + expect(exportConnectionsStub).to.have.been.calledOnce; expect(finish).to.not.have.been.called; act(() => { - result.current.onChangeFilename(filename + '-changed'); + result.current.exportConnections.onChangeFilename(filename + '-changed'); }); - expect(result.current.state.error).to.equal(''); + expect(result.current.exportConnections.state.error).to.equal(''); }); }); diff --git a/packages/compass-connection-import-export/src/hooks/use-export.ts b/packages/compass-connection-import-export/src/hooks/use-export.ts index e774944552c..60118ea5e74 100644 --- a/packages/compass-connection-import-export/src/hooks/use-export.ts +++ b/packages/compass-connection-import-export/src/hooks/use-export.ts @@ -4,8 +4,10 @@ import { COMMON_INITIAL_STATE, useImportExportConnectionsCommon, } from './common'; -import type { ConnectionInfo } from '@mongodb-js/connection-storage/renderer'; -import { ConnectionStorage } from '@mongodb-js/connection-storage/renderer'; +import { + type ConnectionInfo, + useConnectionStorageContext, +} from '@mongodb-js/connection-storage/provider'; import { promises as fs } from 'fs'; import type { ImportExportResult, @@ -13,6 +15,7 @@ import type { CommonImportExportState, } from './common'; import { usePreference } from 'compass-preferences-model/provider'; +import { useConnectionRepository } from '@mongodb-js/compass-connections/provider'; type ExportConnectionsState = CommonImportExportState & { removeSecrets: boolean; @@ -35,22 +38,15 @@ function connectionInfosToConnectionShortInfos( })); } -export function useExportConnections( - { - finish, - favoriteConnections, - open, - trackingProps, - }: { - finish: (result: ImportExportResult) => void; - favoriteConnections: Pick[]; - open: boolean; - trackingProps?: Record; - }, - exportConnections = ConnectionStorage.exportConnections.bind( - ConnectionStorage - ) -): { +export function useExportConnections({ + finish, + open, + trackingProps, +}: { + finish: (result: ImportExportResult) => void; + open: boolean; + trackingProps?: Record; +}): { onCancel: () => void; onSubmit: () => void; onChangeFilename: (filename: string) => void; @@ -59,6 +55,16 @@ export function useExportConnections( onChangeRemoveSecrets: (evt: React.ChangeEvent) => void; state: ExportConnectionsState; } { + const { favoriteConnections } = useConnectionRepository(); + const connectionStorage = useConnectionStorageContext(); + const exportConnectionsImpl = + connectionStorage.exportConnections?.bind(connectionStorage); + if (!exportConnectionsImpl) { + throw new Error( + 'Export Connections feature requires the provided ConnectionStorage to implement exportConnections' + ); + } + const [state, setState] = useState(INITIAL_STATE); useEffect(() => setState(INITIAL_STATE), [open]); const { passphrase, filename, connectionList, removeSecrets } = state; @@ -100,7 +106,7 @@ export function useExportConnections( // in the UI, we protect users against combining them by disabling the passphrase input. const passphrase = removeSecrets ? '' : state.passphrase; try { - const fileContents = await exportConnections({ + const fileContents = await exportConnectionsImpl({ options: { passphrase, filterConnectionIds, diff --git a/packages/compass-connection-import-export/src/hooks/use-import.spec.ts b/packages/compass-connection-import-export/src/hooks/use-import.spec.ts index 3030ace17e8..68e606959b6 100644 --- a/packages/compass-connection-import-export/src/hooks/use-import.spec.ts +++ b/packages/compass-connection-import-export/src/hooks/use-import.spec.ts @@ -1,3 +1,4 @@ +import React from 'react'; import { expect } from 'chai'; import sinon from 'sinon'; import type { @@ -10,48 +11,65 @@ import type { ImportExportResult } from './common'; import os from 'os'; import path from 'path'; import { promises as fs } from 'fs'; +import { + type ConnectionInfo, + type ConnectionStorage, + InMemoryConnectionStorage, +} from '@mongodb-js/connection-storage/provider'; +import { ConnectionStorageProvider } from '@mongodb-js/connection-storage/provider'; +import { useConnectionRepository } from '@mongodb-js/compass-connections/provider'; type UseImportConnectionsProps = Parameters[0]; type UseImportConnectionsResult = ReturnType; +type UseConnectionRepositoryResult = ReturnType; +type HookResults = { + connectionRepository: UseConnectionRepositoryResult; + importConnections: UseImportConnectionsResult; +}; const exampleFileContents = '{"a":"b"}'; describe('useImportConnections', function () { let sandbox: sinon.SinonSandbox; let finish: sinon.SinonStub; let finishedPromise: Promise; - let importConnections: sinon.SinonStub; - let deserializeConnections: sinon.SinonStub; let defaultProps: UseImportConnectionsProps; let renderHookResult: RenderHookResult< Partial, - UseImportConnectionsResult + HookResults >; - let result: RenderResult; + let result: RenderResult; let rerender: (props: Partial) => void; let tmpdir: string; let exampleFile: string; + let connectionStorage: ConnectionStorage; beforeEach(async function () { sandbox = sinon.createSandbox(); finishedPromise = new Promise((resolve) => { finish = sinon.stub().callsFake(resolve); }); - importConnections = sinon.stub(); - deserializeConnections = sinon.stub(); defaultProps = { finish, open: true, - favoriteConnections: [], trackingProps: { context: 'Tests' }, }; + connectionStorage = new InMemoryConnectionStorage(); + const wrapper: React.FC = ({ children }) => + React.createElement(ConnectionStorageProvider, { + value: connectionStorage, + children, + }); renderHookResult = renderHook( (props: Partial = {}) => { - return useImportConnections( - { ...defaultProps, ...props }, - importConnections, - deserializeConnections - ); - } + return { + connectionRepository: useConnectionRepository(), + importConnections: useImportConnections({ + ...defaultProps, + ...props, + }), + }; + }, + { wrapper } ); ({ result, rerender } = renderHookResult); tmpdir = path.join( @@ -71,41 +89,48 @@ describe('useImportConnections', function () { }); it('updates filename if changed', async function () { - deserializeConnections.callsFake(function ({ - content, - options, - }: { - content: string; - options: any; - }) { - expect(content).to.equal(exampleFileContents); - expect(options.passphrase).to.equal(''); - return [ - { - id: 'id1', - favorite: { name: 'name1' }, - }, - ]; - }); + const deserializeStub = sandbox + .stub(connectionStorage, 'deserializeConnections') + .callsFake(function ({ + content, + options, + }: { + content: string; + options: any; + }) { + expect(content).to.equal(exampleFileContents); + expect(options.passphrase).to.equal(''); + return Promise.resolve([ + { + id: 'id1', + favorite: { name: 'name1' }, + } as ConnectionInfo, + ]); + }); act(() => { - result.current.onChangeFilename(exampleFile); + result.current.importConnections.onChangeFilename(exampleFile); }); - expect(result.current.state.filename).to.equal(exampleFile); - expect(result.current.state.error).to.equal(''); - expect(result.current.state.connectionList).to.deep.equal([]); + expect(result.current.importConnections.state.filename).to.equal( + exampleFile + ); + expect(result.current.importConnections.state.error).to.equal(''); + expect(result.current.importConnections.state.connectionList).to.deep.equal( + [] + ); await renderHookResult.waitForValueToChange( - () => result.current.state.connectionList.length + () => result.current.importConnections.state.connectionList.length ); - expect(deserializeConnections).to.have.been.calledOnce; - expect(result.current.state.connectionList).to.deep.equal([ - { id: 'id1', name: 'name1', selected: true, isExistingFavorite: false }, - ]); + expect(deserializeStub).to.have.been.calledOnce; + expect(result.current.importConnections.state.connectionList).to.deep.equal( + [{ id: 'id1', name: 'name1', selected: true, isExistingFavorite: false }] + ); }); it('updates passphrase if changed', async function () { - deserializeConnections + sandbox + .stub(connectionStorage, 'deserializeConnections') .onFirstCall() .callsFake(function ({ content, @@ -130,47 +155,58 @@ describe('useImportConnections', function () { }) { expect(content).to.equal(exampleFileContents); expect(options.passphrase).to.equal('s3cr3t'); - return [ + return Promise.resolve([ { id: 'id1', favorite: { name: 'name1' }, - }, - ]; + } as ConnectionInfo, + ]); }); act(() => { - result.current.onChangeFilename(exampleFile); - result.current.onChangePassphrase('wrong'); + result.current.importConnections.onChangeFilename(exampleFile); + result.current.importConnections.onChangePassphrase('wrong'); }); - expect(result.current.state.passphrase).to.equal('wrong'); + expect(result.current.importConnections.state.passphrase).to.equal('wrong'); - expect(result.current.state.error).to.equal(''); + expect(result.current.importConnections.state.error).to.equal(''); await renderHookResult.waitForValueToChange( - () => result.current.state.error + () => result.current.importConnections.state.error + ); + expect(result.current.importConnections.state.error).to.equal( + 'wrong password' + ); + expect(result.current.importConnections.state.passphraseRequired).to.equal( + true ); - expect(result.current.state.error).to.equal('wrong password'); - expect(result.current.state.passphraseRequired).to.equal(true); act(() => { - result.current.onChangePassphrase('s3cr3t'); + result.current.importConnections.onChangePassphrase('s3cr3t'); }); - expect(result.current.state.passphrase).to.equal('s3cr3t'); + expect(result.current.importConnections.state.passphrase).to.equal( + 's3cr3t' + ); await renderHookResult.waitForValueToChange( - () => result.current.state.error + () => result.current.importConnections.state.error ); - expect(result.current.state.error).to.equal(''); - expect(result.current.state.passphraseRequired).to.equal(true); - expect(result.current.state.connectionList).to.have.lengthOf(1); + expect(result.current.importConnections.state.error).to.equal(''); + expect(result.current.importConnections.state.passphraseRequired).to.equal( + true + ); + expect( + result.current.importConnections.state.connectionList + ).to.have.lengthOf(1); }); it('does not select existing favorites by default', async function () { - deserializeConnections.callsFake( - ({ content, options }: { content: string; options: any }) => { + sandbox + .stub(connectionStorage, 'deserializeConnections') + .callsFake(({ content, options }: { content: string; options: any }) => { expect(content).to.equal(exampleFileContents); expect(options.passphrase).to.equal(''); - return [ + return Promise.resolve([ { id: 'id1', favorite: { name: 'name1' }, @@ -179,35 +215,53 @@ describe('useImportConnections', function () { id: 'id2', favorite: { name: 'name2' }, }, - ]; - } - ); + ] as ConnectionInfo[]); + }); - rerender({ - favoriteConnections: [{ id: 'id1', favorite: { name: 'name1' } }], + await act(async () => { + await result.current.connectionRepository.saveConnection({ + id: 'id1', + connectionOptions: { connectionString: 'mongodb://localhost:2020' }, + favorite: { + name: 'name1', + }, + savedConnectionType: 'favorite', + }); }); + + rerender({}); act(() => { - result.current.onChangeFilename(exampleFile); + result.current.importConnections.onChangeFilename(exampleFile); }); await renderHookResult.waitForValueToChange( - () => result.current.state.connectionList.length - ); - expect(result.current.state.connectionList).to.deep.equal([ - { id: 'id1', name: 'name1', selected: false, isExistingFavorite: true }, - { id: 'id2', name: 'name2', selected: true, isExistingFavorite: false }, - ]); - - rerender({ - favoriteConnections: [ - { id: 'id1', favorite: { name: 'name1' } }, - { id: 'id2', favorite: { name: 'name2' } }, - ], + () => result.current.importConnections.state.connectionList.length + ); + expect(result.current.importConnections.state.connectionList).to.deep.equal( + [ + { id: 'id1', name: 'name1', selected: false, isExistingFavorite: true }, + { id: 'id2', name: 'name2', selected: true, isExistingFavorite: false }, + ] + ); + + await act(async () => { + await result.current.connectionRepository.saveConnection({ + id: 'id2', + connectionOptions: { connectionString: 'mongodb://localhost:2020' }, + favorite: { + name: 'name2', + }, + savedConnectionType: 'favorite', + }); }); - expect(result.current.state.connectionList).to.deep.equal([ - { id: 'id1', name: 'name1', selected: false, isExistingFavorite: true }, - { id: 'id2', name: 'name2', selected: true, isExistingFavorite: true }, - ]); + + rerender({}); + expect(result.current.importConnections.state.connectionList).to.deep.equal( + [ + { id: 'id1', name: 'name1', selected: false, isExistingFavorite: true }, + { id: 'id2', name: 'name2', selected: true, isExistingFavorite: true }, + ] + ); }); it('handles actual import', async function () { @@ -221,34 +275,37 @@ describe('useImportConnections', function () { favorite: { name: 'name2' }, }, ]; - deserializeConnections.callsFake(() => connections); - importConnections.callsFake(({ content }: { content: string }) => { - expect(content).to.equal(exampleFileContents); - return connections; - }); + sandbox + .stub(connectionStorage, 'deserializeConnections') + .resolves(connections as ConnectionInfo[]); + const importConnectionsStub = sandbox + .stub(connectionStorage, 'importConnections') + .callsFake(({ content }: { content: string }) => { + expect(content).to.equal(exampleFileContents); + return Promise.resolve(); + }); act(() => { - result.current.onChangeFilename(exampleFile); + result.current.importConnections.onChangeFilename(exampleFile); }); await renderHookResult.waitForValueToChange( - () => result.current.state.fileContents + () => result.current.importConnections.state.fileContents ); act(() => { - result.current.onChangeConnectionList([ + result.current.importConnections.onChangeConnectionList([ { id: 'id1', name: 'name1', selected: false }, { id: 'id2', name: 'name2', selected: true }, ]); }); act(() => { - result.current.onSubmit(); + result.current.importConnections.onSubmit(); }); expect(await finishedPromise).to.equal('succeeded'); - expect(importConnections).to.have.been.calledOnce; - const arg = importConnections.firstCall.args[0]; - expect(arg.options.trackingProps).to.deep.equal({ context: 'Tests' }); - expect(arg.options.saveConnections).to.equal(undefined); - expect(arg.options.filterConnectionIds).to.deep.equal(['id2']); + expect(importConnectionsStub).to.have.been.calledOnce; + const arg = importConnectionsStub.firstCall.args[0]; + expect(arg?.options?.trackingProps).to.deep.equal({ context: 'Tests' }); + expect(arg?.options?.filterConnectionIds).to.deep.equal(['id2']); }); }); diff --git a/packages/compass-connection-import-export/src/hooks/use-import.ts b/packages/compass-connection-import-export/src/hooks/use-import.ts index edb8381773a..cd35965e723 100644 --- a/packages/compass-connection-import-export/src/hooks/use-import.ts +++ b/packages/compass-connection-import-export/src/hooks/use-import.ts @@ -1,6 +1,8 @@ import { useCallback, useEffect, useState } from 'react'; -import type { ConnectionInfo } from '@mongodb-js/connection-storage/renderer'; -import { ConnectionStorage } from '@mongodb-js/connection-storage/renderer'; +import { + type ConnectionStorage, + useConnectionStorageContext, +} from '@mongodb-js/connection-storage/provider'; import { promises as fs } from 'fs'; import { COMMON_INITIAL_STATE, @@ -11,6 +13,7 @@ import type { ConnectionShortInfo, CommonImportExportState, } from './common'; +import { useConnectionRepository } from '@mongodb-js/compass-connections/provider'; type ConnectionImportInfo = ConnectionShortInfo & { isExistingFavorite: boolean; @@ -35,7 +38,7 @@ async function loadFile( }: Pick & { favoriteConnectionIds: string[]; }, - deserializeConnections: typeof ConnectionStorage['deserializeConnections'] + deserializeConnections: Required['deserializeConnections'] ): Promise> { if (!filename) { return INITIAL_STATE; @@ -80,26 +83,15 @@ async function loadFile( } } -export function useImportConnections( - { - finish, - favoriteConnections, - open, - trackingProps, - }: { - finish: (result: ImportExportResult) => void; - favoriteConnections: Pick[]; - open: boolean; - trackingProps?: Record; - connectionStorage?: typeof ConnectionStorage; - }, - importConnections = ConnectionStorage.importConnections.bind( - ConnectionStorage - ), - deserializeConnections = ConnectionStorage.deserializeConnections.bind( - ConnectionStorage - ) -): { +export function useImportConnections({ + finish, + open, + trackingProps, +}: { + finish: (result: ImportExportResult) => void; + open: boolean; + trackingProps?: Record; +}): { onCancel: () => void; onSubmit: () => void; onChangeFilename: (filename: string) => void; @@ -107,6 +99,18 @@ export function useImportConnections( onChangeConnectionList: (connectionInfos: ConnectionShortInfo[]) => void; state: ImportConnectionsState; } { + const { favoriteConnections } = useConnectionRepository(); + const connectionStorage = useConnectionStorageContext(); + const importConnectionsImpl = + connectionStorage.importConnections?.bind(connectionStorage); + const deserializeConnectionsImpl = + connectionStorage.deserializeConnections?.bind(connectionStorage); + if (!importConnectionsImpl || !deserializeConnectionsImpl) { + throw new Error( + 'Import Connections feature requires the provided ConnectionStorage to implement importConnections and deserializeConnections' + ); + } + const [state, setState] = useState(INITIAL_STATE); useEffect(() => setState(INITIAL_STATE), [open]); const { passphrase, filename, fileContents, connectionList } = state; @@ -134,7 +138,7 @@ export function useImportConnections( .filter((x) => x.selected) .map((x) => x.id); try { - await importConnections({ + await importConnectionsImpl({ content: fileContents, options: { passphrase, @@ -163,7 +167,7 @@ export function useImportConnections( timer = undefined; void loadFile( { filename, passphrase, favoriteConnectionIds }, - deserializeConnections + deserializeConnectionsImpl ).then((stateUpdate) => { setState((prevState) => { if ( diff --git a/packages/compass-databases-navigation/.depcheckrc b/packages/compass-connections-navigation/.depcheckrc similarity index 100% rename from packages/compass-databases-navigation/.depcheckrc rename to packages/compass-connections-navigation/.depcheckrc diff --git a/packages/compass-databases-navigation/.eslintignore b/packages/compass-connections-navigation/.eslintignore similarity index 100% rename from packages/compass-databases-navigation/.eslintignore rename to packages/compass-connections-navigation/.eslintignore diff --git a/packages/compass-databases-navigation/.eslintrc.js b/packages/compass-connections-navigation/.eslintrc.js similarity index 92% rename from packages/compass-databases-navigation/.eslintrc.js rename to packages/compass-connections-navigation/.eslintrc.js index e4cf824b6ac..9c3ab95632f 100644 --- a/packages/compass-databases-navigation/.eslintrc.js +++ b/packages/compass-connections-navigation/.eslintrc.js @@ -1,3 +1,4 @@ +'use strict'; module.exports = { root: true, extends: ['@mongodb-js/eslint-config-compass'], diff --git a/packages/compass-databases-navigation/.mocharc.js b/packages/compass-connections-navigation/.mocharc.js similarity index 82% rename from packages/compass-databases-navigation/.mocharc.js rename to packages/compass-connections-navigation/.mocharc.js index 30aecfb78c3..5a33f216327 100644 --- a/packages/compass-databases-navigation/.mocharc.js +++ b/packages/compass-connections-navigation/.mocharc.js @@ -1 +1,2 @@ +'use strict'; module.exports = require('@mongodb-js/mocha-config-compass/react'); diff --git a/packages/compass-databases-navigation/.prettierignore b/packages/compass-connections-navigation/.prettierignore similarity index 100% rename from packages/compass-databases-navigation/.prettierignore rename to packages/compass-connections-navigation/.prettierignore diff --git a/packages/compass-databases-navigation/.prettierrc.json b/packages/compass-connections-navigation/.prettierrc.json similarity index 100% rename from packages/compass-databases-navigation/.prettierrc.json rename to packages/compass-connections-navigation/.prettierrc.json diff --git a/packages/compass-databases-navigation/package.json b/packages/compass-connections-navigation/package.json similarity index 81% rename from packages/compass-databases-navigation/package.json rename to packages/compass-connections-navigation/package.json index b6f9cd3b6e1..c0f210504c8 100644 --- a/packages/compass-databases-navigation/package.json +++ b/packages/compass-connections-navigation/package.json @@ -1,5 +1,5 @@ { - "name": "@mongodb-js/compass-databases-navigation", + "name": "@mongodb-js/compass-connections-navigation", "description": "Databases and collections sidebar navigation tree", "author": { "name": "MongoDB Inc", @@ -13,7 +13,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "1.23.1", + "version": "1.25.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -47,17 +47,20 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "dependencies": { - "@mongodb-js/compass-components": "^1.22.1", - "compass-preferences-model": "^2.18.1", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/connection-info": "^0.2.1", + "@mongodb-js/connection-form": "^1.24.0", + "@mongodb-js/compass-workspaces": "^0.7.0", + "compass-preferences-model": "^2.20.0", "react": "^17.0.2", "react-virtualized-auto-sizer": "^1.0.6", "react-window": "^1.8.6" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "@types/chai": "^4.2.21", diff --git a/packages/compass-databases-navigation/src/collection-item.tsx b/packages/compass-connections-navigation/src/collection-item.tsx similarity index 85% rename from packages/compass-databases-navigation/src/collection-item.tsx rename to packages/compass-connections-navigation/src/collection-item.tsx index 23c0260b1cf..85b8fe4ec41 100644 --- a/packages/compass-databases-navigation/src/collection-item.tsx +++ b/packages/compass-connections-navigation/src/collection-item.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react/prop-types */ import React, { useCallback, useMemo } from 'react'; import { useHoverState, @@ -8,7 +7,7 @@ import { Icon, } from '@mongodb-js/compass-components'; import type { ItemAction } from '@mongodb-js/compass-components'; -import { COLLECTION_ROW_HEIGHT } from './constants'; +import { ROW_HEIGHT } from './constants'; import { ItemContainer, ItemLabel, @@ -22,6 +21,7 @@ import type { } from './tree-item'; import type { Actions } from './constants'; import { usePreference } from 'compass-preferences-model/provider'; +import { getItemPaddingStyles } from './utils'; const CollectionIcon: React.FunctionComponent<{ type: string; @@ -38,13 +38,12 @@ const CollectionIcon: React.FunctionComponent<{ }; const collectionItem = css({ - height: COLLECTION_ROW_HEIGHT, + height: ROW_HEIGHT, }); const itemButtonWrapper = css({ - height: COLLECTION_ROW_HEIGHT, + height: ROW_HEIGHT, paddingRight: spacing[1], - paddingLeft: spacing[5] + spacing[1] + spacing[4], }); const collectionItemLabel = css({ @@ -54,13 +53,16 @@ const collectionItemLabel = css({ export const CollectionItem: React.FunctionComponent< VirtualListItemProps & TreeItemProps & NamespaceItemProps > = ({ + connectionId, id, name, + level, type, posInSet, setSize, isActive, isReadOnly, + isSingleConnection, isTabbable, style, onNamespaceAction, @@ -70,28 +72,35 @@ export const CollectionItem: React.FunctionComponent< ); const [hoverProps, isHovered] = useHoverState(); + const itemPaddingStyles = useMemo( + () => getItemPaddingStyles({ level, isSingleConnection }), + [level, isSingleConnection] + ); + const onDefaultAction = useCallback( (evt) => { if (evt.metaKey || evt.ctrlKey) { onNamespaceAction( + connectionId, evt.currentTarget.dataset.id as string, 'open-in-new-tab' ); } else { onNamespaceAction( + connectionId, evt.currentTarget.dataset.id as string, 'select-collection' ); } }, - [onNamespaceAction] + [connectionId, onNamespaceAction] ); const onAction = useCallback( (action: Actions) => { - onNamespaceAction(id, action); + onNamespaceAction(connectionId, id, action); }, - [id, onNamespaceAction] + [connectionId, id, onNamespaceAction] ); const actions = useMemo(() => { @@ -150,7 +159,7 @@ export const CollectionItem: React.FunctionComponent< - + {name} diff --git a/packages/compass-connections-navigation/src/connection-item.tsx b/packages/compass-connections-navigation/src/connection-item.tsx new file mode 100644 index 00000000000..ad63ecb0848 --- /dev/null +++ b/packages/compass-connections-navigation/src/connection-item.tsx @@ -0,0 +1,187 @@ +import React, { useCallback, useMemo } from 'react'; +import { + useHoverState, + spacing, + css, + ItemActionControls, + Icon, + ServerIcon, +} from '@mongodb-js/compass-components'; +import type { ItemAction } from '@mongodb-js/compass-components'; +import { ROW_HEIGHT } from './constants'; +import { + ItemContainer, + ItemLabel, + ItemWrapper, + ItemButtonWrapper, + ExpandButton, +} from './tree-item'; +import type { + VirtualListItemProps, + TreeItemProps, + NamespaceItemProps, +} from './tree-item'; +import type { Actions } from './constants'; +import type { ConnectionInfo } from '@mongodb-js/connection-info'; +import { getItemPaddingStyles } from './utils'; + +const iconStyles = css({ + flex: 'none', +}); + +const connectionItem = css({ + height: ROW_HEIGHT, +}); + +const itemButtonWrapper = css({ + height: ROW_HEIGHT, + paddingRight: spacing[1], +}); + +const connectionItemLabel = css({ + marginLeft: spacing[2], +}); + +export const ConnectionItem: React.FunctionComponent< + VirtualListItemProps & + TreeItemProps & + NamespaceItemProps & { + isExpanded: boolean; + onConnectionExpand(id: string, isExpanded: boolean): void; + onConnectionSelect(id: string): void; + } & { connectionInfo: ConnectionInfo } +> = ({ + id, + name, + level, + posInSet, + setSize, + isExpanded, + isActive, + isReadOnly, + isSingleConnection, + isTabbable, + style, + connectionInfo, + onNamespaceAction, + onConnectionExpand, + onConnectionSelect, +}) => { + const [hoverProps, isHovered] = useHoverState(); + + const isLocalhost = + connectionInfo.connectionOptions.connectionString.startsWith( + 'mongodb://localhost' + ); // TODO(COMPASS-7832) + const isFavorite = connectionInfo.savedConnectionType === 'favorite'; + + const itemPaddingStyles = useMemo( + () => getItemPaddingStyles({ level, isSingleConnection }), + [level, isSingleConnection] + ); + + const onExpandButtonClick = useCallback( + (evt: React.MouseEvent) => { + evt.stopPropagation(); + onConnectionExpand(connectionInfo.id, !isExpanded); + }, + [onConnectionExpand, connectionInfo.id, isExpanded] + ); + + const onDefaultAction = useCallback( + () => onConnectionSelect(connectionInfo.id), + [onConnectionSelect, connectionInfo.id] + ); + + const onAction = useCallback( + (action: Actions) => { + onNamespaceAction(id, id, action); + }, + [id, onNamespaceAction] + ); + + const actions: ItemAction[] = useMemo(() => { + const isFavorite = connectionInfo.savedConnectionType === 'favorite'; + + const actions: ItemAction[] = [ + { + action: 'connection-performance-metrics', + icon: 'Gauge', + label: 'View performance metrics', + }, + { + action: 'open-connection-info', + icon: 'InfoWithCircle', + label: 'Show connection info', + }, + { + action: 'copy-connection-string', + icon: 'Copy', + label: 'Copy connection string', + }, + { + action: 'connection-toggle-favorite', + icon: 'Favorite', + label: isFavorite ? 'Unfavorite' : 'Favorite', + }, + { + action: 'connection-disconnect', + icon: 'Disconnect', + label: 'Disconnect', + variant: 'destructive', + }, + ]; + + return actions; + }, [connectionInfo.savedConnectionType]); + + const connectionIcon = isLocalhost ? ( + + ) : isFavorite ? ( + + ) : ( + + ); + + return ( + + + + + {connectionIcon} + + {name} + + + {!isReadOnly && ( + + onAction={onAction} + isVisible={isActive || isHovered} + data-testid="sidebar-connection-item-actions" + iconSize="small" + actions={actions} + > + )} + + + ); +}; diff --git a/packages/compass-connections-navigation/src/connections-navigation-tree.spec.tsx b/packages/compass-connections-navigation/src/connections-navigation-tree.spec.tsx new file mode 100644 index 00000000000..d75fd909646 --- /dev/null +++ b/packages/compass-connections-navigation/src/connections-navigation-tree.spec.tsx @@ -0,0 +1,530 @@ +/* eslint-disable @typescript-eslint/no-empty-function */ +import React from 'react'; +import { + render, + screen, + cleanup, + within, + waitFor, +} from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { expect } from 'chai'; +import Sinon from 'sinon'; +import { + type Connection, + ConnectionsNavigationTree, +} from './connections-navigation-tree'; +import type { PreferencesAccess } from 'compass-preferences-model'; +import { createSandboxFromDefaultPreferences } from 'compass-preferences-model'; +import { PreferencesProvider } from 'compass-preferences-model/provider'; +import { type WorkspaceTab } from '@mongodb-js/compass-workspaces'; + +const connections: Connection[] = [ + { + connectionInfo: { + id: 'connection_ready', + connectionOptions: { + connectionString: 'mongodb://turtle', + }, + favorite: { + name: 'turtles', + }, + savedConnectionType: 'favorite', + }, + name: 'turtles', + databasesStatus: 'ready', + databasesLength: 2, + databases: [ + { + _id: 'db_initial', + name: 'foo', + collectionsStatus: 'initial', + collectionsLength: 5, + collections: [], + }, + { + _id: 'db_ready', + name: 'bar', + collectionsStatus: 'ready', + collectionsLength: 3, + collections: [ + { _id: 'db_ready.meow', name: 'meow', type: 'collection' }, + { _id: 'db_ready.woof', name: 'woof', type: 'timeseries' }, + { _id: 'db_ready.bwok', name: 'bwok', type: 'view' }, + ], + }, + ], + isReady: true, + isDataLake: false, + isWritable: false, + }, + { + connectionInfo: { + id: 'connection_initial', + connectionOptions: { + connectionString: 'mongodb://peaches', + }, + favorite: { + name: 'peaches', + }, + savedConnectionType: 'favorite', + }, + name: 'peaches', + databasesStatus: 'initial', + databasesLength: 2, + databases: [], + isReady: true, + isDataLake: false, + isWritable: false, + }, +]; + +const props = { + connections, + expanded: { turtles: { bar: true } }, + activeWorkspace: { + connectionId: 'connection_ready', + namespace: 'db_ready.meow', + type: 'Collection', + } as WorkspaceTab, + onConnectionExpand: () => {}, + onDatabaseExpand: () => {}, + onNamespaceAction: () => {}, +}; + +describe('ConnectionsNavigationTree', function () { + let preferences: PreferencesAccess; + + async function renderConnectionsNavigationTree(customProps = {}) { + preferences = await createSandboxFromDefaultPreferences(); + await preferences.savePreferences({ + enableRenameCollectionModal: true, + enableNewMultipleConnectionSystem: true, + }); + return render( + + + + ); + } + + afterEach(cleanup); + + context('when the rename collection feature flag is enabled', () => { + it('shows the Rename Collection action', async function () { + await renderConnectionsNavigationTree({ + expanded: { connection_ready: { db_ready: true } }, + }); + + const collection = screen.getByTestId('sidebar-collection-db_ready.meow'); + const showActionsButton = within(collection).getByTitle('Show actions'); + + expect(within(collection).getByTitle('Show actions')).to.exist; + + userEvent.click(showActionsButton); + + expect(screen.getByText('Rename collection')).to.exist; + }); + + it('should activate callback with `rename-collection` when corresponding action is clicked', async function () { + const spy = Sinon.spy(); + await renderConnectionsNavigationTree({ + expanded: { connection_ready: { db_ready: true } }, + onNamespaceAction: spy, + }); + + const collection = screen.getByTestId('sidebar-collection-db_ready.meow'); + + userEvent.click(within(collection).getByTitle('Show actions')); + userEvent.click(screen.getByText('Rename collection')); + + expect(spy).to.be.calledOnceWithExactly( + 'connection_ready', + 'db_ready.meow', + 'rename-collection' + ); + }); + }); + + it('should render connections', async function () { + await renderConnectionsNavigationTree(); + + expect(screen.getByText('turtles')).to.exist; + expect(screen.getByText('peaches')).to.exist; + }); + + it('when a connection is collapsed, it should not render databases', async function () { + await renderConnectionsNavigationTree(); + + expect(screen.queryByText('foo')).not.to.exist; + expect(screen.queryByText('bar')).not.to.exist; + }); + + it('when a connection is expanded, it should render databases', async function () { + await renderConnectionsNavigationTree({ + expanded: { connection_ready: {} }, + }); + + expect(screen.getByText('foo')).to.exist; + expect(screen.getByText('bar')).to.exist; + }); + + it('when a connection is expanded but databases are not ready, it should render database placeholders', async function () { + await renderConnectionsNavigationTree({ + expanded: { connection_initial: {} }, + }); + + expect(screen.getAllByTestId('placeholder')).to.have.lengthOf(5); + }); + + it('when database is expanded, it should render collections', async function () { + await renderConnectionsNavigationTree({ + expanded: { connection_ready: { db_ready: true } }, + }); + + expect(screen.getByText('meow')).to.exist; + expect(screen.getByText('woof')).to.exist; + expect(screen.getByText('bwok')).to.exist; + }); + + it('when database is expanded but collections are not ready, it should render collection placeholders', async function () { + await renderConnectionsNavigationTree({ + expanded: { connection_ready: { db_initial: true } }, + }); + + expect(screen.getAllByTestId('placeholder')).to.have.lengthOf(5); + }); + + it('should make current active namespace tabbable', async function () { + await renderConnectionsNavigationTree({ + expanded: { + connection_ready: {}, + }, + activeWorkspace: { + ...props.activeWorkspace, + namespace: 'db_ready', + type: 'Collections', + } as WorkspaceTab, + }); + + userEvent.tab(); + + await waitFor(() => { + // Virtual list will be the one to grab the focus first, but will + // immediately forward it to the element and mocking raf here breaks + // virtual list implementatin, waitFor is to accomodate for that + expect(document.querySelector('[data-id="db_ready"]')).to.eq( + document.activeElement + ); + return true; + }); + }); + + describe('when isReadOnly is false or undefined', function () { + it('should show all database actions on hover', async function () { + await renderConnectionsNavigationTree({ + expanded: { connection_ready: {} }, + }); + + userEvent.hover(screen.getByText('foo')); + + const database = screen.getByTestId('sidebar-database-db_initial'); + + expect(within(database).getByTitle('Create collection')).to.exist; + expect(within(database).getByTitle('Drop database')).to.exist; + }); + + it('should show all database actions for active namespace', async function () { + await renderConnectionsNavigationTree({ + expanded: { + connection_ready: {}, + }, + activeWorkspace: { + ...props.activeWorkspace, + namespace: 'db_ready', + type: 'Collections', + } as WorkspaceTab, + }); + + const database = screen.getByTestId('sidebar-database-db_ready'); + + expect(within(database).getByTitle('Create collection')).to.exist; + expect(within(database).getByTitle('Drop database')).to.exist; + }); + + it('should show all collection actions', async function () { + await renderConnectionsNavigationTree({ + expanded: { + connection_ready: { db_ready: true }, + }, + }); + + const collection = screen.getByTestId('sidebar-collection-db_ready.meow'); + const showActionsButton = within(collection).getByTitle('Show actions'); + + expect(within(collection).getByTitle('Show actions')).to.exist; + + userEvent.click(showActionsButton); + + expect(screen.getByText('Open in new tab')).to.exist; + expect(() => screen.getByText('Rename collection')).to.throw; + expect(screen.getByText('Drop collection')).to.exist; + }); + + it('should show all view actions', async function () { + await renderConnectionsNavigationTree({ + expanded: { + connection_ready: { db_ready: true }, + }, + activeWorkspace: { + ...props.activeWorkspace, + namespace: 'db_ready.bwok', + } as WorkspaceTab, + }); + + const collection = screen.getByTestId('sidebar-collection-db_ready.bwok'); + const showActionsButton = within(collection).getByTitle('Show actions'); + + expect(within(collection).getByTitle('Show actions')).to.exist; + + userEvent.click(showActionsButton); + + expect(screen.getByText('Open in new tab')).to.exist; + expect(screen.getByText('Drop view')).to.exist; + expect(screen.getByText('Duplicate view')).to.exist; + expect(screen.getByText('Modify view')).to.exist; + + // views cannot be renamed + expect(() => screen.getByText('Rename collection')).to.throw; + }); + }); + + describe('when isReadOnly is true', function () { + it('should not show database actions', async function () { + await renderConnectionsNavigationTree({ + expanded: { + connection_ready: { db_ready: true }, + }, + activeWorkspace: { + ...props.activeWorkspace, + namespace: 'db_ready', + type: 'Collections', + } as WorkspaceTab, + isReadOnly: true, + }); + + const database = screen.getByTestId('sidebar-database-db_ready'); + + expect(() => within(database).getByTitle('Create collection')).to.throw; + expect(() => within(database).getByTitle('Drop database')).to.throw; + }); + + it('should show only one collection action', async function () { + await renderConnectionsNavigationTree({ + expanded: { + connection_ready: { db_ready: true }, + }, + activeWorkspace: { + ...props.activeWorkspace, + namespace: 'db_ready.bwok', + type: 'Collection', + } as WorkspaceTab, + isReadOnly: true, + }); + + const collection = screen.getByTestId('sidebar-collection-db_ready.bwok'); + + expect(within(collection).getByTitle('Open in new tab')).to.exist; + }); + }); + + describe('onNamespaceAction', function () { + let preferences: PreferencesAccess; + beforeEach(async function () { + preferences = await createSandboxFromDefaultPreferences(); + await preferences.savePreferences({ + enableRenameCollectionModal: true, + enableNewMultipleConnectionSystem: true, + }); + }); + + it('should activate callback with `select-connection` when a connection is clicked', async function () { + const spy = Sinon.spy(); + await renderConnectionsNavigationTree({ + onConnectionSelect: spy, + }); + + userEvent.click(screen.getByText('turtles')); + + expect(spy).to.be.calledOnceWithExactly('connection_ready'); + }); + + it('should activate callback with `select-database` when database is clicked', async function () { + const spy = Sinon.spy(); + await renderConnectionsNavigationTree({ + expanded: { connection_ready: {} }, + onNamespaceAction: spy, + }); + + userEvent.click(screen.getByText('foo')); + + expect(spy).to.be.calledOnceWithExactly( + 'connection_ready', + 'db_initial', + 'select-database' + ); + }); + + it('should activate callback with `select-collection` when collection is clicked', async function () { + const spy = Sinon.spy(); + await renderConnectionsNavigationTree({ + expanded: { connection_ready: { db_ready: true } }, + onNamespaceAction: spy, + }); + + userEvent.click(screen.getByText('meow')); + + expect(spy).to.be.calledOnceWithExactly( + 'connection_ready', + 'db_ready.meow', + 'select-collection' + ); + }); + + describe('database actions', function () { + it('should activate callback with `drop-database` when corresponding action is clicked', async function () { + const spy = Sinon.spy(); + await renderConnectionsNavigationTree({ + expanded: { connection_ready: {} }, + onNamespaceAction: spy, + activeWorkspace: { + ...props.activeWorkspace, + namespace: 'db_initial', + type: 'Collections', + } as WorkspaceTab, + }); + + userEvent.click(screen.getByTitle('Drop database')); + + expect(spy).to.be.calledOnceWithExactly( + 'connection_ready', + 'db_initial', + 'drop-database' + ); + }); + + it('should activate callback with `create-collection` when corresponding action is clicked', async function () { + const spy = Sinon.spy(); + await renderConnectionsNavigationTree({ + expanded: { connection_ready: {} }, + onNamespaceAction: spy, + activeWorkspace: { + ...props.activeWorkspace, + namespace: 'db_initial', + type: 'Collections', + } as WorkspaceTab, + }); + + userEvent.click(screen.getByTitle('Create collection')); + + expect(spy).to.be.calledOnceWithExactly( + 'connection_ready', + 'db_initial', + 'create-collection' + ); + }); + }); + + describe('collection actions', function () { + it('should activate callback with `open-in-new-tab` when corresponding action is clicked', async function () { + const spy = Sinon.spy(); + await renderConnectionsNavigationTree({ + expanded: { connection_ready: { db_ready: true } }, + onNamespaceAction: spy, + }); + + const collection = screen.getByTestId( + 'sidebar-collection-db_ready.meow' + ); + + userEvent.click(within(collection).getByTitle('Show actions')); + userEvent.click(screen.getByText('Open in new tab')); + + expect(spy).to.be.calledOnceWithExactly( + 'connection_ready', + 'db_ready.meow', + 'open-in-new-tab' + ); + }); + + it('should activate callback with `drop-collection` when corresponding action is clicked', async function () { + const spy = Sinon.spy(); + await renderConnectionsNavigationTree({ + expanded: { connection_ready: { db_ready: true } }, + onNamespaceAction: spy, + }); + + const collection = screen.getByTestId( + 'sidebar-collection-db_ready.meow' + ); + + userEvent.click(within(collection).getByTitle('Show actions')); + userEvent.click(screen.getByText('Drop collection')); + + expect(spy).to.be.calledOnceWithExactly( + 'connection_ready', + 'db_ready.meow', + 'drop-collection' + ); + }); + }); + + describe('view actions', function () { + it('should activate callback with `duplicate-view` when corresponding action is clicked', async function () { + const spy = Sinon.spy(); + await renderConnectionsNavigationTree({ + expanded: { connection_ready: { db_ready: true } }, + activeWorkspace: { + ...props.activeWorkspace, + namespace: 'db_ready.bwok', + } as WorkspaceTab, + onNamespaceAction: spy, + }); + + const view = screen.getByTestId('sidebar-collection-db_ready.bwok'); + + userEvent.click(within(view).getByTitle('Show actions')); + userEvent.click(screen.getByText('Duplicate view')); + + expect(spy).to.be.calledOnceWithExactly( + 'connection_ready', + 'db_ready.bwok', + 'duplicate-view' + ); + }); + + it('should activate callback with `modify-view` when corresponding action is clicked', async function () { + const spy = Sinon.spy(); + await renderConnectionsNavigationTree({ + expanded: { connection_ready: { db_ready: true } }, + activeWorkspace: { + ...props.activeWorkspace, + namespace: 'db_ready.bwok', + } as WorkspaceTab, + onNamespaceAction: spy, + }); + + const view = screen.getByTestId('sidebar-collection-db_ready.bwok'); + + userEvent.click(within(view).getByTitle('Show actions')); + userEvent.click(screen.getByText('Modify view')); + + expect(spy).to.be.calledOnceWithExactly( + 'connection_ready', + 'db_ready.bwok', + 'modify-view' + ); + }); + }); + }); +}); diff --git a/packages/compass-connections-navigation/src/connections-navigation-tree.tsx b/packages/compass-connections-navigation/src/connections-navigation-tree.tsx new file mode 100644 index 00000000000..a26493270aa --- /dev/null +++ b/packages/compass-connections-navigation/src/connections-navigation-tree.tsx @@ -0,0 +1,602 @@ +import React, { useCallback, useMemo, memo, useRef } from 'react'; +import { FixedSizeList as List, areEqual } from 'react-window'; +import AutoSizer from 'react-virtualized-auto-sizer'; +import { + FadeInPlaceholder, + css, + useId, + VisuallyHidden, + spacing, +} from '@mongodb-js/compass-components'; +import { PlaceholderItem } from './placeholder-item'; +import { + MAX_COLLECTION_PLACEHOLDER_ITEMS, + MAX_DATABASE_PLACEHOLDER_ITEMS, + MIN_DATABASE_PLACEHOLDER_ITEMS, + ROW_HEIGHT, +} from './constants'; +import { DatabaseItem } from './database-item'; +import { CollectionItem } from './collection-item'; +import type { Actions } from './constants'; +import { useVirtualNavigationTree } from './use-virtual-navigation-tree'; +import type { NavigationTreeData } from './use-virtual-navigation-tree'; +import { TopPlaceholder } from './top-placeholder'; +import { ConnectionItem } from './connection-item'; +import { type ConnectionInfo } from '@mongodb-js/connection-info'; +import StyledNavigationItem from './styled-navigation-item'; +import { usePreference } from 'compass-preferences-model/provider'; +import { type WorkspaceTab } from '@mongodb-js/compass-workspaces'; + +type Collection = { + _id: string; + name: string; + type: string; +}; + +type Status = 'initial' | 'fetching' | 'refreshing' | 'ready' | 'error'; + +type Database = { + _id: string; + name: string; + collectionsStatus: Status; + collectionsLength: number; + collections: Collection[]; +}; + +export type Connection = { + connectionInfo: ConnectionInfo; + name: string; + databasesStatus: Status; + databasesLength: number; + databases: Database[]; + isReady: boolean; + isDataLake: boolean; + isWritable: boolean; +}; + +type PlaceholderTreeItem = { + key: string; + type: 'placeholder'; + level: number; + id?: string; + colorCode?: string; +}; + +type ConnectionTreeItem = { + key: string; + type: 'connection'; + level: number; + id: string; + name: string; + isExpanded: boolean; + setSize: number; + posInSet: number; + colorCode?: string; + connectionInfo: ConnectionInfo; +}; + +type DatabaseTreeItem = { + connectionId: string; + key: string; + type: 'database'; + level: number; + id: string; + name: string; + isExpanded: boolean; + setSize: number; + posInSet: number; + colorCode?: string; +}; + +type CollectionTreeItem = { + connectionId: string; + key: string; + type: 'collection' | 'view' | 'timeseries'; + level: number; + id: string; + name: string; + setSize: number; + posInSet: number; + colorCode?: string; +}; + +export type TreeItem = + | PlaceholderTreeItem + | ConnectionTreeItem + | DatabaseTreeItem + | CollectionTreeItem; + +type ListItemData = { + items: TreeItem[]; + isReadOnly: boolean; + isSingleConnection?: boolean; + activeWorkspace?: WorkspaceTab; + currentTabbable?: string; + onConnectionExpand(this: void, id: string, isExpanded: boolean): void; + onConnectionSelect(this: void, id: string): void; + onDatabaseExpand( + this: void, + connectionId: string, + id: string, + isExpanded: boolean + ): void; + onNamespaceAction( + this: void, + connectionId: string, + namespace: string, + action: Actions + ): void; +}; + +const collectionItemContainer = css({ + position: 'relative', +}); + +const connectionToItems = ({ + connection: { + connectionInfo, + name, + databases, + databasesStatus, + databasesLength, + }, + connectionIndex, + connectionsLength, + expanded, +}: { + connection: Connection; + connectionIndex: number; + connectionsLength: number; + expanded?: Record>; +}): TreeItem[] => { + const isExpanded = !!(expanded && expanded[connectionInfo.id]); + + const areDatabasesReady = ['ready', 'refreshing', 'error'].includes( + databasesStatus + ); + + const placeholdersLength = Math.max( + Math.min(databasesLength, MAX_DATABASE_PLACEHOLDER_ITEMS), + // we are connecting and we don't have metadata on how many databases are in this cluster + MIN_DATABASE_PLACEHOLDER_ITEMS + ); + + const colorCode = connectionInfo.favorite?.color; + + const connectionTI: ConnectionTreeItem = { + key: String(connectionIndex), + level: 1, + id: connectionInfo.id, + name, + type: 'connection' as const, + isExpanded, + setSize: connectionsLength, + posInSet: connectionIndex + 1, + connectionInfo, + colorCode, + }; + + if (!isExpanded) { + return [connectionTI]; + } + + return ([connectionTI] as TreeItem[]).concat( + areDatabasesReady + ? databases.flatMap((database, databaseIndex) => { + const dbExpanded = expanded?.[connectionInfo.id] || {}; + return databaseToItems({ + connectionId: connectionInfo.id, + database, + connectionIndex, + databaseIndex, + databasesLength: databases.length, + expanded: dbExpanded, + level: 2, + colorCode, + }); + }) + : Array.from({ length: placeholdersLength }, (_, index) => ({ + key: `${connectionIndex}-${index}`, + level: 2, + type: 'placeholder' as const, + colorCode, + })) + ); +}; + +const databaseToItems = ({ + database: { + _id: id, + name, + collections, + collectionsLength, + collectionsStatus, + }, + connectionId, + connectionIndex, + databaseIndex, + databasesLength, + expanded, + level, + colorCode, +}: { + database: Database; + connectionId: string; + connectionIndex?: number; + databaseIndex: number; + databasesLength: number; + expanded?: Record; + level: number; + colorCode?: string; +}): TreeItem[] => { + const isExpanded = expanded ? expanded[id] : false; + const isInConnection = typeof connectionIndex !== undefined; + + const databaseTI: DatabaseTreeItem = { + connectionId, + key: isInConnection + ? `${connectionIndex as number}-${databaseIndex}` + : `${databaseIndex}`, + level, + id, + name, + type: 'database' as const, + isExpanded, + setSize: databasesLength, + posInSet: databaseIndex + 1, + colorCode, + }; + + if (!isExpanded) { + return [databaseTI]; + } + + const areCollectionsReady = ['ready', 'refreshing', 'error'].includes( + collectionsStatus + ); + + const placeholdersLength = Math.min( + collectionsLength, + MAX_COLLECTION_PLACEHOLDER_ITEMS + ); + + return ([databaseTI] as TreeItem[]).concat( + areCollectionsReady + ? collections.map(({ _id: id, name, type }, index) => ({ + connectionId, + key: isInConnection + ? `${connectionIndex as number}-${databaseIndex}-${index}` + : `${databaseIndex}-${index}`, + level: level + 1, + id, + name, + type: type as 'collection' | 'view' | 'timeseries', + setSize: collections.length, + posInSet: index + 1, + colorCode, + })) + : Array.from({ length: placeholdersLength }, (_, index) => ({ + key: isInConnection + ? `${connectionIndex as number}-${databaseIndex}-${index}` + : `${databaseIndex}-${index}`, + level: level + 1, + type: 'placeholder' as const, + colorCode, + })) + ); +}; + +const NavigationItem = memo<{ + index: number; + style: React.CSSProperties; + data: ListItemData; +}>(function NavigationItem({ index, style, data }) { + const { + items, + isSingleConnection, + isReadOnly, + activeWorkspace, + currentTabbable, + onConnectionExpand, + onConnectionSelect, + onDatabaseExpand, + onNamespaceAction, + } = data; + + const itemData = items[index]; + + let Item: React.ReactElement; + + if (itemData.type === 'connection') { + Item = ( + + ); + } else if (itemData.type === 'database') { + Item = ( + + ); + } else { + Item = ( +
+ { + return ( + itemData.type !== 'placeholder' && ( + + ) + ); + }} + fallback={() => ( + + )} + > +
+ ); + } + + return ( + + {Item} + + ); +}, areEqual); + +const navigationTree = css({ + flex: '1 0 auto', +}); + +interface ConnectionsNavigationTreeProps { + connections: Connection[]; + expanded?: Record>; + onConnectionExpand?(id: string, isExpanded: boolean): void; + onConnectionSelect?(id: string): void; + onDatabaseExpand(connectionId: string, id: string, isExpanded: boolean): void; + onNamespaceAction( + connectionId: string, + namespace: string, + action: Actions + ): void; + activeWorkspace?: WorkspaceTab; + isReadOnly?: boolean; +} + +const ConnectionsNavigationTree: React.FunctionComponent< + ConnectionsNavigationTreeProps +> = ({ + connections, + expanded, + activeWorkspace, + // onConnectionExpand and onConnectionSelect only has a default to support single-connection usage + // eslint-disable-next-line @typescript-eslint/no-empty-function + onConnectionExpand = () => {}, + // eslint-disable-next-line @typescript-eslint/no-empty-function + onConnectionSelect = () => {}, + onDatabaseExpand, + onNamespaceAction, + isReadOnly = false, +}) => { + const isSingleConnection = !usePreference( + 'enableNewMultipleConnectionSystem' + ); + + const listRef = useRef(null); + const id = useId(); + + const items: TreeItem[] = useMemo(() => { + if (!isSingleConnection) { + return connections.flatMap((connection, connectionIndex) => + connectionToItems({ + connection, + connectionIndex, + connectionsLength: connections.length, + expanded, + }) + ); + } else { + const connection = connections[0]; + return connection.databases.flatMap((database, databaseIndex) => { + let isExpanded: undefined | Record = undefined; + if (expanded) { + isExpanded = expanded[connection.connectionInfo.id] || {}; + } + + return databaseToItems({ + connectionId: connection.connectionInfo.id, + database, + databaseIndex, + databasesLength: connection.databases.length || 0, + expanded: isExpanded, + level: 1, + }); + }); + } + }, [isSingleConnection, connections, expanded]); + + const onExpandedChange = useCallback( + ({ id, type, connectionId }, isExpanded: boolean) => { + if (type === 'database') onDatabaseExpand(connectionId, id, isExpanded); + if (type === 'connection') onConnectionExpand(id, isExpanded); + }, + [onDatabaseExpand, onConnectionExpand] + ); + + const onFocusMove = useCallback( + (item) => { + const idx = items.indexOf(item); + if (idx >= 0) { + // It is possible that the item we are trying to move the focus to is + // not rendered currently. Scroll it into view so that it's rendered and + // can be focused + listRef.current?.scrollToItem(idx); + } + }, + [items] + ); + + const [rootProps, currentTabbable] = useVirtualNavigationTree( + { + items: items as NavigationTreeData, + activeItemId: + (activeWorkspace as { namespace?: string })?.namespace || '', // TODO(COMPASS-7887) + onExpandedChange, + onFocusMove, + } + ); + + const itemData: ListItemData = useMemo(() => { + return { + items, + isReadOnly, + isSingleConnection, + activeWorkspace, + currentTabbable, + onNamespaceAction, + onConnectionExpand, + onConnectionSelect, + onDatabaseExpand, + }; + }, [ + items, + isReadOnly, + isSingleConnection, + activeWorkspace, + currentTabbable, + onNamespaceAction, + onConnectionExpand, + onConnectionSelect, + onDatabaseExpand, + ]); + + const getItemKey = useCallback((index: number, data: typeof itemData) => { + return data.items[index].key; + }, []); + + const isTestEnv = process.env.NODE_ENV === 'test'; + + return ( + <> + Databases and Collections +
+ + {({ + width = isTestEnv ? 1024 : '', + height = isTestEnv ? 768 : '', + }) => ( + + {NavigationItem} + + )} + +
+ + ); +}; + +const MCContainer = css({ + display: 'flex', + flex: '1 0 auto', + height: `calc(100% - ${spacing[3]}px)`, +}); + +const SCContainer = css({ + display: 'flex', + flex: '1 0 auto', + height: 0, +}); + +const contentContainer = css({ + display: 'flex', + flex: '1 0 auto', +}); + +const NavigationWithPlaceholder: React.FunctionComponent< + { isReady: boolean } & React.ComponentProps +> = ({ isReady, ...props }) => { + const isSingleConnection = !usePreference( + 'enableNewMultipleConnectionSystem' + ); + + return ( + { + return ( + + ); + }} + fallback={() => { + return ( + + ); + }} + > + ); +}; + +export { NavigationWithPlaceholder, ConnectionsNavigationTree }; diff --git a/packages/compass-connections-navigation/src/constants.tsx b/packages/compass-connections-navigation/src/constants.tsx new file mode 100644 index 00000000000..2bef93486f5 --- /dev/null +++ b/packages/compass-connections-navigation/src/constants.tsx @@ -0,0 +1,26 @@ +import { spacing } from '@mongodb-js/compass-components'; + +// TODO: Currently we show placeholder for every collection/database item in the list, but +// do we want to / need to? +export const MAX_COLLECTION_PLACEHOLDER_ITEMS = Infinity; +export const MAX_DATABASE_PLACEHOLDER_ITEMS = Infinity; +export const MIN_DATABASE_PLACEHOLDER_ITEMS = 5; +export const ROW_HEIGHT = spacing[5]; +// export const COLLETIONS_MARGIN_BOTTOM = spacing[1]; + +export type Actions = + | 'connection-performance-metrics' + | 'open-connection-info' + | 'copy-connection-string' + | 'connection-toggle-favorite' + | 'connection-disconnect' + | 'select-connection' + | 'select-database' + | 'drop-database' + | 'select-collection' + | 'create-collection' + | 'drop-collection' + | 'open-in-new-tab' + | 'duplicate-view' + | 'modify-view' + | 'rename-collection'; diff --git a/packages/compass-databases-navigation/src/database-item.tsx b/packages/compass-connections-navigation/src/database-item.tsx similarity index 61% rename from packages/compass-databases-navigation/src/database-item.tsx rename to packages/compass-connections-navigation/src/database-item.tsx index b3282246b96..176e096df20 100644 --- a/packages/compass-databases-navigation/src/database-item.tsx +++ b/packages/compass-connections-navigation/src/database-item.tsx @@ -1,20 +1,19 @@ -/* eslint-disable react/prop-types */ import React, { useCallback, useMemo } from 'react'; import { useHoverState, spacing, css, - cx, ItemActionControls, Icon, } from '@mongodb-js/compass-components'; import type { ItemAction } from '@mongodb-js/compass-components'; -import { DATABASE_ROW_HEIGHT } from './constants'; +import { ROW_HEIGHT } from './constants'; import { ItemContainer, ItemLabel, ItemWrapper, ItemButtonWrapper, + ExpandButton, } from './tree-item'; import type { VirtualListItemProps, @@ -22,60 +21,15 @@ import type { NamespaceItemProps, } from './tree-item'; import type { Actions } from './constants'; - -const buttonReset = css({ - padding: 0, - margin: 0, - background: 'none', - border: 'none', -}); - -const expandButton = css({ - display: 'flex', - // Not using leafygreen spacing here because none of them allow to align the - // button with the search bar content. This probably can go away when we are - // rebuilding the search also - padding: 7, - transition: 'transform .16s linear', - transform: 'rotate(0deg)', - '&:hover': { - cursor: 'pointer', - }, -}); - -const expanded = css({ - transform: 'rotate(90deg)', -}); - -const ExpandButton: React.FunctionComponent<{ - onClick: React.MouseEventHandler; - isExpanded: boolean; -}> = ({ onClick, isExpanded }) => { - return ( - - ); -}; +import { getItemPaddingStyles } from './utils'; const databaseItem = css({ - height: DATABASE_ROW_HEIGHT, + height: ROW_HEIGHT, }); const itemButtonWrapper = css({ - height: DATABASE_ROW_HEIGHT, + height: ROW_HEIGHT, paddingRight: spacing[1], - paddingLeft: spacing[2], }); const databaseItemLabel = css({ @@ -87,16 +41,23 @@ export const DatabaseItem: React.FunctionComponent< TreeItemProps & NamespaceItemProps & { isExpanded: boolean; - onDatabaseExpand(id: string, isExpanded: boolean): void; + onDatabaseExpand( + connectionId: string, + id: string, + isExpanded: boolean + ): void; } > = ({ + connectionId, id, name, + level, posInSet, setSize, isExpanded, isActive, isReadOnly, + isSingleConnection, isTabbable, style, onNamespaceAction, @@ -104,29 +65,35 @@ export const DatabaseItem: React.FunctionComponent< }) => { const [hoverProps, isHovered] = useHoverState(); + const itemPaddingStyles = useMemo( + () => getItemPaddingStyles({ level, isSingleConnection }), + [level, isSingleConnection] + ); + const onExpandButtonClick = useCallback( (evt: React.MouseEvent) => { evt.stopPropagation(); - onDatabaseExpand(id, !isExpanded); + onDatabaseExpand(connectionId, id, !isExpanded); }, - [onDatabaseExpand, id, isExpanded] + [onDatabaseExpand, connectionId, id, isExpanded] ); const onDefaultAction = useCallback( (evt) => { onNamespaceAction( + connectionId, evt.currentTarget.dataset.id as string, 'select-database' ); }, - [onNamespaceAction] + [connectionId, onNamespaceAction] ); const onAction = useCallback( (action: Actions) => { - onNamespaceAction(id, action); + onNamespaceAction(connectionId, id, action); }, - [id, onNamespaceAction] + [connectionId, id, onNamespaceAction] ); const actions: ItemAction[] = useMemo(() => { @@ -148,7 +115,7 @@ export const DatabaseItem: React.FunctionComponent< - + = ({ level, style, isSingleConnection }) => { + const itemPaddingStyles = useMemo( + () => + getItemPaddingStyles({ level, isPlaceholder: true, isSingleConnection }), + [level, isSingleConnection] + ); + + return ( +
+ +
+ ); +}; diff --git a/packages/compass-databases-navigation/src/databases-navigation-tree.spec.tsx b/packages/compass-connections-navigation/src/sc-connections-navigation-tree.spec.tsx similarity index 59% rename from packages/compass-databases-navigation/src/databases-navigation-tree.spec.tsx rename to packages/compass-connections-navigation/src/sc-connections-navigation-tree.spec.tsx index fa9f4daa8cd..7b05088529c 100644 --- a/packages/compass-databases-navigation/src/databases-navigation-tree.spec.tsx +++ b/packages/compass-connections-navigation/src/sc-connections-navigation-tree.spec.tsx @@ -10,29 +10,44 @@ import { import userEvent from '@testing-library/user-event'; import { expect } from 'chai'; import Sinon from 'sinon'; -import { DatabasesNavigationTree } from './databases-navigation-tree'; +import { + type Connection, + ConnectionsNavigationTree, +} from './connections-navigation-tree'; import type { PreferencesAccess } from 'compass-preferences-model'; import { createSandboxFromDefaultPreferences } from 'compass-preferences-model'; import { PreferencesProvider } from 'compass-preferences-model/provider'; +import type { WorkspaceTab } from '@mongodb-js/compass-workspaces'; -const databases = [ - { - _id: 'foo', - name: 'foo', - collectionsStatus: 'initial', - collectionsLength: 5, - collections: [], - }, +const connections: Connection[] = [ { - _id: 'bar', - name: 'bar', - collectionsStatus: 'ready', - collectionsLength: 3, - collections: [ - { _id: 'bar.meow', name: 'meow', type: 'collection' }, - { _id: 'bar.woof', name: 'woof', type: 'timeseries' }, - { _id: 'bar.bwok', name: 'bwok', type: 'view' }, + connectionInfo: { id: 'connectionId' } as any, + databases: [ + { + _id: 'foo', + name: 'foo', + collectionsStatus: 'initial', + collectionsLength: 5, + collections: [], + }, + { + _id: 'bar', + name: 'bar', + collectionsStatus: 'ready', + collectionsLength: 3, + collections: [ + { _id: 'bar.meow', name: 'meow', type: 'collection' }, + { _id: 'bar.woof', name: 'woof', type: 'timeseries' }, + { _id: 'bar.bwok', name: 'bwok', type: 'view' }, + ], + }, ], + databasesLength: 2, + databasesStatus: 'ready', + isDataLake: false, + isReady: true, + isWritable: true, + name: 'test', }, ]; @@ -42,23 +57,32 @@ const TEST_VIRTUAL_PROPS = { __TEST_REACT_WINDOW_OVERSCAN: Infinity, }; -describe('DatabasesNavigationTree', function () { +const activeWorkspace = { + connectionId: 'connectionId', + namespace: 'bar.meow', + type: 'Collection', +}; + +describe('ConnectionsNavigationTree -- Single connection usage', function () { afterEach(cleanup); context('when the rename collection feature flag is enabled', () => { let preferences: PreferencesAccess; beforeEach(async function () { preferences = await createSandboxFromDefaultPreferences(); - await preferences.savePreferences({ enableRenameCollectionModal: true }); + await preferences.savePreferences({ + enableRenameCollectionModal: true, + enableNewMultipleConnectionSystem: false, + }); }); it('shows the Rename Collection action', function () { render( - {}} onNamespaceAction={() => {}} {...TEST_VIRTUAL_PROPS} @@ -80,10 +104,10 @@ describe('DatabasesNavigationTree', function () { const spy = Sinon.spy(); render( - {}} {...TEST_VIRTUAL_PROPS} @@ -96,18 +120,22 @@ describe('DatabasesNavigationTree', function () { userEvent.click(within(collection).getByTitle('Show actions')); userEvent.click(screen.getByText('Rename collection')); - expect(spy).to.be.calledOnceWithExactly('bar.meow', 'rename-collection'); + expect(spy).to.be.calledOnceWithExactly( + 'connectionId', + 'bar.meow', + 'rename-collection' + ); }); }); it('should render databases', function () { render( - {}} onNamespaceAction={() => {}} {...TEST_VIRTUAL_PROPS} - > + > ); expect(screen.getByText('foo')).to.exist; @@ -116,13 +144,13 @@ describe('DatabasesNavigationTree', function () { it('should render collections when database is expanded', function () { render( - {}} onNamespaceAction={() => {}} {...TEST_VIRTUAL_PROPS} - > + > ); expect(screen.getByText('meow')).to.exist; @@ -132,13 +160,13 @@ describe('DatabasesNavigationTree', function () { it('should render collection placeholders when database is expanded but collections are not ready', function () { render( - {}} onNamespaceAction={() => {}} {...TEST_VIRTUAL_PROPS} - > + > ); expect(screen.getAllByTestId('placeholder')).to.have.lengthOf(5); @@ -146,13 +174,19 @@ describe('DatabasesNavigationTree', function () { it('should make current active namespace tabbable', async function () { render( - {}} onNamespaceAction={() => {}} {...TEST_VIRTUAL_PROPS} - > + > ); userEvent.tab(); @@ -171,12 +205,12 @@ describe('DatabasesNavigationTree', function () { describe('when isReadOnly is false or undefined', function () { it('should show all database actions on hover', function () { render( - {}} onNamespaceAction={() => {}} {...TEST_VIRTUAL_PROPS} - > + > ); userEvent.hover(screen.getByText('foo')); @@ -189,13 +223,19 @@ describe('DatabasesNavigationTree', function () { it('should show all database actions for active namespace', function () { render( - {}} onNamespaceAction={() => {}} {...TEST_VIRTUAL_PROPS} - > + > ); const database = screen.getByTestId('sidebar-database-bar'); @@ -206,14 +246,14 @@ describe('DatabasesNavigationTree', function () { it('should show all collection actions', function () { render( - {}} onNamespaceAction={() => {}} {...TEST_VIRTUAL_PROPS} - > + > ); const collection = screen.getByTestId('sidebar-collection-bar.meow'); @@ -230,14 +270,19 @@ describe('DatabasesNavigationTree', function () { it('should show all view actions', function () { render( - {}} onNamespaceAction={() => {}} {...TEST_VIRTUAL_PROPS} - > + > ); const collection = screen.getByTestId('sidebar-collection-bar.bwok'); @@ -260,14 +305,20 @@ describe('DatabasesNavigationTree', function () { describe('when isReadOnly is true', function () { it('should not show database actions', function () { render( - {}} onNamespaceAction={() => {}} isReadOnly {...TEST_VIRTUAL_PROPS} - > + > ); const database = screen.getByTestId('sidebar-database-bar'); @@ -278,15 +329,20 @@ describe('DatabasesNavigationTree', function () { it('should show only one collection action', function () { render( - {}} onNamespaceAction={() => {}} isReadOnly {...TEST_VIRTUAL_PROPS} - > + > ); const collection = screen.getByTestId('sidebar-collection-bar.bwok'); @@ -299,69 +355,97 @@ describe('DatabasesNavigationTree', function () { it('should activate callback with `select-database` when database is clicked', function () { const spy = Sinon.spy(); render( - {}} {...TEST_VIRTUAL_PROPS} - > + > ); userEvent.click(screen.getByText('foo')); - expect(spy).to.be.calledOnceWithExactly('foo', 'select-database'); + expect(spy).to.be.calledOnceWithExactly( + 'connectionId', + 'foo', + 'select-database' + ); }); it('should activate callback with `select-collection` when collection is clicked', function () { const spy = Sinon.spy(); render( - {}} {...TEST_VIRTUAL_PROPS} - > + > ); userEvent.click(screen.getByText('meow')); - expect(spy).to.be.calledOnceWithExactly('bar.meow', 'select-collection'); + expect(spy).to.be.calledOnceWithExactly( + 'connectionId', + 'bar.meow', + 'select-collection' + ); }); describe('database actions', function () { it('should activate callback with `drop-database` when corresponding action is clicked', function () { const spy = Sinon.spy(); render( - {}} {...TEST_VIRTUAL_PROPS} - > + > ); userEvent.click(screen.getByTitle('Drop database')); - expect(spy).to.be.calledOnceWithExactly('foo', 'drop-database'); + expect(spy).to.be.calledOnceWithExactly( + 'connectionId', + 'foo', + 'drop-database' + ); }); it('should activate callback with `create-collection` when corresponding action is clicked', function () { const spy = Sinon.spy(); render( - {}} {...TEST_VIRTUAL_PROPS} - > + > ); userEvent.click(screen.getByTitle('Create collection')); - expect(spy).to.be.calledOnceWithExactly('foo', 'create-collection'); + expect(spy).to.be.calledOnceWithExactly( + 'connectionId', + 'foo', + 'create-collection' + ); }); }); @@ -369,14 +453,14 @@ describe('DatabasesNavigationTree', function () { it('should activate callback with `open-in-new-tab` when corresponding action is clicked', function () { const spy = Sinon.spy(); render( - {}} {...TEST_VIRTUAL_PROPS} - > + > ); const collection = screen.getByTestId('sidebar-collection-bar.meow'); @@ -384,20 +468,24 @@ describe('DatabasesNavigationTree', function () { userEvent.click(within(collection).getByTitle('Show actions')); userEvent.click(screen.getByText('Open in new tab')); - expect(spy).to.be.calledOnceWithExactly('bar.meow', 'open-in-new-tab'); + expect(spy).to.be.calledOnceWithExactly( + 'connectionId', + 'bar.meow', + 'open-in-new-tab' + ); }); it('should activate callback with `drop-collection` when corresponding action is clicked', function () { const spy = Sinon.spy(); render( - {}} {...TEST_VIRTUAL_PROPS} - > + > ); const collection = screen.getByTestId('sidebar-collection-bar.meow'); @@ -405,7 +493,11 @@ describe('DatabasesNavigationTree', function () { userEvent.click(within(collection).getByTitle('Show actions')); userEvent.click(screen.getByText('Drop collection')); - expect(spy).to.be.calledOnceWithExactly('bar.meow', 'drop-collection'); + expect(spy).to.be.calledOnceWithExactly( + 'connectionId', + 'bar.meow', + 'drop-collection' + ); }); }); @@ -414,14 +506,19 @@ describe('DatabasesNavigationTree', function () { const spy = Sinon.spy(); render( - {}} {...TEST_VIRTUAL_PROPS} - > + > ); const view = screen.getByTestId('sidebar-collection-bar.bwok'); @@ -429,21 +526,30 @@ describe('DatabasesNavigationTree', function () { userEvent.click(within(view).getByTitle('Show actions')); userEvent.click(screen.getByText('Duplicate view')); - expect(spy).to.be.calledOnceWithExactly('bar.bwok', 'duplicate-view'); + expect(spy).to.be.calledOnceWithExactly( + 'connectionId', + 'bar.bwok', + 'duplicate-view' + ); }); it('should activate callback with `modify-view` when corresponding action is clicked', function () { const spy = Sinon.spy(); render( - {}} {...TEST_VIRTUAL_PROPS} - > + > ); const view = screen.getByTestId('sidebar-collection-bar.bwok'); @@ -451,7 +557,11 @@ describe('DatabasesNavigationTree', function () { userEvent.click(within(view).getByTitle('Show actions')); userEvent.click(screen.getByText('Modify view')); - expect(spy).to.be.calledOnceWithExactly('bar.bwok', 'modify-view'); + expect(spy).to.be.calledOnceWithExactly( + 'connectionId', + 'bar.bwok', + 'modify-view' + ); }); }); }); diff --git a/packages/compass-connections-navigation/src/styled-navigation-item.tsx b/packages/compass-connections-navigation/src/styled-navigation-item.tsx new file mode 100644 index 00000000000..a599dd25106 --- /dev/null +++ b/packages/compass-connections-navigation/src/styled-navigation-item.tsx @@ -0,0 +1,46 @@ +import React, { useMemo } from 'react'; +import { + useConnectionColor, + DefaultColorCode, +} from '@mongodb-js/connection-form'; + +export default function StyledNavigationItem({ + colorCode, + isSingleConnection, + children, +}: { + colorCode?: string; + isSingleConnection: boolean; + children: React.ReactChild; +}): React.ReactElement { + const { connectionColorToHex, connectionColorToHexActive } = + useConnectionColor(); + + const style: React.CSSProperties & { + '--item-bg-color'?: string; + '--item-bg-color-hover'?: string; + '--item-bg-color-active'?: string; + } = useMemo(() => { + const style: { + '--item-bg-color'?: string; + '--item-bg-color-hover'?: string; + '--item-bg-color-active'?: string; + } = {}; + + if (!isSingleConnection) { + if (colorCode && colorCode !== DefaultColorCode) { + style['--item-bg-color'] = connectionColorToHex(colorCode); + style['--item-bg-color-hover'] = connectionColorToHexActive(colorCode); + style['--item-bg-color-active'] = connectionColorToHexActive(colorCode); + } + } + return style; + }, [ + isSingleConnection, + colorCode, + connectionColorToHex, + connectionColorToHexActive, + ]); + + return
{children}
; +} diff --git a/packages/compass-databases-navigation/src/databases-placeholder.tsx b/packages/compass-connections-navigation/src/top-placeholder.tsx similarity index 58% rename from packages/compass-databases-navigation/src/databases-placeholder.tsx rename to packages/compass-connections-navigation/src/top-placeholder.tsx index 325d5117247..cb81dd49855 100644 --- a/packages/compass-databases-navigation/src/databases-placeholder.tsx +++ b/packages/compass-connections-navigation/src/top-placeholder.tsx @@ -6,11 +6,17 @@ const placeholderList = css({ maskImage: 'linear-gradient(to bottom, black 30%, transparent 95%)', }); -export const DatabasesPlaceholder: React.FunctionComponent = () => { +export const TopPlaceholder: React.FunctionComponent<{ + isSingleConnection?: boolean; +}> = ({ isSingleConnection }) => { const items = useMemo(() => { return Array.from({ length: 10 }, (_, idx) => ( - + )); - }, []); + }, [isSingleConnection]); return
{items}
; }; diff --git a/packages/compass-databases-navigation/src/tree-item.tsx b/packages/compass-connections-navigation/src/tree-item.tsx similarity index 63% rename from packages/compass-databases-navigation/src/tree-item.tsx rename to packages/compass-connections-navigation/src/tree-item.tsx index 7c103b18c84..72356e94918 100644 --- a/packages/compass-databases-navigation/src/tree-item.tsx +++ b/packages/compass-connections-navigation/src/tree-item.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react/prop-types */ import React, { useCallback } from 'react'; import type { CSSProperties } from 'react'; import { @@ -7,8 +6,34 @@ import { cx, mergeProps, spacing, + Icon, } from '@mongodb-js/compass-components'; import type { Actions } from './constants'; +import { usePreference } from 'compass-preferences-model/provider'; + +const buttonReset = css({ + padding: 0, + margin: 0, + background: 'none', + border: 'none', +}); + +const expandButton = css({ + display: 'flex', + // Not using leafygreen spacing here because none of them allow to align the + // button with the search bar content. This probably can go away when we are + // rebuilding the search also + padding: 7, + transition: 'transform .16s linear', + transform: 'rotate(0deg)', + '&:hover': { + cursor: 'pointer', + }, +}); + +const expanded = css({ + transform: 'rotate(90deg)', +}); export type VirtualListItemProps = { style?: CSSProperties; @@ -16,17 +41,45 @@ export type VirtualListItemProps = { export type TreeItemProps = { id: string; + level: number; posInSet: number; setSize: number; isTabbable: boolean; }; export type NamespaceItemProps = { + connectionId: string; name: string; type: string; isActive: boolean; isReadOnly: boolean; - onNamespaceAction(namespace: string, action: Actions): void; + isSingleConnection?: boolean; + onNamespaceAction( + connectionId: string, + namespace: string, + action: Actions + ): void; +}; + +export const ExpandButton: React.FunctionComponent<{ + onClick: React.MouseEventHandler; + isExpanded: boolean; +}> = ({ onClick, isExpanded }) => { + return ( + + ); }; export function useDefaultAction( @@ -61,6 +114,12 @@ export function useDefaultAction( const itemContainer = css({ cursor: 'pointer', color: 'var(--item-color)', + backgroundColor: 'var(--item-bg-color)', + backgroundRadius: 'var(--item-bg-radius)', + + '.item-background': { + backgroundColor: 'var(--item-bg-color)', + }, '& .item-action-controls': { marginLeft: 'auto', @@ -72,6 +131,10 @@ const itemContainer = css({ backgroundColor: 'var(--item-bg-color-hover)', }, + '&:hover': { + backgroundColor: 'var(--item-bg-color-hover)', + }, + '& .item-action-controls:hover + .item-background': { display: 'none', }, @@ -83,13 +146,23 @@ const itemContainer = css({ const activeItemContainer = css({ color: 'var(--item-color-active)', + backgroundColor: 'var(--item-bg-color-active)', fontWeight: 'bold', - '.item-background, :hover .item-background': { + '&:hover': { + backgroundColor: 'var(--item-bg-color-active)', + }, +}); + +const legacyActiveItemContainer = css({ + color: 'var(--item-color-active)', + backgroundColor: 'var(--item-bg-color-active)', + fontWeight: 'bold', + + '&:hover': { backgroundColor: 'var(--item-bg-color-active)', }, - // this is copied from leafygreen's own navigation, hence the pixel values '::before': { zIndex: 1, backgroundColor: 'var(--item-color-active)', @@ -158,12 +231,19 @@ export const ItemContainer: React.FunctionComponent< className, ...props }) => { + const isMultipleConnection = usePreference( + 'enableNewMultipleConnectionSystem' + ); const focusRingProps = useFocusRing(); const defaultActionProps = useDefaultAction(onDefaultAction); const extraCSS = []; if (isActive) { - extraCSS.push(activeItemContainer); + if (isMultipleConnection) { + extraCSS.push(activeItemContainer); + } else { + extraCSS.push(legacyActiveItemContainer); + } } const treeItemProps = mergeProps( @@ -201,8 +281,12 @@ export const ItemWrapper: React.FunctionComponent< export const ItemButtonWrapper: React.FunctionComponent< React.HTMLProps -> = ({ className, children }) => { - return
{children}
; +> = ({ className, children, ...rest }) => { + return ( +
+ {children} +
+ ); }; export const ItemLabel: React.FunctionComponent< diff --git a/packages/compass-databases-navigation/src/use-virtual-navigation-tree.spec.tsx b/packages/compass-connections-navigation/src/use-virtual-navigation-tree.spec.tsx similarity index 99% rename from packages/compass-databases-navigation/src/use-virtual-navigation-tree.spec.tsx rename to packages/compass-connections-navigation/src/use-virtual-navigation-tree.spec.tsx index 3756c930f48..b8776d903cf 100644 --- a/packages/compass-databases-navigation/src/use-virtual-navigation-tree.spec.tsx +++ b/packages/compass-connections-navigation/src/use-virtual-navigation-tree.spec.tsx @@ -1,6 +1,5 @@ /* eslint-disable @typescript-eslint/ban-types */ /* eslint-disable @typescript-eslint/no-empty-function */ -/* eslint-disable react/prop-types */ import React, { useCallback, useMemo, useState } from 'react'; import { render, screen, cleanup } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; diff --git a/packages/compass-databases-navigation/src/use-virtual-navigation-tree.tsx b/packages/compass-connections-navigation/src/use-virtual-navigation-tree.tsx similarity index 100% rename from packages/compass-databases-navigation/src/use-virtual-navigation-tree.tsx rename to packages/compass-connections-navigation/src/use-virtual-navigation-tree.tsx diff --git a/packages/compass-connections-navigation/src/utils.ts b/packages/compass-connections-navigation/src/utils.ts new file mode 100644 index 00000000000..00f9cca863a --- /dev/null +++ b/packages/compass-connections-navigation/src/utils.ts @@ -0,0 +1,44 @@ +import { spacing } from '@mongodb-js/compass-components'; + +export const getItemPaddingStyles = ({ + level, + isPlaceholder, + isSingleConnection, +}: { + level: number; + isPlaceholder?: boolean; + isSingleConnection?: boolean; +}) => { + let paddingLeft = 0; + if (isSingleConnection) { + /** SC version */ + switch (level) { + case 1: + paddingLeft = spacing[2]; + break; + case 2: + paddingLeft = spacing[6]; + break; + } + + if (isPlaceholder && level === 1) { + paddingLeft += spacing[2]; + } + } else { + /** MC version */ + switch (level) { + case 2: + paddingLeft = spacing[3] + spacing[1] + spacing[50]; + break; + case 3: + paddingLeft = spacing[6] + spacing[2]; + break; + } + + if (isPlaceholder && (level === 1 || level === 2)) { + paddingLeft += spacing[2]; + } + } + + return { paddingLeft }; +}; diff --git a/packages/compass-databases-navigation/tsconfig-lint.json b/packages/compass-connections-navigation/tsconfig-lint.json similarity index 100% rename from packages/compass-databases-navigation/tsconfig-lint.json rename to packages/compass-connections-navigation/tsconfig-lint.json diff --git a/packages/compass-databases-navigation/tsconfig.json b/packages/compass-connections-navigation/tsconfig.json similarity index 100% rename from packages/compass-databases-navigation/tsconfig.json rename to packages/compass-connections-navigation/tsconfig.json diff --git a/packages/compass-connections/.eslintrc.js b/packages/compass-connections/.eslintrc.js index e4cf824b6ac..9c3ab95632f 100644 --- a/packages/compass-connections/.eslintrc.js +++ b/packages/compass-connections/.eslintrc.js @@ -1,3 +1,4 @@ +'use strict'; module.exports = { root: true, extends: ['@mongodb-js/eslint-config-compass'], diff --git a/packages/compass-connections/.mocharc.js b/packages/compass-connections/.mocharc.js index a7e53abc444..61091edba73 100644 --- a/packages/compass-connections/.mocharc.js +++ b/packages/compass-connections/.mocharc.js @@ -1 +1,2 @@ +'use strict'; module.exports = require('@mongodb-js/mocha-config-compass/compass-plugin'); diff --git a/packages/compass-connections/package.json b/packages/compass-connections/package.json index 1c83a7f76d2..50df061a925 100644 --- a/packages/compass-connections/package.json +++ b/packages/compass-connections/package.json @@ -13,7 +13,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "1.25.1", + "version": "1.27.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -51,28 +51,26 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "dependencies": { - "@mongodb-js/compass-components": "^1.22.1", + "@mongodb-js/compass-components": "^1.24.0", "bson": "^6.6.0", - "@mongodb-js/compass-connection-import-export": "^0.21.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-maybe-protect-connection-string": "^0.16.1", - "@mongodb-js/connection-form": "^1.23.1", - "@mongodb-js/connection-info": "^0.1.5", - "@mongodb-js/connection-storage": "^0.8.1", - "compass-preferences-model": "^2.18.1", - "hadron-app-registry": "^9.1.8", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-maybe-protect-connection-string": "^0.18.0", + "@mongodb-js/connection-form": "^1.25.0", + "@mongodb-js/connection-info": "^0.2.1", + "@mongodb-js/connection-storage": "^0.10.0", + "compass-preferences-model": "^2.20.0", + "hadron-app-registry": "^9.1.10", "lodash": "^4.17.21", "mongodb-build-info": "^1.7.0", "mongodb-connection-string-url": "^2.6.0", - "mongodb-data-service": "^22.18.1", - "react": "^17.0.2", - "uuid": "^8.2.0" + "mongodb-data-service": "^22.19.1", + "react": "^17.0.2" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/dom": "^8.11.1", "@testing-library/react": "^12.1.4", "@testing-library/react-hooks": "^7.0.2", diff --git a/packages/compass-connections/provider.js b/packages/compass-connections/provider.js index a9d6fd5cfc3..5b56677514c 100644 --- a/packages/compass-connections/provider.js +++ b/packages/compass-connections/provider.js @@ -1 +1,2 @@ +'use strict'; module.exports = require('./dist/provider'); diff --git a/packages/compass-connections/src/components/connection-list/connection-list.spec.tsx b/packages/compass-connections/src/components/connection-list/connection-list.spec.tsx index 8a0cf06acc0..5191deadb81 100644 --- a/packages/compass-connections/src/components/connection-list/connection-list.spec.tsx +++ b/packages/compass-connections/src/components/connection-list/connection-list.spec.tsx @@ -125,6 +125,36 @@ describe('ConnectionList Component', function () { ); expect(filter).to.not.exist; }); + + it('does not show connection import export option when there is no openConnectionImportExportModal prop', function () { + const connectionsHeader = screen.getByTestId( + 'favorite-connections-list-header' + ); + userEvent.hover(connectionsHeader); + expect(() => screen.getByTestId('favorites-menu-show-actions')).to.throw; + }); + + it('shows connection import export option when there is a openConnectionImportExportModal prop', function () { + cleanup(); + render( + true} + onDoubleClick={() => true} + openConnectionImportExportModal={() => {}} + /> + ); + const connectionsHeader = screen.getByTestId( + 'favorite-connections-list-header' + ); + userEvent.hover(connectionsHeader); + expect(() => screen.getByTestId('favorites-menu-show-actions')).to.not + .throw; + }); }); describe('with more than 10 favorite connections', function () { diff --git a/packages/compass-connections/src/components/connection-list/connection-list.tsx b/packages/compass-connections/src/components/connection-list/connection-list.tsx index 4d1b98c56c2..b6998cdc117 100644 --- a/packages/compass-connections/src/components/connection-list/connection-list.tsx +++ b/packages/compass-connections/src/components/connection-list/connection-list.tsx @@ -13,7 +13,7 @@ import { ItemActionControls, } from '@mongodb-js/compass-components'; import type { ItemAction } from '@mongodb-js/compass-components'; -import type { ConnectionInfo } from '@mongodb-js/connection-storage/renderer'; +import type { ConnectionInfo } from '@mongodb-js/connection-storage/provider'; import type AppRegistry from 'hadron-app-registry'; import Connection from './connection'; @@ -141,6 +141,20 @@ const favoriteActions: ItemAction[] = [ }, ]; +type ConnectionListProps = { + activeConnectionId?: string; + appRegistry: AppRegistry; + recentConnections: ConnectionInfo[]; + favoriteConnections: ConnectionInfo[]; + createNewConnection: () => void; + setActiveConnectionId: (connectionId: string) => void; + onDoubleClick: (connectionInfo: ConnectionInfo) => void; + removeAllRecentsConnections: () => void; + duplicateConnection: (connectionInfo: ConnectionInfo) => void; + removeConnection: (connectionInfo: ConnectionInfo) => void; + openConnectionImportExportModal?: (action: FavoriteAction) => void; +}; + function ConnectionList({ activeConnectionId, appRegistry, @@ -153,19 +167,7 @@ function ConnectionList({ duplicateConnection, removeConnection, openConnectionImportExportModal, -}: { - activeConnectionId?: string; - appRegistry: AppRegistry; - recentConnections: ConnectionInfo[]; - favoriteConnections: ConnectionInfo[]; - createNewConnection: () => void; - setActiveConnectionId: (connectionId: string) => void; - onDoubleClick: (connectionInfo: ConnectionInfo) => void; - removeAllRecentsConnections: () => void; - duplicateConnection: (connectionInfo: ConnectionInfo) => void; - removeConnection: (connectionInfo: ConnectionInfo) => void; - openConnectionImportExportModal: (modal: FavoriteAction) => void; -}): React.ReactElement { +}: ConnectionListProps): React.ReactElement { const darkMode = useDarkMode(); const [recentHoverProps, recentHeaderHover] = useHoverState(); const [favoriteHoverProps, favoriteHeaderHover] = useHoverState(); @@ -226,13 +228,15 @@ function ConnectionList({ > Saved connections - - data-testid="favorites-menu" - onAction={openConnectionImportExportModal} - iconSize="small" - actions={favoriteActions} - isVisible={favoriteHeaderHover} - > + {openConnectionImportExportModal && ( + + data-testid="favorites-menu" + onAction={openConnectionImportExportModal} + iconSize="small" + actions={favoriteActions} + isVisible={favoriteHeaderHover} + > + )}
{showFilteredSavedConnections && ( { - return Promise.resolve(mockConnections); - }, - getLegacyConnections: () => Promise.resolve([]), - save: () => Promise.resolve(), - delete: () => Promise.resolve(), - load: (id: string) => - Promise.resolve(mockConnections.find((conn) => conn.id === id)), - importConnections: () => Promise.resolve([]), - exportConnections: () => Promise.resolve('{}'), - deserializeConnections: () => Promise.resolve([]), - } as unknown as typeof ConnectionStorage; -} - async function loadSavedConnectionAndConnect(connectionInfo: ConnectionInfo) { const savedConnectionButton = screen.getByTestId( `saved-connection-button-${connectionInfo.id}` @@ -96,11 +78,11 @@ describe('Connections Component', function () { context('when rendered', function () { let loadConnectionsSpy: sinon.SinonSpy; beforeEach(function () { - const mockStorage = getMockConnectionStorage([]); + const mockStorage = new InMemoryConnectionStorage([]); loadConnectionsSpy = sinon.spy(mockStorage, 'loadAll'); render( - + - + ); }); @@ -157,15 +139,15 @@ describe('Connections Component', function () { context('when rendered with saved connections in storage', function () { let connectSpyFn: sinon.SinonSpy; - let mockStorage: typeof ConnectionStorage; + let mockStorage: ConnectionStorage; let savedConnectionId: string; let savedConnectionWithAppNameId: string; let saveConnectionSpy: sinon.SinonSpy; let connections: ConnectionInfo[]; beforeEach(async function () { - savedConnectionId = uuid(); - savedConnectionWithAppNameId = uuid(); + savedConnectionId = new UUID().toString(); + savedConnectionWithAppNameId = new UUID().toString(); saveConnectionSpy = sinon.spy(); connections = [ @@ -184,7 +166,7 @@ describe('Connections Component', function () { }, }, ]; - mockStorage = getMockConnectionStorage(connections); + mockStorage = new InMemoryConnectionStorage(connections); sinon.replace(mockStorage, 'save', saveConnectionSpy); const connectionsManager = getConnectionsManager(() => { @@ -197,7 +179,7 @@ describe('Connections Component', function () { render( - + - + ); @@ -331,8 +313,8 @@ describe('Connections Component', function () { beforeEach(async function () { saveConnectionSpy = sinon.spy(); - savedConnectableId = uuid(); - savedUnconnectableId = uuid(); + savedConnectableId = new UUID().toString(); + savedUnconnectableId = new UUID().toString(); mockConnectFn = sinon.fake( async ({ @@ -380,12 +362,12 @@ describe('Connections Component', function () { }, }, ]; - const mockStorage = getMockConnectionStorage(connections); + const mockStorage = new InMemoryConnectionStorage(connections); sinon.replace(mockStorage, 'save', saveConnectionSpy); render( - + - + ); @@ -509,95 +491,4 @@ describe('Connections Component', function () { }); } ); - - context('when user has any legacy connection', function () { - it('shows modal', async function () { - const mockStorage = getMockConnectionStorage([]); - sinon - .stub(mockStorage, 'getLegacyConnections') - .resolves([{ name: 'Connection1' }]); - - render( - - - - - - - - - - ); - - await waitFor( - () => expect(screen.getByTestId('legacy-connections-modal')).to.exist - ); - - const modal = screen.getByTestId('legacy-connections-modal'); - expect(within(modal).getByText('Connection1')).to.exist; - }); - - it('does not show modal when user hides it', async function () { - const mockStorage = getMockConnectionStorage([]); - sinon - .stub(mockStorage, 'getLegacyConnections') - .resolves([{ name: 'Connection2' }]); - - const { rerender } = render( - - - - - - - - - - ); - - await waitFor(() => screen.getByTestId('legacy-connections-modal')); - - const modal = screen.getByTestId('legacy-connections-modal'); - - const storageSpy = sinon.spy(Storage.prototype, 'setItem'); - - // Click the don't show again checkbox and close the modal - fireEvent.click(within(modal).getByText(/don't show this again/i)); - fireEvent.click(within(modal).getByText(/close/i)); - - rerender( - - - - - - - - - - ); - - // Saves data in storage - expect(storageSpy.firstCall.args).to.deep.equal([ - 'hide_legacy_connections_modal', - 'true', - ]); - - expect(() => { - screen.getByTestId('legacy-connections-modal'); - }).to.throw; - }); - }); }); diff --git a/packages/compass-connections/src/components/connections.tsx b/packages/compass-connections/src/components/connections.tsx index 40521c75234..2394a466ba5 100644 --- a/packages/compass-connections/src/components/connections.tsx +++ b/packages/compass-connections/src/components/connections.tsx @@ -8,24 +8,18 @@ import { spacing, useDarkMode, } from '@mongodb-js/compass-components'; -import { - ExportConnectionsModal, - ImportConnectionsModal, -} from '@mongodb-js/compass-connection-import-export'; import { useLoggerAndTelemetry } from '@mongodb-js/compass-logging/provider'; import ConnectionForm from '@mongodb-js/connection-form'; -import { type ConnectionInfo } from '@mongodb-js/connection-storage/renderer'; -import { useConnectionStorageContext } from '@mongodb-js/connection-storage/provider'; +import { type ConnectionInfo } from '@mongodb-js/connection-storage/provider'; import type AppRegistry from 'hadron-app-registry'; import type { connect, DataService } from 'mongodb-data-service'; -import React, { useCallback, useMemo, useState } from 'react'; +import React, { useMemo } from 'react'; import { usePreference } from 'compass-preferences-model/provider'; import { cloneDeep } from 'lodash'; import { useConnections } from '../stores/connections-store'; import Connecting from './connecting/connecting'; import ConnectionList from './connection-list/connection-list'; import FormHelp from './form-help/form-help'; -import { LegacyConnectionsModal } from './legacy-connections-modal'; type ConnectFn = typeof connect; @@ -87,7 +81,8 @@ function Connections({ onConnected, onConnectionFailed, onConnectionAttemptStarted, - getAutoConnectInfo, + openConnectionImportExportModal, + __TEST_INITIAL_CONNECTION_INFO, }: { appRegistry: AppRegistry; onConnected: ( @@ -99,13 +94,12 @@ function Connections({ error: Error ) => void; onConnectionAttemptStarted: (connectionInfo: ConnectionInfo) => void; - getAutoConnectInfo?: () => Promise; + openConnectionImportExportModal?: ( + action: 'export-favorites' | 'import-favorites' + ) => void; + __TEST_INITIAL_CONNECTION_INFO?: ConnectionInfo; }): React.ReactElement { const { log, mongoLogId } = useLoggerAndTelemetry('COMPASS-CONNECTIONS'); - // TODO(COMPASS-7397): services should not be used directly in render method, - // when this code is refactored to use the hadron plugin interface, storage - // should be handled through the plugin activation lifecycle - const connectionStorage = useConnectionStorageContext(); const { state, @@ -123,7 +117,7 @@ function Connections({ onConnected, onConnectionFailed, onConnectionAttemptStarted, - getAutoConnectInfo, + __TEST_INITIAL_CONNECTION_INFO, }); const { connectingConnectionId, @@ -135,24 +129,8 @@ function Connections({ oidcDeviceAuthUserCode, } = state; - const [showExportConnectionsModal, setShowExportConnectionsModal] = - useState(false); - const [showImportConnectionsModal, setShowImportConnectionsModal] = - useState(false); - const darkMode = useDarkMode(); - const openConnectionImportExportModal = useCallback( - (action: 'export-favorites' | 'import-favorites') => { - if (action === 'export-favorites') { - setShowExportConnectionsModal(true); - } else { - setShowImportConnectionsModal(true); - } - }, - [] - ); - const protectConnectionStrings = usePreference('protectConnectionStrings'); const forceConnectionOptions = usePreference('forceConnectionOptions'); const showKerberosPasswordField = usePreference('showKerberosPasswordField'); @@ -260,21 +238,6 @@ function Connections({ } /> )} - - -
); } diff --git a/packages/compass-connections/src/components/legacy-connections-modal.tsx b/packages/compass-connections/src/components/legacy-connections-modal.tsx index 89755bf5961..bd3be4d5214 100644 --- a/packages/compass-connections/src/components/legacy-connections-modal.tsx +++ b/packages/compass-connections/src/components/legacy-connections-modal.tsx @@ -14,7 +14,7 @@ import { Banner, } from '@mongodb-js/compass-components'; -import type { ConnectionStorage } from '@mongodb-js/connection-storage/renderer'; +import { useConnectionStorageContext } from '@mongodb-js/connection-storage/provider'; const LEGACY_MODAL_STORAGE_KEY = 'hide_legacy_connections_modal'; @@ -43,7 +43,21 @@ const footerStyles = css({ justifyContent: 'space-between', }); -const useLegacyModel = (connectionStorage: typeof ConnectionStorage) => { +const useLegacyModel = () => { + const connectionStorage = useConnectionStorageContext(); + if (typeof connectionStorage.getLegacyConnections !== 'function') { + throw new Error( + 'LegacyConnections migrations require provided ConnectionStorage to implement getLegacyConnections' + ); + } + + const getLegacyConnectionsImpl = useCallback( + async (options?: { signal: AbortSignal | undefined }) => { + return await connectionStorage.getLegacyConnections?.(options); + }, + [connectionStorage] + ); + const [connections, setConnections] = useState<{ name: string }[]>([]); const [isModalHiddenByUser, setIsModalHiddenByUser] = usePersistedState( @@ -52,10 +66,12 @@ const useLegacyModel = (connectionStorage: typeof ConnectionStorage) => { ); useEffect(() => { - void connectionStorage - .getLegacyConnections() - .then((connections) => setConnections(connections)); - }, []); + void getLegacyConnectionsImpl().then((connections) => { + if (connections) { + setConnections(connections); + } + }); + }, [getLegacyConnectionsImpl]); return { isOpen: connections.length > 0 && !isModalHiddenByUser, @@ -68,14 +84,10 @@ const useLegacyModel = (connectionStorage: typeof ConnectionStorage) => { }; }; -export const LegacyConnectionsModal = ({ - connectionStorage, -}: { - connectionStorage: typeof ConnectionStorage; -}) => { +export const LegacyConnectionsModal = () => { const [isHideModalChecked, setIsHideModalChecked] = useState(false); const { hideModal, hideModalPermanently, isOpen, connections } = - useLegacyModel(connectionStorage); + useLegacyModel(); const onCloseModal = useCallback(() => { if (isHideModalChecked) { diff --git a/packages/compass-connections/src/connection-info-provider.tsx b/packages/compass-connections/src/connection-info-provider.tsx new file mode 100644 index 00000000000..05e38a6e365 --- /dev/null +++ b/packages/compass-connections/src/connection-info-provider.tsx @@ -0,0 +1,66 @@ +import React, { createContext, useContext, useRef } from 'react'; +import type { ConnectionInfo } from '@mongodb-js/connection-info'; +import { + createServiceLocator, + createServiceProvider, +} from 'hadron-app-registry'; +import { useConnectionRepository } from './provider'; + +export type { ConnectionInfo }; + +export type ConnectionInfoAccess = { + getCurrentConnectionInfo(): ConnectionInfo; +}; + +const ConnectionInfoContext = createContext(null); +export const TEST_CONNECTION_INFO: ConnectionInfo = { + id: 'TEST', + connectionOptions: { + connectionString: 'mongodb://localhost:27020', + }, +}; +export function useConnectionInfo() { + const connectionInfo = useContext(ConnectionInfoContext); + if (!connectionInfo) { + if (process.env.NODE_ENV !== 'test') { + throw new Error( + 'Could not find the current ConnectionInfo. Did you forget to setup the ConnectionInfoContext?' + ); + } + return TEST_CONNECTION_INFO; + } + return connectionInfo; +} +export const ConnectionInfoProvider: React.FC<{ + connectionInfoId?: string; + children?: + | ((connectionInfo: ConnectionInfo) => React.ReactNode) + | React.ReactNode; +}> = createServiceProvider(function ConnectionInfoProvider({ + connectionInfoId, + children, +}) { + const { getConnectionInfoById } = useConnectionRepository(); + const connectionInfo = connectionInfoId + ? getConnectionInfoById(connectionInfoId) + : undefined; + return connectionInfo ? ( + + {typeof children === 'function' ? children(connectionInfo) : children} + + ) : null; +}); +export const useConnectionInfoAccess = (): ConnectionInfoAccess => { + const connectionInfo = useConnectionInfo(); + const connectionInfoRef = useRef(connectionInfo); + connectionInfoRef.current = connectionInfo; + return { + getCurrentConnectionInfo() { + return connectionInfoRef.current; + }, + }; +}; +export const connectionInfoAccessLocator = createServiceLocator( + useConnectionInfoAccess, + 'connectionInfoAccessLocator' +); diff --git a/packages/compass-connections/src/connection-scoped-app-registry.spec.ts b/packages/compass-connections/src/connection-scoped-app-registry.spec.ts index 4f311fb2652..642d60db873 100644 --- a/packages/compass-connections/src/connection-scoped-app-registry.spec.ts +++ b/packages/compass-connections/src/connection-scoped-app-registry.spec.ts @@ -1,6 +1,6 @@ import { expect } from 'chai'; import { spy } from 'sinon'; -import type { ConnectionInfoAccess } from '@mongodb-js/connection-storage/provider'; +import type { ConnectionInfoAccess } from './connection-info-provider'; import { ConnectionScopedAppRegistryImpl } from './connection-scoped-app-registry'; const connectionInfoAccess: ConnectionInfoAccess = { @@ -15,7 +15,7 @@ const connectionInfoAccess: ConnectionInfoAccess = { }; describe('ConnectionScopedGlobalAppRegistry', function () { - it('should add sourceConnectionInfoId as extra args when payload is not provided', function () { + it('should add connectionId as extra args when payload is not provided', function () { const emitSpy = spy(); const newAppRegistryEmitter = new ConnectionScopedAppRegistryImpl<'schema-analyzed'>( @@ -25,11 +25,11 @@ describe('ConnectionScopedGlobalAppRegistry', function () { newAppRegistryEmitter.emit('schema-analyzed'); expect(emitSpy).to.have.been.calledWith('schema-analyzed', { - sourceConnectionInfoId: '1234', + connectionId: '1234', }); }); - it('should add the sourceConnectionInfoId as extra args also when payload is provided', function () { + it('should add the connectionId as extra args also when payload is provided', function () { const emitSpy = spy(); const newAppRegistryEmitter = new ConnectionScopedAppRegistryImpl<'schema-analyzed'>( @@ -44,7 +44,7 @@ describe('ConnectionScopedGlobalAppRegistry', function () { record: true, }, { - sourceConnectionInfoId: '1234', + connectionId: '1234', } ); }); diff --git a/packages/compass-connections/src/connection-scoped-app-registry.ts b/packages/compass-connections/src/connection-scoped-app-registry.ts index 965bb14839d..b11300f59b5 100644 --- a/packages/compass-connections/src/connection-scoped-app-registry.ts +++ b/packages/compass-connections/src/connection-scoped-app-registry.ts @@ -6,7 +6,7 @@ import { import { type ConnectionInfoAccess, connectionInfoAccessLocator, -} from '@mongodb-js/connection-storage/provider'; +} from './connection-info-provider'; export type ConnectionScopedAppRegistryLocator< T extends string, @@ -46,9 +46,9 @@ export class ConnectionScopedAppRegistryImpl * relying on AppRegistry events. */ emit(event: T, ...payload: any[]): void { - const sourceConnectionInfoId = + const connectionId = this.connectionInfoAccess.getCurrentConnectionInfo().id; - this.appRegistryEmitter(event, ...payload, { sourceConnectionInfoId }); + this.appRegistryEmitter(event, ...payload, { connectionId }); } } diff --git a/packages/compass-connections/src/connections-manager.spec.ts b/packages/compass-connections/src/connections-manager.spec.ts index c51fea5f257..18ad28ec8b9 100644 --- a/packages/compass-connections/src/connections-manager.spec.ts +++ b/packages/compass-connections/src/connections-manager.spec.ts @@ -294,6 +294,31 @@ describe('ConnectionsManager', function () { } ); + context('when connecting to Atlas Streams', function () { + beforeEach(function () { + connectionsManager = getConnectionsManager(mockConnectFn); + }); + + it('should throw an error', async function () { + const maybeError = await connectionsManager + .connect( + { + id: '1', + connectionOptions: { + connectionString: + 'mongodb://atlas-stream-example.mongodb.net/?tls=true', + }, + }, + getConnectionConfigurationOptions() + ) + .catch((error) => error); + + expect(maybeError.message).to.equal( + 'Atlas Stream Processing is not yet supported on MongoDB Compass. To work with your Stream Processing Instance, connect with mongosh or MongoDB for VS Code.' + ); + }); + }); + context('when a connection attempt is cancelled', function () { let canceledPromise; beforeEach(function () { diff --git a/packages/compass-connections/src/connections-manager.ts b/packages/compass-connections/src/connections-manager.ts index 1256ff7cf7e..80c242c7fd1 100644 --- a/packages/compass-connections/src/connections-manager.ts +++ b/packages/compass-connections/src/connections-manager.ts @@ -12,6 +12,7 @@ import type { import { mongoLogId } from '@mongodb-js/compass-logging/provider'; import { cloneDeep, merge } from 'lodash'; import { adjustConnectionOptionsBeforeConnect } from '@mongodb-js/connection-form'; +import mongodbBuildInfo from 'mongodb-build-info'; type ConnectFn = typeof connect; type ConnectionInfoId = ConnectionInfo['id']; @@ -181,6 +182,12 @@ export class ConnectionsManager extends EventEmitter { return existingDataService; } + this.updateAndNotifyConnectionStatus( + connectionId, + ConnectionsManagerEvents.ConnectionAttemptStarted, + [connectionId] + ); + const adjustedConnectionInfoForConnection: ConnectionInfo = merge( cloneDeep({ id: connectionId, ...originalConnectionInfo }), { @@ -199,17 +206,21 @@ export class ConnectionsManager extends EventEmitter { } ); - this.updateAndNotifyConnectionStatus( - connectionId, - ConnectionsManagerEvents.ConnectionAttemptStarted, - [connectionId] - ); const connectionAttempt = createConnectionAttempt({ logger: this.logger, connectFn: this.__TEST_CONNECT_FN, }); + this.connectionAttempts.set(connectionId, connectionAttempt); + // Temporarily disable Atlas Streams connections until https://jira.mongodb.org/browse/STREAMS-862 + // is done. + if (isAtlasStreamsInstance(adjustedConnectionInfoForConnection)) { + throw new Error( + 'Atlas Stream Processing is not yet supported on MongoDB Compass. To work with your Stream Processing Instance, connect with mongosh or MongoDB for VS Code.' + ); + } + const dataService = await connectionAttempt.connect( adjustedConnectionInfoForConnection.connectionOptions ); @@ -353,3 +364,18 @@ export class ConnectionsManager extends EventEmitter { this.emit(connectionEvent, ...connectionEventParams); } } + +function isAtlasStreamsInstance( + adjustedConnectionInfoForConnection: ConnectionInfo +) { + try { + return mongodbBuildInfo.isAtlasStream( + adjustedConnectionInfoForConnection.connectionOptions.connectionString + ); + } catch { + // This catch-all is not ideal, but it safe-guards regular connections + // instead of making assumptions on the fact that the implementation + // of `mongodbBuildInfo.isAtlasStream` would never throw. + return false; + } +} diff --git a/packages/compass-connections/src/hooks/use-active-connections.spec.ts b/packages/compass-connections/src/hooks/use-active-connections.spec.ts index d30067c0803..5df27ec8283 100644 --- a/packages/compass-connections/src/hooks/use-active-connections.spec.ts +++ b/packages/compass-connections/src/hooks/use-active-connections.spec.ts @@ -3,15 +3,16 @@ import { ConnectionsManager, ConnectionsManagerProvider, } from '../provider'; +import type { EventEmitter } from 'events'; import { renderHook } from '@testing-library/react-hooks'; import { createElement } from 'react'; -import { ConnectionStorageContext } from '@mongodb-js/connection-storage/provider'; import { ConnectionStorageEvents, + ConnectionStorageProvider, + InMemoryConnectionStorage, type ConnectionInfo, type ConnectionStorage, - ConnectionStorageBus, -} from '@mongodb-js/connection-storage/renderer'; +} from '@mongodb-js/connection-storage/provider'; import { expect } from 'chai'; import Sinon from 'sinon'; import { waitFor } from '@testing-library/dom'; @@ -43,18 +44,15 @@ const mockConnections: ConnectionInfo[] = [ describe('useActiveConnections', function () { let renderHookWithContext: typeof renderHook; let connectionsManager: ConnectionsManager; - let mockConnectionStorage: typeof ConnectionStorage; + let mockConnectionStorage: ConnectionStorage; beforeEach(function () { connectionsManager = new ConnectionsManager({} as any); - mockConnectionStorage = { - events: new ConnectionStorageBus(), - loadAll: Sinon.stub().resolves([]), - } as any; + mockConnectionStorage = new InMemoryConnectionStorage(mockConnections); renderHookWithContext = (callback, options) => { const wrapper: React.FC = ({ children }) => - createElement(ConnectionStorageContext.Provider, { + createElement(ConnectionStorageProvider, { value: mockConnectionStorage, children: [ createElement(ConnectionsManagerProvider, { @@ -73,10 +71,6 @@ describe('useActiveConnections', function () { }); it('should return active connections', async function () { - mockConnectionStorage = { - events: new ConnectionStorageBus(), - loadAll: Sinon.stub().resolves(mockConnections), - } as any; (connectionsManager as any).connectionStatuses.set('turtle', 'connected'); const { result } = renderHookWithContext(() => useActiveConnections()); @@ -87,10 +81,6 @@ describe('useActiveConnections', function () { }); it('should listen to connections manager updates', async function () { - mockConnectionStorage = { - events: new ConnectionStorageBus(), - loadAll: Sinon.stub().resolves(mockConnections), - } as any; (connectionsManager as any).connectionStatuses.set('turtle', 'connected'); const { result } = renderHookWithContext(() => useActiveConnections()); @@ -111,11 +101,8 @@ describe('useActiveConnections', function () { }); it('should listen to connections storage updates', async function () { - const loadAllStub = Sinon.stub().resolves(mockConnections); - mockConnectionStorage = { - loadAll: loadAllStub, - events: new ConnectionStorageBus(), - } as any; + const loadAllStub = (mockConnectionStorage.loadAll = + Sinon.stub().resolves(mockConnections)); (connectionsManager as any).connectionStatuses.set('turtle', 'connected'); const { result } = renderHookWithContext(() => useActiveConnections()); @@ -126,7 +113,7 @@ describe('useActiveConnections', function () { }, mockConnections[1], ]); - mockConnectionStorage.events.emit( + (mockConnectionStorage as unknown as EventEmitter).emit( ConnectionStorageEvents.ConnectionsChanged ); diff --git a/packages/compass-connections/src/hooks/use-can-open-new-connections.spec.ts b/packages/compass-connections/src/hooks/use-can-open-new-connections.spec.ts index 953d57cb0db..e2d037f80a5 100644 --- a/packages/compass-connections/src/hooks/use-can-open-new-connections.spec.ts +++ b/packages/compass-connections/src/hooks/use-can-open-new-connections.spec.ts @@ -14,9 +14,9 @@ import { PreferencesProvider } from 'compass-preferences-model/provider'; import { ConnectionsManager, ConnectionsManagerProvider } from '../provider'; import { type ConnectionStorage, - ConnectionStorageContext, + ConnectionStorageProvider, + InMemoryConnectionStorage, } from '@mongodb-js/connection-storage/provider'; -import { ConnectionStorageBus } from '@mongodb-js/connection-storage/renderer'; import { useCanOpenNewConnections } from './use-can-open-new-connections'; const FAVORITE_CONNECTION_INFO: ConnectionInfo = { @@ -57,22 +57,17 @@ describe('useCanOpenNewConnections', function () { beforeEach(async function () { preferencesAccess = await createSandboxFromDefaultPreferences(); connectionManager = new ConnectionsManager({} as any); - connectionStorage = { - loadAll() { - return Promise.resolve([ - FAVORITE_CONNECTION_INFO, - NONFAVORITE_CONNECTION_INFO, - ]); - }, - events: new ConnectionStorageBus(), - } as ConnectionStorage; + connectionStorage = new InMemoryConnectionStorage([ + FAVORITE_CONNECTION_INFO, + NONFAVORITE_CONNECTION_INFO, + ]); renderHookWithContext = (callback, options) => { const wrapper: React.FC = ({ children }) => createElement(PreferencesProvider, { value: preferencesAccess, children: [ - createElement(ConnectionStorageContext.Provider, { + createElement(ConnectionStorageProvider, { value: connectionStorage, children: [ createElement(ConnectionsManagerProvider, { diff --git a/packages/compass-connections/src/hooks/use-connection-repository.spec.ts b/packages/compass-connections/src/hooks/use-connection-repository.spec.ts index b1661fca57f..07aba917551 100644 --- a/packages/compass-connections/src/hooks/use-connection-repository.spec.ts +++ b/packages/compass-connections/src/hooks/use-connection-repository.spec.ts @@ -1,48 +1,25 @@ -import type { ConnectionInfo } from '@mongodb-js/connection-info'; import { useConnectionRepository } from './use-connection-repository'; import { expect } from 'chai'; -import { stub } from 'sinon'; +import { spy, restore } from 'sinon'; import { type ConnectionStorage, - ConnectionStorageBus, + InMemoryConnectionStorage, + ConnectionStorageProvider, ConnectionStorageEvents, -} from '@mongodb-js/connection-storage/renderer'; -import { ConnectionStorageContext } from '@mongodb-js/connection-storage/provider'; +} from '@mongodb-js/connection-storage/provider'; import { renderHook } from '@testing-library/react-hooks'; import { waitFor } from '@testing-library/react'; import { createElement } from 'react'; describe('useConnectionRepository', function () { let renderHookWithContext: typeof renderHook; - let mockStorage: typeof ConnectionStorage; - let saveStub: ReturnType; - let deleteStub: ReturnType; - - function mockStorageWithConnections( - connections: Partial[] - ): typeof ConnectionStorage { - saveStub = stub(); - deleteStub = stub(); - - mockStorage = { - events: new ConnectionStorageBus(), - loadAll(): Promise { - return Promise.resolve(connections as ConnectionInfo[]); - }, - async load({ id }: { id: string }): Promise { - return Promise.resolve( - connections.find((c) => c.id === id) as ConnectionInfo - ); - }, - save: saveStub, - delete: deleteStub, - } as any; - } + let mockStorage: ConnectionStorage; beforeEach(function () { + mockStorage = new InMemoryConnectionStorage([]); renderHookWithContext = (callback, options) => { const wrapper: React.FC = ({ children }) => - createElement(ConnectionStorageContext.Provider, { + createElement(ConnectionStorageProvider, { value: mockStorage, children, }); @@ -50,9 +27,13 @@ describe('useConnectionRepository', function () { }; }); + afterEach(function () { + restore(); + }); + describe('favoriteConnections', function () { it('should return favourite connections sorted by name alphabetically', async function () { - mockStorageWithConnections([ + mockStorage = new InMemoryConnectionStorage([ { id: '2', savedConnectionType: 'favorite', @@ -63,6 +44,13 @@ describe('useConnectionRepository', function () { savedConnectionType: 'favorite', favorite: { name: 'Aa' }, }, + { + id: '3', + savedConnectionType: 'autoConnectInfo', + }, + { + id: '4', + }, ]); const { result } = renderHookWithContext(() => useConnectionRepository()); @@ -75,7 +63,7 @@ describe('useConnectionRepository', function () { }); it('should not change if only non favourite connections change', async function () { - mockStorageWithConnections([ + mockStorage = new InMemoryConnectionStorage([ { id: '2', savedConnectionType: 'favorite', @@ -98,6 +86,10 @@ describe('useConnectionRepository', function () { mockStorage.loadAll = function () { return Promise.resolve([ + { + id: '4', + savedConnectionType: 'autoConnectInfo', + }, { id: '3', savedConnectionType: 'recent', @@ -116,7 +108,7 @@ describe('useConnectionRepository', function () { ]); }; - mockStorage.events.emit(ConnectionStorageEvents.ConnectionsChanged); + mockStorage.emit(ConnectionStorageEvents.ConnectionsChanged); await waitFor(() => { const favoriteConnections = result.current.favoriteConnections; @@ -131,7 +123,7 @@ describe('useConnectionRepository', function () { describe('nonFavoriteConnections', function () { it('should return non favourite connections sorted by name alphabetically', async function () { - mockStorageWithConnections([ + mockStorage = new InMemoryConnectionStorage([ { id: '2', savedConnectionType: 'recent', @@ -142,6 +134,10 @@ describe('useConnectionRepository', function () { savedConnectionType: 'recent', favorite: { name: 'Aa' }, }, + { + id: '3', + savedConnectionType: 'autoConnectInfo', + }, ]); const { result } = renderHookWithContext(() => useConnectionRepository()); @@ -154,7 +150,7 @@ describe('useConnectionRepository', function () { }); it('should not change if only favourite connections change', async function () { - mockStorageWithConnections([ + mockStorage = new InMemoryConnectionStorage([ { id: '2', savedConnectionType: 'recent', @@ -195,7 +191,7 @@ describe('useConnectionRepository', function () { ]); }; - mockStorage.events.emit(ConnectionStorageEvents.ConnectionsChanged); + mockStorage.emit(ConnectionStorageEvents.ConnectionsChanged); await waitFor(() => { const favoriteConnections = result.current.favoriteConnections; @@ -208,9 +204,39 @@ describe('useConnectionRepository', function () { }); }); + describe('when there is autoConnectInfo available from underlying storage', function () { + it('getConnectionInfoById should return the connection info for auto connection if the correct id is specified', async function () { + mockStorage = new InMemoryConnectionStorage([ + { + id: '2', + savedConnectionType: 'favorite', + favorite: { name: 'Bb' }, + }, + { + id: '1', + savedConnectionType: 'favorite', + favorite: { name: 'Aa' }, + }, + { + id: '3', + savedConnectionType: 'autoConnectInfo', + }, + ]); + + const { result } = renderHookWithContext(() => useConnectionRepository()); + await waitFor(() => { + expect(result.current.getConnectionInfoById('3')).to.deep.equal({ + id: '3', + savedConnectionType: 'autoConnectInfo', + }); + }); + }); + }); + describe('#saveConnection', function () { it('should save a new connection if it has a valid connection string', async function () { - mockStorageWithConnections([]); + mockStorage = new InMemoryConnectionStorage([]); + const saveSpy = spy(mockStorage, 'save'); const { result } = renderHookWithContext(() => useConnectionRepository()); const connectionToSave = { @@ -220,15 +246,16 @@ describe('useConnectionRepository', function () { await result.current.saveConnection(connectionToSave); - expect(saveStub).to.have.been.calledOnceWith({ + expect(saveSpy).to.have.been.calledOnceWith({ connectionInfo: connectionToSave, }); }); it('should merge the connection info is one was already saved with the same id', async function () { - mockStorageWithConnections([ + mockStorage = new InMemoryConnectionStorage([ { id: '1', savedConnectionType: 'favorite' }, ]); + const saveSpy = spy(mockStorage, 'save'); const { result } = renderHookWithContext(() => useConnectionRepository()); const connectionToSave = { @@ -238,7 +265,7 @@ describe('useConnectionRepository', function () { await result.current.saveConnection(connectionToSave); - expect(saveStub).to.have.been.calledOnceWith({ + expect(saveSpy).to.have.been.calledOnceWith({ connectionInfo: { id: '1', savedConnectionType: 'favorite', @@ -248,7 +275,7 @@ describe('useConnectionRepository', function () { }); it('should merge oidc connection info if exists', async function () { - mockStorageWithConnections([ + mockStorage = new InMemoryConnectionStorage([ { id: '1', savedConnectionType: 'favorite', @@ -258,6 +285,7 @@ describe('useConnectionRepository', function () { }, }, ]); + const saveSpy = spy(mockStorage, 'save'); const { result } = renderHookWithContext(() => useConnectionRepository()); const connectionToSave = { @@ -271,7 +299,7 @@ describe('useConnectionRepository', function () { await result.current.saveConnection(connectionToSave); - expect(saveStub).to.have.been.calledOnceWith({ + expect(saveSpy).to.have.been.calledOnceWith({ connectionInfo: { id: '1', savedConnectionType: 'favorite', @@ -285,7 +313,8 @@ describe('useConnectionRepository', function () { }); it('should not save a new connection if it has an invalid connection string', async function () { - mockStorageWithConnections([]); + mockStorage = new InMemoryConnectionStorage([]); + const saveSpy = spy(mockStorage, 'save'); const { result } = renderHookWithContext(() => useConnectionRepository()); try { @@ -295,7 +324,7 @@ describe('useConnectionRepository', function () { }); expect.fail('Expected saveConnection to throw an exception.'); - expect(saveStub).to.not.have.been.calledOnce; + expect(saveSpy).to.not.have.been.calledOnce; } catch (ex) { expect(ex).to.have.property( 'message', @@ -307,7 +336,8 @@ describe('useConnectionRepository', function () { describe('#deleteConnection', function () { it('should delete a saved connection from the underlying storage', async function () { - mockStorageWithConnections([]); + mockStorage = new InMemoryConnectionStorage([]); + const deleteSpy = spy(mockStorage, 'delete'); const { result } = renderHookWithContext(() => useConnectionRepository()); const connectionToDelete = { id: '1', @@ -316,7 +346,7 @@ describe('useConnectionRepository', function () { await result.current.deleteConnection(connectionToDelete); - expect(deleteStub).to.have.been.calledOnceWith(connectionToDelete); + expect(deleteSpy).to.have.been.calledOnceWith(connectionToDelete); }); }); }); diff --git a/packages/compass-connections/src/hooks/use-connection-repository.ts b/packages/compass-connections/src/hooks/use-connection-repository.ts index 654e61a7280..030e7323848 100644 --- a/packages/compass-connections/src/hooks/use-connection-repository.ts +++ b/packages/compass-connections/src/hooks/use-connection-repository.ts @@ -3,8 +3,10 @@ import type { ConnectionInfo } from '@mongodb-js/connection-info'; import ConnectionString from 'mongodb-connection-string-url'; import { merge } from 'lodash'; import isEqual from 'lodash/isEqual'; -import { ConnectionStorageEvents } from '@mongodb-js/connection-storage/renderer'; -import { useConnectionStorageContext } from '@mongodb-js/connection-storage/provider'; +import { + ConnectionStorageEvents, + useConnectionStorageContext, +} from '@mongodb-js/connection-storage/provider'; import { useState, useEffect, useCallback } from 'react'; import { BSON } from 'bson'; @@ -43,6 +45,9 @@ export type ConnectionRepository = { nonFavoriteConnections: ConnectionInfo[]; saveConnection: (info: PartialConnectionInfo) => Promise; deleteConnection: (info: ConnectionInfo) => Promise; + getConnectionInfoById: ( + id: ConnectionInfo['id'] + ) => ConnectionInfo | undefined; }; export function useConnectionRepository(): ConnectionRepository { @@ -56,6 +61,10 @@ export function useConnectionRepository(): ConnectionRepository { ConnectionInfo[] >([]); + const [autoConnectInfo, setAutoConnectInfo] = useState< + ConnectionInfo | undefined + >(undefined); + const persistOIDCTokens = usePreference('persistOIDCTokens'); useEffect(() => { @@ -66,9 +75,17 @@ export function useConnectionRepository(): ConnectionRepository { .sort(sortedAlphabetically); const nonFavoriteConnections = allConnections - .filter((connection) => connection.savedConnectionType !== 'favorite') + .filter( + ({ savedConnectionType }) => + savedConnectionType !== 'favorite' && + savedConnectionType !== 'autoConnectInfo' + ) .sort(sortedAlphabetically); + const autoConnectInfo = allConnections.find( + ({ savedConnectionType }) => savedConnectionType === 'autoConnectInfo' + ); + setFavoriteConnections((prevList) => { if (areConnectionsEqual(prevList, favoriteConnections)) { return prevList; @@ -84,6 +101,16 @@ export function useConnectionRepository(): ConnectionRepository { return nonFavoriteConnections; } }); + + if (autoConnectInfo) { + setAutoConnectInfo((prevAutoConnectInfo) => { + if (prevAutoConnectInfo?.id !== autoConnectInfo.id) { + return autoConnectInfo; + } else { + return prevAutoConnectInfo; + } + }); + } } void updateListsOfConnections(); @@ -92,13 +119,13 @@ export function useConnectionRepository(): ConnectionRepository { void updateListsOfConnections(); } - storage.events.on( + storage.on( ConnectionStorageEvents.ConnectionsChanged, updateListsOfConnectionsSubscriber ); return () => { - storage.events.off( + storage.off( ConnectionStorageEvents.ConnectionsChanged, updateListsOfConnectionsSubscriber ); @@ -122,7 +149,7 @@ export function useConnectionRepository(): ConnectionRepository { } } - await storage.save({ connectionInfo: infoToSave }); + await storage.save?.({ connectionInfo: infoToSave }); return infoToSave; }, [storage, persistOIDCTokens] @@ -130,12 +157,24 @@ export function useConnectionRepository(): ConnectionRepository { const deleteConnection = useCallback( async (info: ConnectionInfo) => { - await storage.delete(info); + await storage.delete?.(info); }, [storage] ); + const getConnectionInfoById = useCallback( + (connectionInfoId: ConnectionInfo['id']) => { + const allConnections = [ + ...favoriteConnections, + ...nonFavoriteConnections, + ...(autoConnectInfo ? [autoConnectInfo] : []), + ]; + return allConnections.find(({ id }) => id === connectionInfoId); + }, + [favoriteConnections, nonFavoriteConnections, autoConnectInfo] + ); return { + getConnectionInfoById, favoriteConnections, nonFavoriteConnections, saveConnection, diff --git a/packages/compass-connections/src/hooks/use-tab-connection-theme.spec.ts b/packages/compass-connections/src/hooks/use-tab-connection-theme.spec.ts new file mode 100644 index 00000000000..9dd5f566aed --- /dev/null +++ b/packages/compass-connections/src/hooks/use-tab-connection-theme.spec.ts @@ -0,0 +1,130 @@ +import { expect } from 'chai'; +import { renderHook } from '@testing-library/react-hooks'; +import { waitFor } from '@testing-library/react'; +import { createElement } from 'react'; +import type { ConnectionInfo } from '@mongodb-js/connection-info'; +import { useTabConnectionTheme } from '../provider'; +import { + type ConnectionStorage, + ConnectionStorageProvider, + InMemoryConnectionStorage, +} from '@mongodb-js/connection-storage/provider'; +import { + type PreferencesAccess, + createSandboxFromDefaultPreferences, +} from 'compass-preferences-model'; +import { PreferencesProvider } from 'compass-preferences-model/provider'; + +const CONNECTION_INFO: ConnectionInfo = { + id: '1234', + connectionOptions: { + connectionString: 'mongodb://localhost:27017', + }, + favorite: { + color: 'color3', + name: 'my kingdom for a hook', + }, +}; + +const CONNECTION_INFO_NO_COLOR: ConnectionInfo = { + id: '1234', + connectionOptions: { + connectionString: 'mongodb://localhost:27017', + }, + favorite: { + name: 'look what is done cannot be now amended', + }, +}; + +const CONNECTION_INFO_INVALID_COLOR: ConnectionInfo = { + id: '1234', + connectionOptions: { + connectionString: 'mongodb://localhost:27017', + }, + favorite: { + color: 'notacolorlol', + name: 'what do I fear? myself?', + }, +}; + +describe('useTabConnectionTheme', function () { + let renderHookWithContext: typeof renderHook; + let mockStorage: ConnectionStorage; + let preferencesAccess: PreferencesAccess; + + beforeEach(async function () { + preferencesAccess = await createSandboxFromDefaultPreferences(); + await preferencesAccess.savePreferences({ + enableNewMultipleConnectionSystem: true, + }); + + mockStorage = new InMemoryConnectionStorage([CONNECTION_INFO]); + renderHookWithContext = (callback, options) => { + const wrapper: React.FC = ({ children }) => + createElement(PreferencesProvider, { + value: preferencesAccess, + children: createElement(ConnectionStorageProvider, { + value: mockStorage, + children, + }), + }); + return renderHook(callback, { wrapper, ...options }); + }; + }); + + describe('when a connection does not exist', function () { + it('should not return a theme', function () { + const { result } = renderHookWithContext(() => { + const { getThemeOf } = useTabConnectionTheme(); + return getThemeOf('NON_EXISTING'); + }); + + expect(result.current).to.be.undefined; + }); + }); + + describe('when a connection exists', function () { + it('should return the theme with the connection colors', async function () { + const { result } = renderHookWithContext(() => { + const { getThemeOf } = useTabConnectionTheme(); + return getThemeOf(CONNECTION_INFO.id); + }); + + await waitFor(() => { + expect(result.current).to.deep.equal({ + '&:focus-visible': { + '--workspace-tab-border-color': '#016BF8', + '--workspace-tab-selected-color': '#016BF8', + }, + '--workspace-tab-background-color': '#FFDFB5', + '--workspace-tab-border-color': '#E8EDEB', + '--workspace-tab-color': '#5C6C75', + '--workspace-tab-selected-background-color': '#FFD19A', + '--workspace-tab-selected-color': '#3D4F58', + }); + }); + }); + + it('should not return a theme when there is no color', async function () { + const { result } = renderHookWithContext(() => { + const { getThemeOf } = useTabConnectionTheme(); + return getThemeOf(CONNECTION_INFO_NO_COLOR.id); + }); + + await waitFor(() => { + expect(result.current).to.equal(undefined); + }); + }); + + it('should not return a theme when the color is invalid', async function () { + const { result } = renderHookWithContext(() => { + const { getThemeOf } = useTabConnectionTheme(); + return getThemeOf(CONNECTION_INFO_INVALID_COLOR.id); + }); + + await waitFor(() => { + expect(result.current).to.equal(undefined); + }); + }); + }); +}); diff --git a/packages/compass-connections/src/hooks/use-tab-connection-theme.ts b/packages/compass-connections/src/hooks/use-tab-connection-theme.ts new file mode 100644 index 00000000000..fb5357affb7 --- /dev/null +++ b/packages/compass-connections/src/hooks/use-tab-connection-theme.ts @@ -0,0 +1,76 @@ +import { type ConnectionInfo } from '@mongodb-js/connection-info'; +import { useConnectionColor } from '@mongodb-js/connection-form'; +import { useConnectionRepository } from './use-connection-repository'; +import { useDarkMode, type TabTheme } from '@mongodb-js/compass-components'; +import { palette } from '@mongodb-js/compass-components'; +import { useCallback } from 'react'; +import { usePreference } from 'compass-preferences-model/provider'; + +type ThemeProvider = { + getThemeOf( + this: void, + connectionId: ConnectionInfo['id'] + ): TabTheme | undefined; +}; + +export function useTabConnectionTheme(): ThemeProvider { + const { connectionColorToHex, connectionColorToHexActive } = + useConnectionColor(); + const { getConnectionInfoById } = useConnectionRepository(); + const darkTheme = useDarkMode(); + const isMultipleConnectionsEnabled = usePreference( + 'enableNewMultipleConnectionSystem' + ); + + const getThemeOf = useCallback( + (connectionId: ConnectionInfo['id']) => { + const connectionInfo = getConnectionInfoById(connectionId); + const color = connectionInfo?.favorite?.color; + const bgColor = connectionColorToHex(color); + const activeBgColor = connectionColorToHexActive(color); + + if ( + !color || + !bgColor || + !activeBgColor || + !isMultipleConnectionsEnabled + ) { + return; + } + + return { + '--workspace-tab-background-color': bgColor, + '--workspace-tab-border-color': darkTheme + ? palette.gray.dark2 + : palette.gray.light2, + '--workspace-tab-color': darkTheme + ? palette.gray.base + : palette.gray.dark1, + '--workspace-tab-selected-background-color': activeBgColor, + '--workspace-tab-selected-color': darkTheme + ? palette.gray.light2 + : palette.gray.dark2, + '&:focus-visible': { + '--workspace-tab-border-color': darkTheme + ? palette.blue.light1 + : palette.blue.base, + '--workspace-tab-selected-color': darkTheme + ? palette.blue.light1 + : palette.blue.base, + }, + }; + }, + [ + palette, + getConnectionInfoById, + connectionColorToHex, + connectionColorToHexActive, + darkTheme, + isMultipleConnectionsEnabled, + ] + ); + + return { + getThemeOf, + }; +} diff --git a/packages/compass-connections/src/index.ts b/packages/compass-connections/src/index.ts index ea44f58c806..9bb67f4b562 100644 --- a/packages/compass-connections/src/index.ts +++ b/packages/compass-connections/src/index.ts @@ -1,3 +1,3 @@ import Connections from './components/connections'; - +export { LegacyConnectionsModal } from './components/legacy-connections-modal'; export default Connections; diff --git a/packages/compass-connections/src/provider.ts b/packages/compass-connections/src/provider.ts index 2a0e14ec681..8102e829a09 100644 --- a/packages/compass-connections/src/provider.ts +++ b/packages/compass-connections/src/provider.ts @@ -1,7 +1,7 @@ import { createContext, useContext } from 'react'; import { createServiceLocator } from 'hadron-app-registry'; -import { useConnectionInfo } from '@mongodb-js/connection-storage/provider'; - +import { useConnectionInfo } from './connection-info-provider'; +import { EventEmitter } from 'events'; import type { DataService } from 'mongodb-data-service'; import { ConnectionsManager } from './connections-manager'; import { createNoopLoggerAndTelemetry } from '@mongodb-js/compass-logging/provider'; @@ -11,8 +11,16 @@ export * from './connections-manager'; export { useConnections } from './stores/connections-store'; export { useActiveConnections } from './hooks/use-active-connections'; +class TestConnectionsManager extends EventEmitter { + getDataServiceForConnection() { + return new EventEmitter() as unknown as DataService; + } +} + const ConnectionsManagerContext = createContext( - null + process.env.NODE_ENV === 'test' + ? (new TestConnectionsManager() as unknown as ConnectionsManager) + : null ); export const ConnectionsManagerProvider = ConnectionsManagerContext.Provider; @@ -79,3 +87,10 @@ export { type CanNotOpenConnectionReason, useCanOpenNewConnections, } from './hooks/use-can-open-new-connections'; +export { + type ConnectionRepository, + useConnectionRepository, +} from './hooks/use-connection-repository'; +export * from './connection-info-provider'; + +export { useTabConnectionTheme } from './hooks/use-tab-connection-theme'; diff --git a/packages/compass-connections/src/stores/connections-store.spec.ts b/packages/compass-connections/src/stores/connections-store.spec.ts index 18ede2a8ef2..df93bd106a9 100644 --- a/packages/compass-connections/src/stores/connections-store.spec.ts +++ b/packages/compass-connections/src/stores/connections-store.spec.ts @@ -1,21 +1,20 @@ import { expect } from 'chai'; -import EventEmitter from 'events'; import { waitFor, cleanup } from '@testing-library/react'; import type { RenderResult } from '@testing-library/react-hooks'; import { renderHook, act } from '@testing-library/react-hooks'; import sinon from 'sinon'; import { useConnections } from './connections-store'; -import { - type ConnectionStorage, - ConnectionStorageBus, -} from '@mongodb-js/connection-storage/renderer'; import { createSandboxFromDefaultPreferences } from 'compass-preferences-model'; import { createElement } from 'react'; import { PreferencesProvider } from 'compass-preferences-model/provider'; import { type ConnectionInfo } from '@mongodb-js/connection-storage/main'; -import { ConnectionStorageContext } from '@mongodb-js/connection-storage/provider'; +import { + InMemoryConnectionStorage, + type ConnectionStorage, + ConnectionStorageProvider, +} from '@mongodb-js/connection-storage/provider'; import { ConnectionsManager, ConnectionsManagerProvider } from '../provider'; import type { DataService, connect } from 'mongodb-data-service'; @@ -58,11 +57,7 @@ const mockConnections: ConnectionInfo[] = [ describe('use-connections hook', function () { let connectionsManager: ConnectionsManager; - let mockConnectionStorage: typeof ConnectionStorage; - let loadAllSpy: sinon.SinonSpy; - let saveSpy: sinon.SinonSpy; - let deleteSpy: sinon.SinonSpy; - let loadSpy: sinon.SinonSpy; + let mockConnectionStorage: ConnectionStorage; let renderHookWithContext: typeof renderHook; before(async function () { @@ -72,7 +67,7 @@ describe('use-connections hook', function () { createElement(PreferencesProvider, { value: preferences, children: [ - createElement(ConnectionStorageContext.Provider, { + createElement(ConnectionStorageProvider, { value: mockConnectionStorage, children: [ createElement(ConnectionsManagerProvider, { @@ -90,19 +85,7 @@ describe('use-connections hook', function () { }); beforeEach(function () { - loadAllSpy = sinon.spy(); - saveSpy = sinon.spy(); - deleteSpy = sinon.spy(); - loadSpy = sinon.spy(); - - mockConnectionStorage = { - events: new ConnectionStorageBus(), - loadAll: loadAllSpy, - save: saveSpy, - delete: deleteSpy, - load: loadSpy, - }; - + mockConnectionStorage = new InMemoryConnectionStorage(mockConnections); connectionsManager = getConnectionsManager(() => Promise.resolve({ mockDataService: 'yes', @@ -111,24 +94,26 @@ describe('use-connections hook', function () { ); }); - afterEach(cleanup); + afterEach(() => { + cleanup(); + sinon.restore(); + }); describe('#onMount', function () { - const getAutoConnectInfo = () => - Promise.resolve({ + it('allows connecting to a dynamically provided connection info object', async function () { + const onConnected = sinon.spy(); + sinon.stub(mockConnectionStorage, 'getAutoConnectInfo').resolves({ id: 'new', connectionOptions: { connectionString: 'mongodb://new-recent', }, }); - it('allows connecting to a dynamically provided connection info object', async function () { - const onConnected = sinon.spy(); + const saveSpy = sinon.spy(mockConnectionStorage, 'save'); renderHookWithContext(() => useConnections({ onConnected, onConnectionFailed: noop, onConnectionAttemptStarted: noop, - getAutoConnectInfo, }) ); @@ -141,8 +126,7 @@ describe('use-connections hook', function () { describe('#loadConnections', function () { it('loads the connections from the connection storage', async function () { - const loadAllSpyWithData = sinon.fake.resolves(mockConnections); - mockConnectionStorage.loadAll = loadAllSpyWithData; + const loadAllSpy = sinon.spy(mockConnectionStorage, 'loadAll'); const { result } = renderHookWithContext(() => useConnections({ @@ -157,14 +141,14 @@ describe('use-connections hook', function () { expect(result.current.favoriteConnections.length).to.equal(2) ); - expect(loadAllSpyWithData).to.have.been.called; + expect(loadAllSpy).to.have.been.called; }); it('filters and sort favorites connections', async function () { const connectionOptions = { connectionString: 'mongodb://turtle', }; - const loadAllSpyWithData = sinon.fake.resolves([ + mockConnectionStorage = new InMemoryConnectionStorage([ { id: '1', savedConnectionType: 'favorite', @@ -179,7 +163,6 @@ describe('use-connections hook', function () { connectionOptions, }, ]); - mockConnectionStorage.loadAll = loadAllSpyWithData; const { result } = renderHookWithContext(() => useConnections({ @@ -216,7 +199,7 @@ describe('use-connections hook', function () { const connectionOptions = { connectionString: 'mongodb://turtle', }; - const loadAllSpyWithData = sinon.fake.resolves([ + mockConnectionStorage = new InMemoryConnectionStorage([ { id: '1', savedConnectionType: 'favorite', @@ -250,7 +233,6 @@ describe('use-connections hook', function () { connectionOptions, }, ]); - mockConnectionStorage.loadAll = loadAllSpyWithData; const { result } = renderHookWithContext(() => useConnections({ @@ -293,6 +275,7 @@ describe('use-connections hook', function () { describe('#connect', function () { it(`calls onConnected`, async function () { const onConnected = sinon.spy(); + const saveSpy = sinon.spy(mockConnectionStorage, 'save'); const { result } = renderHookWithContext(() => useConnections({ onConnected, @@ -318,9 +301,9 @@ describe('use-connections hook', function () { describe('#saveConnection', function () { describe('with an existing connection', function () { let hookResult: RenderResult>; + let saveSpy: sinon.SinonSpy; beforeEach(async function () { - mockConnectionStorage.loadAll = () => Promise.resolve(mockConnections); - mockConnectionStorage.load = () => Promise.resolve(mockConnections[1]); + saveSpy = sinon.spy(mockConnectionStorage, 'save'); const { result } = renderHookWithContext(() => useConnections({ @@ -380,7 +363,9 @@ describe('use-connections hook', function () { }); describe('saving a new connection', function () { + let saveSpy: sinon.SinonSpy; beforeEach(async function () { + saveSpy = sinon.spy(mockConnectionStorage, 'save'); const { result } = renderHookWithContext(() => useConnections({ onConnected: noop, @@ -410,7 +395,10 @@ describe('use-connections hook', function () { describe('saving an invalid connection', function () { let hookResult: RenderResult>; + let saveSpy: sinon.SinonSpy; beforeEach(async function () { + mockConnectionStorage = new InMemoryConnectionStorage([]); + saveSpy = sinon.spy(mockConnectionStorage, 'save'); const { result } = renderHookWithContext(() => useConnections({ onConnected: noop, @@ -448,14 +436,11 @@ describe('use-connections hook', function () { let hookResult: RenderResult>; beforeEach(function () { - mockConnectionStorage.events = new EventEmitter(); - const { result } = renderHookWithContext(() => useConnections({ onConnected: noop, onConnectionFailed: noop, onConnectionAttemptStarted: noop, - connectFn: noop, }) ); @@ -473,11 +458,9 @@ describe('use-connections hook', function () { ]); mockConnectionStorage.loadAll = loadAllSpyWithData; - mockConnectionStorage.events.emit('connections-changed'); + mockConnectionStorage.emit('ConnectionsChanged'); - await waitFor( - () => expect(mockConnectionStorage.loadAll).to.have.been.called - ); + await waitFor(() => expect(loadAllSpyWithData).to.have.been.called); expect(hookResult.current.favoriteConnections.length).to.equal(1); }); @@ -486,8 +469,6 @@ describe('use-connections hook', function () { describe('saving the current active connection', function () { let hookResult: RenderResult>; beforeEach(async function () { - mockConnectionStorage.loadAll = () => Promise.resolve(mockConnections); - const { result } = renderHookWithContext(() => useConnections({ onConnected: noop, @@ -497,9 +478,9 @@ describe('use-connections hook', function () { ); // Wait for the async loading of connections to complete. - await waitFor(() => - expect(result.current.favoriteConnections.length).to.equal(2) - ); + await waitFor(() => { + return expect(result.current.favoriteConnections.length).to.equal(2); + }); // Make the first connection the active connection. act(() => { @@ -537,9 +518,10 @@ describe('use-connections hook', function () { }); }); }); + describe('#removeAllRecentsConnections', function () { it('should delete all recent connections', async function () { - const mockConnections = [ + mockConnectionStorage = new InMemoryConnectionStorage([ { id: 'dolphin', connectionOptions: { @@ -570,9 +552,9 @@ describe('use-connections hook', function () { name: 'oranges', }, }, - ]; - const loadAllSpyWithData = sinon.fake.resolves(mockConnections); - mockConnectionStorage.loadAll = loadAllSpyWithData; + ]); + const loadAllSpy = sinon.spy(mockConnectionStorage, 'loadAll'); + const deleteSpy = sinon.spy(mockConnectionStorage, 'delete'); const { result } = renderHookWithContext(() => useConnections({ @@ -587,16 +569,17 @@ describe('use-connections hook', function () { }); await result.current.removeAllRecentsConnections(); - expect(loadAllSpyWithData).to.have.been.called; + expect(loadAllSpy).to.have.been.called; expect(deleteSpy.callCount).to.equal(2); }); }); + describe('#duplicateConnection', function () { let hookResult: RenderResult>; it('should duplicate a connection', async function () { - const loadAllSpyWithData = sinon.fake.resolves(mockConnections); - mockConnectionStorage.loadAll = loadAllSpyWithData; + const loadAllSpy = sinon.spy(mockConnectionStorage, 'loadAll'); + const saveSpy = sinon.spy(mockConnectionStorage, 'save'); const { result } = renderHookWithContext(() => useConnections({ @@ -613,7 +596,7 @@ describe('use-connections hook', function () { result.current.duplicateConnection(result.current.favoriteConnections[0]); await waitFor(() => { - expect(loadAllSpyWithData).to.have.been.called; + expect(loadAllSpy).to.have.been.called; expect(saveSpy.callCount).to.equal(1); }); @@ -624,11 +607,12 @@ describe('use-connections hook', function () { expect(hookResult.current.state.activeConnectionId).to.not.equal(null); }); }); + describe('#removeConnection', function () { let hookResult: RenderResult>; it('should remove a connection', async function () { - const loadAllSpyWithData = sinon.fake.resolves(mockConnections); - mockConnectionStorage.loadAll = loadAllSpyWithData; + const loadAllSpy = sinon.spy(mockConnectionStorage, 'loadAll'); + const deleteSpy = sinon.spy(mockConnectionStorage, 'delete'); const { result } = renderHookWithContext(() => useConnections({ @@ -645,7 +629,7 @@ describe('use-connections hook', function () { }); result.current.removeConnection(result.current.favoriteConnections[1]); - expect(loadAllSpyWithData).to.have.been.called; + expect(loadAllSpy).to.have.been.called; expect(deleteSpy.callCount).to.equal(1); hookResult = result; }); @@ -657,9 +641,10 @@ describe('use-connections hook', function () { ).equal('mongodb://localhost:27017'); }); }); + describe('#createNewConnection', function () { it('should create a connection', async function () { - const mockConnections = [ + mockConnectionStorage = new InMemoryConnectionStorage([ { id: 'turtle', connectionOptions: { @@ -680,9 +665,8 @@ describe('use-connections hook', function () { name: 'peaches', }, }, - ]; - const loadAllSpyWithData = sinon.fake.resolves(mockConnections); - mockConnectionStorage.loadAll = loadAllSpyWithData; + ]); + const loadAllSpy = sinon.spy(mockConnectionStorage, 'loadAll'); const { result } = renderHookWithContext(() => useConnections({ @@ -697,7 +681,7 @@ describe('use-connections hook', function () { act(() => { result.current.setActiveConnectionById('turtle'); }); - expect(loadAllSpyWithData).to.have.been.called; + expect(loadAllSpy).to.have.been.called; result.current.createNewConnection(); expect(result.current.state.activeConnectionId).not.undefined; expect( diff --git a/packages/compass-connections/src/stores/connections-store.ts b/packages/compass-connections/src/stores/connections-store.ts index 7ee9390c41e..80472479f52 100644 --- a/packages/compass-connections/src/stores/connections-store.ts +++ b/packages/compass-connections/src/stores/connections-store.ts @@ -7,12 +7,13 @@ import { import { getConnectionTitle } from '@mongodb-js/connection-info'; import { type ConnectionInfo } from '@mongodb-js/connection-storage/main'; import { cloneDeep, merge } from 'lodash'; -import { v4 as uuidv4 } from 'uuid'; +import { UUID } from 'bson'; import { ConnectionString } from 'mongodb-connection-string-url'; import { useToast } from '@mongodb-js/compass-components'; import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging'; import { usePreference } from 'compass-preferences-model/provider'; import { useConnectionRepository } from '../hooks/use-connection-repository'; +import { useConnectionStorageContext } from '@mongodb-js/connection-storage/provider'; const { debug, mongoLogId, log } = createLoggerAndTelemetry( 'COMPASS-CONNECTIONS' @@ -41,7 +42,7 @@ type RecursivePartial = { export function createNewConnectionInfo(): ConnectionInfo { return { - id: uuidv4(), + id: new UUID().toString(), connectionOptions: { connectionString: 'mongodb://localhost:27017', }, @@ -58,9 +59,12 @@ type State = { oidcDeviceAuthUserCode: string | null; }; -export function defaultConnectionsState(): State { +export function defaultConnectionsState( + __TEST_INITIAL_CONNECTION_INFO?: ConnectionInfo +): State { return { - activeConnectionInfo: createNewConnectionInfo(), + activeConnectionInfo: + __TEST_INITIAL_CONNECTION_INFO ?? createNewConnectionInfo(), connectingStatusText: '', connectingConnectionId: null, connectionErrorMessage: null, @@ -157,7 +161,7 @@ export function useConnections({ onConnected, onConnectionFailed, onConnectionAttemptStarted, - getAutoConnectInfo, + __TEST_INITIAL_CONNECTION_INFO, }: { onConnected: ( connectionInfo: ConnectionInfo, @@ -168,7 +172,7 @@ export function useConnections({ error: Error ) => void; onConnectionAttemptStarted: (connectionInfo: ConnectionInfo) => void; - getAutoConnectInfo?: () => Promise; + __TEST_INITIAL_CONNECTION_INFO?: ConnectionInfo; }): { state: State; recentConnections: ConnectionInfo[]; @@ -186,6 +190,7 @@ export function useConnections({ // when this code is refactored to use the hadron plugin interface, storage // should be handled through the plugin activation lifecycle const connectionsManager = useConnectionsManagerContext(); + const connectionStorage = useConnectionStorageContext(); const { favoriteConnections, nonFavoriteConnections: recentConnections, @@ -200,7 +205,7 @@ export function useConnections({ const [state, dispatch]: [State, Dispatch] = useReducer( connectionsReducer, - defaultConnectionsState() + defaultConnectionsState(__TEST_INITIAL_CONNECTION_INFO) ); const { activeConnectionId } = state; @@ -317,16 +322,12 @@ export function useConnections({ ); useEffect(() => { - // Load connections after first render. void connectWithAutoConnectInfoIfAvailable(); async function connectWithAutoConnectInfoIfAvailable() { - let connectionInfo: ConnectionInfo | undefined; + let autoConnectInfo: ConnectionInfo | undefined; try { - connectionInfo = - typeof getAutoConnectInfo === 'function' - ? await getAutoConnectInfo() - : undefined; - if (connectionInfo) { + autoConnectInfo = await connectionStorage.getAutoConnectInfo?.(); + if (autoConnectInfo) { log.info( mongoLogId(1_001_000_160), 'Connection Store', @@ -334,12 +335,12 @@ export function useConnections({ ); dispatch({ type: 'set-active-connection', - connectionInfo, + connectionInfo: autoConnectInfo, }); - void connect(connectionInfo, false); + void connect(autoConnectInfo, false); } } catch (error) { - onConnectionFailed(connectionInfo ?? null, error as Error); + onConnectionFailed(autoConnectInfo ?? null, error as Error); log.error( mongoLogId(1_001_000_290), 'Connection Store', @@ -361,7 +362,7 @@ export function useConnections({ // not resolved. connectionsManager.cancelAllConnectionAttempts(); }; - }, [getAutoConnectInfo, persistOIDCTokens]); + }, []); const connect = async ( connectionInfo: ConnectionInfo, @@ -506,7 +507,7 @@ export function useConnections({ duplicateConnection(connectionInfo: ConnectionInfo) { const duplicate: ConnectionInfo = { ...cloneDeep(connectionInfo), - id: uuidv4(), + id: new UUID().toString(), }; if (duplicate.favorite?.name) { diff --git a/packages/compass-crud/.eslintrc.js b/packages/compass-crud/.eslintrc.js index f06f8fc6013..b54d314ab18 100644 --- a/packages/compass-crud/.eslintrc.js +++ b/packages/compass-crud/.eslintrc.js @@ -1,3 +1,4 @@ +'use strict'; module.exports = { root: true, extends: ['@mongodb-js/eslint-config-compass/plugin'], diff --git a/packages/compass-crud/.mocharc.js b/packages/compass-crud/.mocharc.js index a7e53abc444..61091edba73 100644 --- a/packages/compass-crud/.mocharc.js +++ b/packages/compass-crud/.mocharc.js @@ -1 +1,2 @@ +'use strict'; module.exports = require('@mongodb-js/mocha-config-compass/compass-plugin'); diff --git a/packages/compass-crud/package.json b/packages/compass-crud/package.json index 83abc7e5f17..180c796bc17 100644 --- a/packages/compass-crud/package.json +++ b/packages/compass-crud/package.json @@ -6,7 +6,7 @@ "email": "compass@mongodb.com" }, "private": true, - "version": "13.24.1", + "version": "13.26.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -48,51 +48,51 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "devDependencies": { - "@mongodb-js/compass-test-server": "^0.1.13", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/compass-test-server": "^0.1.15", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "@types/reflux": "^6.4.3", "chai": "^4.1.2", "chai-as-promised": "^7.1.1", "depcheck": "^1.4.1", - "electron": "^28.2.10", + "electron": "^29.3.1", "electron-mocha": "^12.2.0", "enzyme": "^3.11.0", "eslint": "^7.25.0", "mocha": "^10.2.0", - "mongodb-instance-model": "^12.18.1", + "mongodb-instance-model": "^12.20.0", "nyc": "^15.1.0", "react-dom": "^17.0.2", "sinon": "^8.1.1", "typescript": "^5.0.4" }, "dependencies": { - "@mongodb-js/compass-app-stores": "^7.10.1", - "@mongodb-js/compass-collection": "^4.23.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-editor": "^0.21.1", - "@mongodb-js/compass-field-store": "^9.0.19", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-query-bar": "^8.25.1", - "@mongodb-js/compass-workspaces": "^0.5.1", - "@mongodb-js/explain-plan-helper": "^1.1.10", - "@mongodb-js/my-queries-storage": "^0.5.1", + "@mongodb-js/compass-app-stores": "^7.12.0", + "@mongodb-js/compass-collection": "^4.25.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-editor": "^0.23.0", + "@mongodb-js/compass-field-store": "^9.2.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-query-bar": "^8.27.0", + "@mongodb-js/compass-workspaces": "^0.7.0", + "@mongodb-js/explain-plan-helper": "^1.1.12", + "@mongodb-js/my-queries-storage": "^0.7.0", "ag-grid-community": "^20.2.0", "ag-grid-react": "^20.2.0", "bson": "^6.6.0", - "compass-preferences-model": "^2.18.1", + "compass-preferences-model": "^2.20.0", "ejson-shell-parser": "^2.0.1", - "hadron-app-registry": "^9.1.8", - "hadron-document": "^8.4.9", - "hadron-type-checker": "^7.1.2", + "hadron-app-registry": "^9.1.10", + "hadron-document": "^8.5.1", + "hadron-type-checker": "^7.2.1", "jsondiffpatch": "^0.5.0", "lodash": "^4.17.21", - "mongodb-data-service": "^22.18.1", + "mongodb-data-service": "^22.19.1", "mongodb-ns": "^2.4.0", "mongodb-query-parser": "^4.1.0", "prop-types": "^15.7.2", diff --git a/packages/compass-crud/src/components/crud-toolbar.spec.tsx b/packages/compass-crud/src/components/crud-toolbar.spec.tsx index 7a5f462d354..f7e32206613 100644 --- a/packages/compass-crud/src/components/crud-toolbar.spec.tsx +++ b/packages/compass-crud/src/components/crud-toolbar.spec.tsx @@ -96,10 +96,6 @@ describe('CrudToolbar Component', function () { beforeEach(async function () { preferences = await createSandboxFromDefaultPreferences(); - await preferences.savePreferences({ - enableBulkUpdateOperations: true, - enableBulkDeleteOperations: true, - }); }); it('renders the query bar role', function () { @@ -283,14 +279,6 @@ describe('CrudToolbar Component', function () { }); describe('update button', function () { - it('should not be visible when the enableBulkUpdateOperations toggle is off', async function () { - await preferences.savePreferences({ - enableBulkUpdateOperations: false, - }); - - expect(screen.queryByText(updateDataText)).to.not.exist; - }); - it('should render disabled when the query has a skip', function () { renderCrudToolbar({ querySkip: 10, @@ -322,14 +310,6 @@ describe('CrudToolbar Component', function () { }); describe('delete button', function () { - it('should not be visible when the enableBulkDeleteOperations toggle is off', async function () { - await preferences.savePreferences({ - enableBulkDeleteOperations: false, - }); - - expect(screen.queryByText(deleteDataText)).to.not.exist; - }); - it('should render disabled when the query has a skip', function () { renderCrudToolbar({ querySkip: 10, diff --git a/packages/compass-crud/src/components/delete-data-menu.tsx b/packages/compass-crud/src/components/delete-data-menu.tsx index a0776181120..81772d8762e 100644 --- a/packages/compass-crud/src/components/delete-data-menu.tsx +++ b/packages/compass-crud/src/components/delete-data-menu.tsx @@ -6,7 +6,6 @@ import { WorkspaceContainer, css, } from '@mongodb-js/compass-components'; -import { usePreference } from 'compass-preferences-model/provider'; type DeleteMenuButtonProps = { isWritable: boolean; @@ -24,12 +23,6 @@ const DeleteMenuButton: React.FunctionComponent = ({ isWritable, onClick, }) => { - const isVisible = usePreference('enableBulkDeleteOperations'); - - if (!isVisible) { - return null; - } - return ( + + )} + + + + - {didSucceed && onSubmitFeedback && ( diff --git a/packages/compass-import-export/.eslintrc.js b/packages/compass-import-export/.eslintrc.js index f06f8fc6013..b54d314ab18 100644 --- a/packages/compass-import-export/.eslintrc.js +++ b/packages/compass-import-export/.eslintrc.js @@ -1,3 +1,4 @@ +'use strict'; module.exports = { root: true, extends: ['@mongodb-js/eslint-config-compass/plugin'], diff --git a/packages/compass-import-export/.mocharc.js b/packages/compass-import-export/.mocharc.js index a7e53abc444..61091edba73 100644 --- a/packages/compass-import-export/.mocharc.js +++ b/packages/compass-import-export/.mocharc.js @@ -1 +1,2 @@ +'use strict'; module.exports = require('@mongodb-js/mocha-config-compass/compass-plugin'); diff --git a/packages/compass-import-export/package.json b/packages/compass-import-export/package.json index 5f08fd06a4d..0ee542d7f64 100644 --- a/packages/compass-import-export/package.json +++ b/packages/compass-import-export/package.json @@ -11,7 +11,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "7.23.1", + "version": "7.25.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -49,22 +49,22 @@ }, "dependencies": { "@electron/remote": "^2.1.2", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-editor": "^0.21.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-utils": "^0.6.1", - "@mongodb-js/compass-workspaces": "^0.5.1", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-editor": "^0.23.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-utils": "^0.6.3", + "@mongodb-js/compass-workspaces": "^0.7.0", "bson": "^6.6.0", - "compass-preferences-model": "^2.18.1", + "compass-preferences-model": "^2.20.0", "debug": "^4.2.0", - "electron": "^28.2.10", - "hadron-app-registry": "^9.1.8", - "hadron-document": "^8.4.9", - "hadron-ipc": "^3.2.12", + "electron": "^29.3.1", + "hadron-app-registry": "^9.1.10", + "hadron-document": "^8.5.1", + "hadron-ipc": "^3.2.14", "lodash": "^4.17.21", "mongodb": "^6.5.0", - "mongodb-data-service": "^22.18.1", + "mongodb-data-service": "^22.19.1", "mongodb-ns": "^2.4.0", "mongodb-query-parser": "^4.1.0", "mongodb-schema": "^12.1.0", @@ -78,11 +78,11 @@ "temp": "^0.9.4" }, "devDependencies": { - "@mongodb-js/compass-test-server": "^0.1.13", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/compass-test-server": "^0.1.15", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "@types/chai": "^4.2.21", diff --git a/packages/compass-import-export/src/components/import-toast.tsx b/packages/compass-import-export/src/components/import-toast.tsx index b526167c6a1..91bf7dc97f2 100644 --- a/packages/compass-import-export/src/components/import-toast.tsx +++ b/packages/compass-import-export/src/components/import-toast.tsx @@ -15,24 +15,32 @@ export function showInProgressToast({ fileName, cancelImport, docsWritten, + numErrors, bytesProcessed, bytesTotal, }: { fileName: string; cancelImport: () => void; docsWritten: number; + numErrors: number; bytesProcessed: number; bytesTotal: number; }) { // Update the toast with the new progress. const progress = bytesTotal ? bytesProcessed / bytesTotal : undefined; + + let statusMessage = `${docsWritten} document${ + docsWritten !== 1 ? 's' : '' + } written.`; + if (numErrors) { + statusMessage += ` ${numErrors} error${numErrors !== 1 ? 's' : ''}.`; + } + openToast(importToastId, { title: `Importing ${path.basename(fileName)}…`, description: ( diff --git a/packages/compass-import-export/src/import/import-csv.spec.ts b/packages/compass-import-export/src/import/import-csv.spec.ts index 5a06e8c9efc..c498c5b4f0e 100644 --- a/packages/compass-import-export/src/import/import-csv.spec.ts +++ b/packages/compass-import-export/src/import/import-csv.spec.ts @@ -781,48 +781,39 @@ describe('importCSV', function () { expect(result.dbStats.insertedCount).to.equal(0); expect(progressCallback.callCount).to.equal(3); - expect(errorCallback.callCount).to.equal(4); // yes one more MongoBulkWriteError than items in the batch + expect(errorCallback.callCount).to.equal(1); // once for the batch - const expectedErrors = [ + const expectedErrors: ErrorJSON[] = [ { - // first one speems to relate to the batch as there's no index name: 'MongoBulkWriteError', message: 'Document failed validation', code: 121, - }, - { - name: 'WriteConcernError', - message: 'Document failed validation', - index: 0, - code: 121, - }, - { - name: 'WriteConcernError', - message: 'Document failed validation', - index: 1, - code: 121, - }, - { - name: 'WriteConcernError', - message: 'Document failed validation', - index: 2, - code: 121, + numErrors: 3, }, ]; - const errorLog = await fs.promises.readFile(output.path, 'utf8'); - expect(errorLog).to.equal( - expectedErrors.map((e) => JSON.stringify(e)).join(os.EOL) + os.EOL - ); - const errors = errorCallback.args.map((args) => args[0]); - - try { - expect(errors).to.deep.equal(expectedErrors); - } catch (err: any) { - console.log(JSON.stringify({ errors, expectedErrors }, null, 2)); - throw err; - } + expect(errors).to.deep.equal(expectedErrors); + + // the log file has one for each error in the bulk write too + expectedErrors.push({ + name: 'WriteConcernError', + message: 'Document failed validation', + index: 0, + code: 121, + }); + expectedErrors.push({ + name: 'WriteConcernError', + message: 'Document failed validation', + index: 1, + code: 121, + }); + expectedErrors.push({ + name: 'WriteConcernError', + message: 'Document failed validation', + index: 2, + code: 121, + }); const errorsText = await fs.promises.readFile(output.path, 'utf8'); expect(errorsText).to.equal(formatErrorLines(expectedErrors)); diff --git a/packages/compass-import-export/src/import/import-csv.ts b/packages/compass-import-export/src/import/import-csv.ts index 50f111b4720..bb304542c38 100644 --- a/packages/compass-import-export/src/import/import-csv.ts +++ b/packages/compass-import-export/src/import/import-csv.ts @@ -131,7 +131,8 @@ export async function importCSV({ const collectionStream = createCollectionWriteStream( dataService, ns, - stopOnErrors ?? false + stopOnErrors ?? false, + errorCallback ); const parseStream = Papa.parse(Papa.NODE_STREAM_INPUT, { @@ -166,7 +167,6 @@ export async function importCSV({ await processWriteStreamErrors({ collectionStream, output, - errorCallback, }); const result = makeImportResult( @@ -194,7 +194,6 @@ export async function importCSV({ await processWriteStreamErrors({ collectionStream, output, - errorCallback, }); const result = makeImportResult( diff --git a/packages/compass-import-export/src/import/import-json.spec.ts b/packages/compass-import-export/src/import/import-json.spec.ts index 51525e6ca26..74c20d55fb4 100644 --- a/packages/compass-import-export/src/import/import-json.spec.ts +++ b/packages/compass-import-export/src/import/import-json.spec.ts @@ -555,33 +555,34 @@ describe('importJSON', function () { expect(result.dbStats.insertedCount).to.equal(0); expect(progressCallback.callCount).to.equal(2); - expect(errorCallback.callCount).to.equal(3); // yes one more MongoBulkWriteError than items in the batch + expect(errorCallback.callCount).to.equal(1); // once for the batch - const expectedErrors = [ + const expectedErrors: ErrorJSON[] = [ { - // first one speems to relate to the batch as there's no index name: 'MongoBulkWriteError', message: 'Document failed validation', code: 121, - }, - { - name: 'WriteConcernError', - message: 'Document failed validation', - index: 0, - code: 121, - }, - { - name: 'WriteConcernError', - message: 'Document failed validation', - index: 1, - code: 121, + numErrors: 2, }, ]; const errors = errorCallback.args.map((args) => args[0]); - expect(errors).to.deep.equal(expectedErrors); + // the log file has one for each error in the bulk write too + expectedErrors.push({ + name: 'WriteConcernError', + message: 'Document failed validation', + index: 0, + code: 121, + }); + expectedErrors.push({ + name: 'WriteConcernError', + message: 'Document failed validation', + index: 1, + code: 121, + }); + const errorsText = await fs.promises.readFile(output.path, 'utf8'); expect(errorsText).to.equal(formatErrorLines(expectedErrors)); }); diff --git a/packages/compass-import-export/src/import/import-json.ts b/packages/compass-import-export/src/import/import-json.ts index aec7e66b026..344b78b595f 100644 --- a/packages/compass-import-export/src/import/import-json.ts +++ b/packages/compass-import-export/src/import/import-json.ts @@ -100,7 +100,8 @@ export async function importJSON({ const collectionStream = createCollectionWriteStream( dataService, ns, - stopOnErrors ?? false + stopOnErrors ?? false, + errorCallback ); const parserStreams = []; @@ -132,7 +133,6 @@ export async function importJSON({ await processWriteStreamErrors({ collectionStream, output, - errorCallback, }); return makeImportResult( @@ -156,7 +156,6 @@ export async function importJSON({ await processWriteStreamErrors({ collectionStream, output, - errorCallback, }); return makeImportResult(collectionStream, numProcessed, docStatsStream); diff --git a/packages/compass-import-export/src/import/import-types.ts b/packages/compass-import-export/src/import/import-types.ts index 9b7912070b4..5dfb678cb02 100644 --- a/packages/compass-import-export/src/import/import-types.ts +++ b/packages/compass-import-export/src/import/import-types.ts @@ -22,6 +22,7 @@ export type ErrorJSON = { code?: string | number; op?: any; errorInfo?: Document; + numErrors?: number; }; export type ImportProgress = { diff --git a/packages/compass-import-export/src/import/import-utils.ts b/packages/compass-import-export/src/import/import-utils.ts index ef363154679..d61448af016 100644 --- a/packages/compass-import-export/src/import/import-utils.ts +++ b/packages/compass-import-export/src/import/import-utils.ts @@ -46,13 +46,18 @@ export function errorToJSON(error: any): ErrorJSON { } } + // For bulk write errors we include the number of errors that were in the + // batch. So one error actually maps to (potentially) many failed documents. + if (error.writeErrors && Array.isArray(error.writeErrors)) { + obj.numErrors = error.writeErrors.length; + } + return obj; } export async function processWriteStreamErrors({ collectionStream, output, - errorCallback, }: { collectionStream: WritableCollectionStream; output?: Writable; @@ -68,9 +73,9 @@ export async function processWriteStreamErrors({ .concat(stats.writeConcernErrors); for (const error of allErrors) { + debug('write error', error); + const transformedError = errorToJSON(error); - debug('write error', transformedError); - errorCallback?.(transformedError); if (!output) { continue; diff --git a/packages/compass-import-export/src/index.ts b/packages/compass-import-export/src/index.ts index 9ecfb399487..1f431f71572 100644 --- a/packages/compass-import-export/src/index.ts +++ b/packages/compass-import-export/src/index.ts @@ -1,5 +1,6 @@ import { registerHadronPlugin } from 'hadron-app-registry'; import { + connectionInfoAccessLocator, dataServiceLocator, type DataServiceLocator, } from '@mongodb-js/compass-connections/provider'; @@ -27,6 +28,7 @@ export const ImportPlugin = registerHadronPlugin( workspaces: workspacesServiceLocator, preferences: preferencesLocator, logger: createLoggerAndTelemetryLocator('COMPASS-IMPORT-UI'), + connectionInfoAccess: connectionInfoAccessLocator, } ); diff --git a/packages/compass-import-export/src/modules/import.ts b/packages/compass-import-export/src/modules/import.ts index cd59d3c6495..f8a75fba1ac 100644 --- a/packages/compass-import-export/src/modules/import.ts +++ b/packages/compass-import-export/src/modules/import.ts @@ -191,6 +191,7 @@ export const startImport = (): ImportThunkAction> => { globalAppRegistry: appRegistry, workspaces, logger: { log, mongoLogId, track, debug }, + connectionInfoAccess, } ) => { const startTime = Date.now(); @@ -274,7 +275,12 @@ export const startImport = (): ImportThunkAction> => { let promise: Promise; + let numErrors = 0; const errorCallback = (err: ErrorJSON) => { + // For bulk write errors we'll get one callback for the whole batch and + // then numErrors is the number of documents that failed for that batch. + // Usually but not necessarily the entire batch. + numErrors += err.numErrors ?? 1; if (errors.length < 5) { // Only store the first few errors in memory. // The log file tracks all of them. @@ -294,6 +300,7 @@ export const startImport = (): ImportThunkAction> => { showInProgressToast({ cancelImport: () => dispatch(cancelImport()), docsWritten, + numErrors, fileName, bytesProcessed, bytesTotal: fileSize, @@ -404,7 +411,11 @@ export const startImport = (): ImportThunkAction> => { } else { const onReviewDocumentsClick = appRegistry ? () => { - workspaces.openCollectionWorkspace(ns, { newTab: true }); + const { id: connectionId } = + connectionInfoAccess.getCurrentConnectionInfo(); + workspaces.openCollectionWorkspace(connectionId, ns, { + newTab: true, + }); } : undefined; diff --git a/packages/compass-import-export/src/stores/import-store.ts b/packages/compass-import-export/src/stores/import-store.ts index e510c4b97a2..a054405176c 100644 --- a/packages/compass-import-export/src/stores/import-store.ts +++ b/packages/compass-import-export/src/stores/import-store.ts @@ -7,12 +7,14 @@ import type { DataService } from 'mongodb-data-service'; import { cancelImport, importReducer, openImport } from '../modules/import'; import type { WorkspacesService } from '@mongodb-js/compass-workspaces/provider'; import type { LoggerAndTelemetry } from '@mongodb-js/compass-logging/provider'; +import type { ConnectionInfoAccess } from '@mongodb-js/compass-connections/provider'; export type ImportPluginServices = { globalAppRegistry: AppRegistry; dataService: Pick; workspaces: WorkspacesService; logger: LoggerAndTelemetry; + connectionInfoAccess: ConnectionInfoAccess; }; export function configureStore(services: ImportPluginServices) { @@ -37,13 +39,20 @@ export type ImportThunkAction = ThunkAction< export function activatePlugin( _: unknown, - { globalAppRegistry, dataService, workspaces, logger }: ImportPluginServices + { + globalAppRegistry, + dataService, + workspaces, + logger, + connectionInfoAccess, + }: ImportPluginServices ) { const store = configureStore({ globalAppRegistry, dataService, workspaces, logger, + connectionInfoAccess, }); const onOpenImport = ({ diff --git a/packages/compass-import-export/src/utils/collection-stream.ts b/packages/compass-import-export/src/utils/collection-stream.ts index 2a8a6fb4991..41fcb5e7423 100644 --- a/packages/compass-import-export/src/utils/collection-stream.ts +++ b/packages/compass-import-export/src/utils/collection-stream.ts @@ -10,6 +10,8 @@ import type { BulkWriteResult, } from 'mongodb'; import type { DataService } from 'mongodb-data-service'; +import type { ErrorJSON } from '../import/import-types'; +import { errorToJSON } from '../import/import-utils'; import { createDebug } from './logger'; @@ -88,11 +90,13 @@ export class WritableCollectionStream extends Writable { _batchCounter: number; _stats: CollectionStreamStats; _errors: CollectionStreamProgressError[]; + errorCallback?: (error: ErrorJSON) => void; constructor( dataService: Pick, ns: string, - stopOnErrors: boolean + stopOnErrors: boolean, + errorCallback?: (error: ErrorJSON) => void ) { super({ objectMode: true }); this.dataService = dataService; @@ -117,6 +121,7 @@ export class WritableCollectionStream extends Writable { }; this._errors = []; + this.errorCallback = errorCallback; } _write( @@ -181,7 +186,8 @@ export class WritableCollectionStream extends Writable { await this.dataService.insertOne(this.ns, doc); insertedCount += 1; } catch (insertOneByOneError: any) { - this._errors.push(insertOneByOneError as Error); + this._errors.push(insertOneByOneError); + this.errorCallback?.(errorToJSON(insertOneByOneError)); if (this.stopOnErrors) { break; @@ -195,7 +201,9 @@ export class WritableCollectionStream extends Writable { // will not return any result, but server might write some docs and bulk // result can still be accessed on the error instance result = (bulkWriteError as MongoBulkWriteError).result; + this._errors.push(bulkWriteError); + this.errorCallback?.(errorToJSON(bulkWriteError)); } } @@ -281,7 +289,13 @@ export class WritableCollectionStream extends Writable { export const createCollectionWriteStream = function ( dataService: Pick, ns: string, - stopOnErrors: boolean + stopOnErrors: boolean, + errorCallback?: (error: ErrorJSON) => void ) { - return new WritableCollectionStream(dataService, ns, stopOnErrors); + return new WritableCollectionStream( + dataService, + ns, + stopOnErrors, + errorCallback + ); }; diff --git a/packages/compass-import-export/src/utils/get-shell-js-string.ts b/packages/compass-import-export/src/utils/get-shell-js-string.ts index 29f0b0cee67..c62e05fbd3e 100644 --- a/packages/compass-import-export/src/utils/get-shell-js-string.ts +++ b/packages/compass-import-export/src/utils/get-shell-js-string.ts @@ -9,7 +9,6 @@ import type { ExportAggregation, ExportQuery } from '../export/export-types'; const codeFormatting: Partial = { singleQuote: true, - trailingComma: 'none', }; export function queryAsShellJSString({ diff --git a/packages/compass-import-export/test/docs/all-bson-types.exported.canonical.ejson b/packages/compass-import-export/test/docs/all-bson-types.exported.canonical.ejson index d80f7c75006..855094ded6a 100644 --- a/packages/compass-import-export/test/docs/all-bson-types.exported.canonical.ejson +++ b/packages/compass-import-export/test/docs/all-bson-types.exported.canonical.ejson @@ -121,7 +121,7 @@ }, "compressedTimeSeries": { "$binary": { - "base64": "Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09", + "base64": "CQCKW/8XjAEAAIfx//////////H/////////AQAAAAAAAABfAAAAAAAAAAEAAAAAAAAAAgAAAAAAAAAHAAAAAAAAAA4AAAAAAAAAAA==", "subType": "07" } }, diff --git a/packages/compass-import-export/test/docs/all-bson-types.exported.csv b/packages/compass-import-export/test/docs/all-bson-types.exported.csv index 138c7c3d1dd..f3f44b790d7 100644 --- a/packages/compass-import-export/test/docs/all-bson-types.exported.csv +++ b/packages/compass-import-export/test/docs/all-bson-types.exported.csv @@ -1,2 +1,2 @@ _id,double,string,object.key,array[0],array[1],array[2],binData,objectId,boolean,date,null,regex,javascript,symbol,javascriptWithScope,int,timestamp,long,decimal,minKey,maxKey,binaries.generic,binaries.functionData,binaries.binaryOld,binaries.uuidOld,binaries.uuid,binaries.md5,binaries.encrypted,binaries.compressedTimeSeries,binaries.custom,dbRef -1,1.2,"Hello, world!",value,1,2,3,AQID,642d766c7300158b1f22e975,true,2023-04-05T13:25:08.445Z,,/pattern/i,"{""$code"":""function() {}""}","{""$symbol"":""symbol""}","{""$code"":""function() {}"",""$scope"":{""foo"":{""$numberInt"":""1""},""bar"":""a""}}",12345,7218556297505931265,123456789123456784,5.477284286264328586719275128128001E-4088,$MinKey,$MaxKey,AQID,Ly84PQ==,Ly84PQ==,Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09,aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa,Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09,Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09,Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09,Ly84PQ==,"{""$ref"":""namespace"",""$id"":{""$oid"":""642d76b4b7ebfab15d3c4a78""}}" +1,1.2,"Hello, world!",value,1,2,3,AQID,642d766c7300158b1f22e975,true,2023-04-05T13:25:08.445Z,,/pattern/i,"{""$code"":""function() {}""}","{""$symbol"":""symbol""}","{""$code"":""function() {}"",""$scope"":{""foo"":{""$numberInt"":""1""},""bar"":""a""}}",12345,7218556297505931265,123456789123456784,5.477284286264328586719275128128001E-4088,$MinKey,$MaxKey,AQID,Ly84PQ==,Ly84PQ==,Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09,aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa,Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09,Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09,CQCKW/8XjAEAAIfx//////////H/////////AQAAAAAAAABfAAAAAAAAAAEAAAAAAAAAAgAAAAAAAAAHAAAAAAAAAA4AAAAAAAAAAA==,Ly84PQ==,"{""$ref"":""namespace"",""$id"":{""$oid"":""642d76b4b7ebfab15d3c4a78""}}" diff --git a/packages/compass-import-export/test/docs/all-bson-types.exported.default.ejson b/packages/compass-import-export/test/docs/all-bson-types.exported.default.ejson index f3694b1ac99..de134967514 100644 --- a/packages/compass-import-export/test/docs/all-bson-types.exported.default.ejson +++ b/packages/compass-import-export/test/docs/all-bson-types.exported.default.ejson @@ -107,7 +107,7 @@ }, "compressedTimeSeries": { "$binary": { - "base64": "Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09", + "base64": "CQCKW/8XjAEAAIfx//////////H/////////AQAAAAAAAABfAAAAAAAAAAEAAAAAAAAAAgAAAAAAAAAHAAAAAAAAAA4AAAAAAAAAAA==", "subType": "07" } }, diff --git a/packages/compass-import-export/test/docs/all-bson-types.exported.relaxed.ejson b/packages/compass-import-export/test/docs/all-bson-types.exported.relaxed.ejson index eb39d9e815b..f0e2c247b69 100644 --- a/packages/compass-import-export/test/docs/all-bson-types.exported.relaxed.ejson +++ b/packages/compass-import-export/test/docs/all-bson-types.exported.relaxed.ejson @@ -105,7 +105,7 @@ }, "compressedTimeSeries": { "$binary": { - "base64": "Yy8vU1pFU3pUR21RNk9mUjM4QTExQT09", + "base64": "CQCKW/8XjAEAAIfx//////////H/////////AQAAAAAAAABfAAAAAAAAAAEAAAAAAAAAAgAAAAAAAAAHAAAAAAAAAA4AAAAAAAAAAA==", "subType": "07" } }, diff --git a/packages/compass-import-export/test/docs/all-bson-types.js b/packages/compass-import-export/test/docs/all-bson-types.js index 20f08b1765d..d83e13eac89 100644 --- a/packages/compass-import-export/test/docs/all-bson-types.js +++ b/packages/compass-import-export/test/docs/all-bson-types.js @@ -51,7 +51,10 @@ export default [ md5: new Binary(Buffer.from('c//SZESzTGmQ6OfR38A11A=='), 5), // 5 encrypted: new Binary(Buffer.from('c//SZESzTGmQ6OfR38A11A=='), 6), // 6 compressedTimeSeries: new Binary( - Buffer.from('c//SZESzTGmQ6OfR38A11A=='), + Buffer.from( + 'CQCKW/8XjAEAAIfx//////////H/////////AQAAAAAAAABfAAAAAAAAAAEAAAAAAAAAAgAAAAAAAAAHAAAAAAAAAA4AAAAAAAAAAA==', + 'base64' + ), 7 ), // 7 custom: new Binary(Buffer.from('//8='), 128), // 128 diff --git a/packages/compass-indexes/.eslintrc.js b/packages/compass-indexes/.eslintrc.js index f06f8fc6013..b54d314ab18 100644 --- a/packages/compass-indexes/.eslintrc.js +++ b/packages/compass-indexes/.eslintrc.js @@ -1,3 +1,4 @@ +'use strict'; module.exports = { root: true, extends: ['@mongodb-js/eslint-config-compass/plugin'], diff --git a/packages/compass-indexes/.mocharc.js b/packages/compass-indexes/.mocharc.js index a7e53abc444..61091edba73 100644 --- a/packages/compass-indexes/.mocharc.js +++ b/packages/compass-indexes/.mocharc.js @@ -1 +1,2 @@ +'use strict'; module.exports = require('@mongodb-js/mocha-config-compass/compass-plugin'); diff --git a/packages/compass-indexes/package.json b/packages/compass-indexes/package.json index 703f29814c8..6bf36791c0e 100644 --- a/packages/compass-indexes/package.json +++ b/packages/compass-indexes/package.json @@ -6,7 +6,7 @@ "email": "compass@mongodb.com" }, "private": true, - "version": "5.23.1", + "version": "5.25.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -48,15 +48,15 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "chai": "^4.2.0", "depcheck": "^1.4.1", - "electron": "^28.2.10", + "electron": "^29.3.1", "electron-mocha": "^12.2.0", "eslint": "^7.25.0", "mocha": "^10.2.0", @@ -67,23 +67,23 @@ "xvfb-maybe": "^0.2.1" }, "dependencies": { - "@mongodb-js/compass-app-stores": "^7.10.1", - "@mongodb-js/compass-collection": "^4.23.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-editor": "^0.21.1", - "@mongodb-js/compass-field-store": "^9.0.19", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-workspaces": "^0.5.1", - "@mongodb-js/connection-storage": "^0.8.1", + "@mongodb-js/compass-app-stores": "^7.12.0", + "@mongodb-js/compass-collection": "^4.25.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-editor": "^0.23.0", + "@mongodb-js/compass-field-store": "^9.2.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-workspaces": "^0.7.0", + "@mongodb-js/connection-storage": "^0.10.0", "@mongodb-js/mongodb-constants": "^0.9.0", "bson": "^6.6.0", - "compass-preferences-model": "^2.18.1", + "compass-preferences-model": "^2.20.0", "ejson-shell-parser": "^2.0.1", - "hadron-app-registry": "^9.1.8", + "hadron-app-registry": "^9.1.10", "lodash": "^4.17.21", "mongodb": "^6.5.0", - "mongodb-data-service": "^22.18.1", + "mongodb-data-service": "^22.19.1", "mongodb-query-parser": "^4.1.0", "numeral": "^2.0.6", "react": "^17.0.2", diff --git a/packages/compass-indexes/src/components/indexes/indexes.tsx b/packages/compass-indexes/src/components/indexes/indexes.tsx index d34379c6791..701f508d86a 100644 --- a/packages/compass-indexes/src/components/indexes/indexes.tsx +++ b/packages/compass-indexes/src/components/indexes/indexes.tsx @@ -25,7 +25,7 @@ import { } from '../search-indexes-modals'; import type { IndexView } from '../../modules/index-view'; import { usePreference } from 'compass-preferences-model/provider'; -import { useConnectionInfo } from '@mongodb-js/connection-storage/provider'; +import { useConnectionInfo } from '@mongodb-js/compass-connections/provider'; import { getAtlasSearchIndexesLink } from '../../utils/atlas-search-indexes-link'; // This constant is used as a trigger to show an insight whenever number of diff --git a/packages/compass-indexes/src/components/search-indexes-table/search-indexes-table.tsx b/packages/compass-indexes/src/components/search-indexes-table/search-indexes-table.tsx index 3b9ba29ecc1..7ebac5d3a80 100644 --- a/packages/compass-indexes/src/components/search-indexes-table/search-indexes-table.tsx +++ b/packages/compass-indexes/src/components/search-indexes-table/search-indexes-table.tsx @@ -36,6 +36,7 @@ import SearchIndexActions from './search-index-actions'; import { ZeroGraphic } from './zero-graphic'; import type { RootState } from '../../modules'; import BadgeWithIconLink from '../indexes-table/badge-with-icon-link'; +import { useConnectionInfo } from '@mongodb-js/compass-connections/provider'; export const POLLING_INTERVAL = 5000; @@ -284,6 +285,7 @@ export const SearchIndexesTable: React.FunctionComponent< pollingInterval = POLLING_INTERVAL, }) => { const { openCollectionWorkspace } = useOpenWorkspace(); + const { id: connectionId } = useConnectionInfo(); useEffect(() => { const id = setInterval(onPollIndexes, pollingInterval); @@ -323,7 +325,7 @@ export const SearchIndexesTable: React.FunctionComponent< onDropIndex={onDropIndex} onEditIndex={onEditIndex} onRunAggregateIndex={(name) => { - openCollectionWorkspace(namespace, { + openCollectionWorkspace(connectionId, namespace, { newTab: true, ...(isVectorSearchIndex ? { @@ -352,7 +354,14 @@ export const SearchIndexesTable: React.FunctionComponent< ), }; }), - [indexes, namespace, onDropIndex, onEditIndex, openCollectionWorkspace] + [ + connectionId, + indexes, + namespace, + onDropIndex, + onEditIndex, + openCollectionWorkspace, + ] ); if (!isReadyStatus(status)) { diff --git a/packages/compass-indexes/src/modules/description.js b/packages/compass-indexes/src/modules/description.js index d5cff7d0b63..98ab42abff0 100644 --- a/packages/compass-indexes/src/modules/description.js +++ b/packages/compass-indexes/src/modules/description.js @@ -1,3 +1,4 @@ +'use strict'; /** * The module action prefix. */ diff --git a/packages/compass-indexes/src/modules/is-writable.js b/packages/compass-indexes/src/modules/is-writable.js index c0c1e88830c..893c35d7b10 100644 --- a/packages/compass-indexes/src/modules/is-writable.js +++ b/packages/compass-indexes/src/modules/is-writable.js @@ -1,3 +1,4 @@ +'use strict'; /** * The module action prefix. */ diff --git a/packages/compass-indexes/src/modules/namespace.js b/packages/compass-indexes/src/modules/namespace.js index 1677d898f72..c7b8152f960 100644 --- a/packages/compass-indexes/src/modules/namespace.js +++ b/packages/compass-indexes/src/modules/namespace.js @@ -1,3 +1,4 @@ +'use strict'; /** * The prefix. */ diff --git a/packages/compass-intercom/.eslintrc.js b/packages/compass-intercom/.eslintrc.js index e4cf824b6ac..9c3ab95632f 100644 --- a/packages/compass-intercom/.eslintrc.js +++ b/packages/compass-intercom/.eslintrc.js @@ -1,3 +1,4 @@ +'use strict'; module.exports = { root: true, extends: ['@mongodb-js/eslint-config-compass'], diff --git a/packages/compass-intercom/.mocharc.js b/packages/compass-intercom/.mocharc.js index 30aecfb78c3..5a33f216327 100644 --- a/packages/compass-intercom/.mocharc.js +++ b/packages/compass-intercom/.mocharc.js @@ -1 +1,2 @@ +'use strict'; module.exports = require('@mongodb-js/mocha-config-compass/react'); diff --git a/packages/compass-intercom/package.json b/packages/compass-intercom/package.json index 23c74fe3e46..2cd01fcd973 100644 --- a/packages/compass-intercom/package.json +++ b/packages/compass-intercom/package.json @@ -13,7 +13,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "0.2.1", + "version": "0.4.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -50,10 +50,10 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/sinon-chai": "^3.2.5", @@ -67,7 +67,7 @@ "typescript": "^5.0.4" }, "dependencies": { - "compass-preferences-model": "^2.18.1", - "@mongodb-js/compass-logging": "^1.2.14" + "compass-preferences-model": "^2.20.0", + "@mongodb-js/compass-logging": "^1.2.16" } } diff --git a/packages/compass-logging/.eslintrc.js b/packages/compass-logging/.eslintrc.js index e4cf824b6ac..9c3ab95632f 100644 --- a/packages/compass-logging/.eslintrc.js +++ b/packages/compass-logging/.eslintrc.js @@ -1,3 +1,4 @@ +'use strict'; module.exports = { root: true, extends: ['@mongodb-js/eslint-config-compass'], diff --git a/packages/compass-logging/.mocharc.js b/packages/compass-logging/.mocharc.js index 7e473d17b76..e7eaccd61fa 100644 --- a/packages/compass-logging/.mocharc.js +++ b/packages/compass-logging/.mocharc.js @@ -1 +1,2 @@ +'use strict'; module.exports = require('@mongodb-js/mocha-config-compass'); diff --git a/packages/compass-logging/package.json b/packages/compass-logging/package.json index 1a7e19affe2..68b1e430e7c 100644 --- a/packages/compass-logging/package.json +++ b/packages/compass-logging/package.json @@ -13,7 +13,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "1.2.14", + "version": "1.2.16", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -52,17 +52,17 @@ }, "dependencies": { "debug": "^4.3.4", - "hadron-app-registry": "^9.1.8", - "hadron-ipc": "^3.2.12", + "hadron-app-registry": "^9.1.10", + "hadron-ipc": "^3.2.14", "is-electron-renderer": "^2.0.1", "mongodb-log-writer": "^1.3.0", "react": "^17.0.2" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/chai": "^4.2.21", "@types/debug": "^4.1.9", "@types/mocha": "^9.0.0", diff --git a/packages/compass-maybe-protect-connection-string/.eslintrc.js b/packages/compass-maybe-protect-connection-string/.eslintrc.js index e4cf824b6ac..9c3ab95632f 100644 --- a/packages/compass-maybe-protect-connection-string/.eslintrc.js +++ b/packages/compass-maybe-protect-connection-string/.eslintrc.js @@ -1,3 +1,4 @@ +'use strict'; module.exports = { root: true, extends: ['@mongodb-js/eslint-config-compass'], diff --git a/packages/compass-maybe-protect-connection-string/.mocharc.js b/packages/compass-maybe-protect-connection-string/.mocharc.js index 7e473d17b76..e7eaccd61fa 100644 --- a/packages/compass-maybe-protect-connection-string/.mocharc.js +++ b/packages/compass-maybe-protect-connection-string/.mocharc.js @@ -1 +1,2 @@ +'use strict'; module.exports = require('@mongodb-js/mocha-config-compass'); diff --git a/packages/compass-maybe-protect-connection-string/package.json b/packages/compass-maybe-protect-connection-string/package.json index d6236204f04..eeb46163acb 100644 --- a/packages/compass-maybe-protect-connection-string/package.json +++ b/packages/compass-maybe-protect-connection-string/package.json @@ -13,7 +13,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "0.16.1", + "version": "0.18.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -50,14 +50,14 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "dependencies": { - "compass-preferences-model": "^2.18.1", + "compass-preferences-model": "^2.20.0", "mongodb-connection-string-url": "^2.6.0" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/sinon-chai": "^3.2.5", diff --git a/packages/compass-preferences-model/.eslintrc.js b/packages/compass-preferences-model/.eslintrc.js index e4cf824b6ac..9c3ab95632f 100644 --- a/packages/compass-preferences-model/.eslintrc.js +++ b/packages/compass-preferences-model/.eslintrc.js @@ -1,3 +1,4 @@ +'use strict'; module.exports = { root: true, extends: ['@mongodb-js/eslint-config-compass'], diff --git a/packages/compass-preferences-model/.mocharc.js b/packages/compass-preferences-model/.mocharc.js index 30aecfb78c3..5a33f216327 100644 --- a/packages/compass-preferences-model/.mocharc.js +++ b/packages/compass-preferences-model/.mocharc.js @@ -1 +1,2 @@ +'use strict'; module.exports = require('@mongodb-js/mocha-config-compass/react'); diff --git a/packages/compass-preferences-model/package.json b/packages/compass-preferences-model/package.json index eb00b92ab08..b64310ece1e 100644 --- a/packages/compass-preferences-model/package.json +++ b/packages/compass-preferences-model/package.json @@ -2,7 +2,7 @@ "name": "compass-preferences-model", "description": "Compass preferences model", "author": "Lucas Hrabovsky ", - "version": "2.18.1", + "version": "2.20.0", "bugs": { "url": "https://jira.mongodb.org/projects/COMPASS/issues", "email": "compass@mongodb.com" @@ -48,11 +48,11 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "dependencies": { - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-user-data": "^0.1.17", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-user-data": "^0.1.19", "bson": "^6.6.0", - "hadron-app-registry": "^9.1.8", - "hadron-ipc": "^3.2.12", + "hadron-app-registry": "^9.1.10", + "hadron-ipc": "^3.2.14", "js-yaml": "^4.1.0", "lodash": "^4.17.21", "react": "^17.0.2", @@ -60,8 +60,8 @@ "zod": "^3.22.3" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.16", - "@mongodb-js/mocha-config-compass": "^1.3.6", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", "@testing-library/react": "^12.1.4", "@types/js-yaml": "^4.0.5", "@types/yargs-parser": "21.0.0", diff --git a/packages/compass-preferences-model/provider.js b/packages/compass-preferences-model/provider.js index a9d6fd5cfc3..5b56677514c 100644 --- a/packages/compass-preferences-model/provider.js +++ b/packages/compass-preferences-model/provider.js @@ -1 +1,2 @@ +'use strict'; module.exports = require('./dist/provider'); diff --git a/packages/compass-preferences-model/src/feature-flags.ts b/packages/compass-preferences-model/src/feature-flags.ts index 4ac069ecccb..75a6f59ce8d 100644 --- a/packages/compass-preferences-model/src/feature-flags.ts +++ b/packages/compass-preferences-model/src/feature-flags.ts @@ -17,8 +17,6 @@ export type FeatureFlags = { enableOidc: boolean; // Not capitalized "OIDC" for spawn arg casing. newExplainPlan: boolean; showInsights: boolean; - enableBulkUpdateOperations: boolean; - enableBulkDeleteOperations: boolean; enableRenameCollectionModal: boolean; enableNewMultipleConnectionSystem: boolean; }; @@ -53,29 +51,6 @@ export const featureFlags: Required<{ }, }, - /** - * Feature flag bulk updates - * Epic: COMPASS-6671 - */ - enableBulkUpdateOperations: { - stage: 'released', - description: { - short: 'Enable bulk update operations.', - long: 'Allows editing all documents given a query.', - }, - }, - /** - * Feature flag for bulk deletes. - * Epic: COMPASS-6671 - */ - enableBulkDeleteOperations: { - stage: 'released', - description: { - short: 'Enable bulk delete operations.', - long: 'Allows deleting all documents given a query.', - }, - }, - /** * Feature flag for the rename collection modal. */ diff --git a/packages/compass-preferences-model/src/preferences-schema.ts b/packages/compass-preferences-model/src/preferences-schema.ts index 321dd7e4341..d12e2a1b5d9 100644 --- a/packages/compass-preferences-model/src/preferences-schema.ts +++ b/packages/compass-preferences-model/src/preferences-schema.ts @@ -54,6 +54,7 @@ export type UserConfigurablePreferences = PermanentFeatureFlags & enableImportExport: boolean; enableAggregationBuilderRunPipeline: boolean; enableAggregationBuilderExtraOptions: boolean; + enableGenAISampleDocumentPassing: boolean; enableHackoladeBanner: boolean; enablePerformanceAdvisorBanner: boolean; maximumNumberOfActiveConnections?: number; @@ -706,6 +707,19 @@ export const storedUserPreferencesProps: Required<{ type: 'boolean', }, + enableGenAISampleDocumentPassing: { + ui: true, + cli: true, + global: true, + description: { + short: + 'Enable sending sample field values with query and aggregation generation requests.', + long: 'Supplying sample field values improves the results from the AI.', + }, + validator: z.boolean().default(false), + type: 'boolean', + }, + enableHackoladeBanner: { ui: true, cli: true, diff --git a/packages/compass-preferences-model/src/provider.ts b/packages/compass-preferences-model/src/provider.ts index 393f2800ab4..db93d5cefae 100644 --- a/packages/compass-preferences-model/src/provider.ts +++ b/packages/compass-preferences-model/src/provider.ts @@ -8,3 +8,4 @@ export { export { capMaxTimeMSAtPreferenceLimit } from './maxtimems'; export { featureFlags } from './feature-flags'; export { getSettingDescription } from './preferences-schema'; +export type { AllPreferences } from './preferences-schema'; diff --git a/packages/compass-query-bar/.eslintrc.js b/packages/compass-query-bar/.eslintrc.js index f06f8fc6013..b54d314ab18 100644 --- a/packages/compass-query-bar/.eslintrc.js +++ b/packages/compass-query-bar/.eslintrc.js @@ -1,3 +1,4 @@ +'use strict'; module.exports = { root: true, extends: ['@mongodb-js/eslint-config-compass/plugin'], diff --git a/packages/compass-query-bar/.mocharc.js b/packages/compass-query-bar/.mocharc.js index a7e53abc444..61091edba73 100644 --- a/packages/compass-query-bar/.mocharc.js +++ b/packages/compass-query-bar/.mocharc.js @@ -1 +1,2 @@ +'use strict'; module.exports = require('@mongodb-js/mocha-config-compass/compass-plugin'); diff --git a/packages/compass-query-bar/package.json b/packages/compass-query-bar/package.json index 8aa86bf0359..1fe2a0dc9ac 100644 --- a/packages/compass-query-bar/package.json +++ b/packages/compass-query-bar/package.json @@ -6,7 +6,7 @@ "email": "compass@mongodb.com" }, "private": true, - "version": "8.25.1", + "version": "8.27.0", "homepage": "https://github.com/mongodb-js/compass", "license": "SSPL", "bugs": { @@ -48,15 +48,15 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "chai": "^4.2.0", "depcheck": "^1.4.1", - "electron": "^28.2.10", + "electron": "^29.3.1", "electron-mocha": "^12.2.0", "eslint": "^7.25.0", "mocha": "^10.2.0", @@ -67,26 +67,26 @@ "xvfb-maybe": "^0.2.1" }, "dependencies": { - "@mongodb-js/atlas-service": "^0.15.1", - "@mongodb-js/compass-app-stores": "^7.10.1", - "@mongodb-js/compass-collection": "^4.23.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-editor": "^0.21.1", - "@mongodb-js/compass-field-store": "^9.0.19", - "@mongodb-js/compass-generative-ai": "^0.8.1", - "@mongodb-js/compass-logging": "^1.2.14", + "@mongodb-js/atlas-service": "^0.17.0", + "@mongodb-js/compass-app-stores": "^7.12.0", + "@mongodb-js/compass-collection": "^4.25.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-editor": "^0.23.0", + "@mongodb-js/compass-field-store": "^9.2.0", + "@mongodb-js/compass-generative-ai": "^0.10.0", + "@mongodb-js/compass-logging": "^1.2.16", "@mongodb-js/mongodb-constants": "^0.9.0", - "@mongodb-js/my-queries-storage": "^0.5.1", + "@mongodb-js/my-queries-storage": "^0.7.0", "bson": "^6.6.0", - "compass-preferences-model": "^2.18.1", - "hadron-app-registry": "^9.1.8", + "compass-preferences-model": "^2.20.0", + "hadron-app-registry": "^9.1.10", "lodash": "^4.17.21", "mongodb": "^6.5.0", - "mongodb-instance-model": "^12.18.1", + "mongodb-instance-model": "^12.20.0", "mongodb-ns": "^2.4.0", "mongodb-query-parser": "^4.1.0", - "mongodb-query-util": "^2.1.9", + "mongodb-query-util": "^2.2.1", "mongodb-schema": "^12.1.0", "react": "^17.0.2", "react-redux": "^8.1.3", diff --git a/packages/compass-query-bar/src/components/option-editor.tsx b/packages/compass-query-bar/src/components/option-editor.tsx index 779eb8f26f4..394643f4ebc 100644 --- a/packages/compass-query-bar/src/components/option-editor.tsx +++ b/packages/compass-query-bar/src/components/option-editor.tsx @@ -93,6 +93,7 @@ type OptionEditorProps = { value?: string; ['data-testid']?: string; insights?: Signal | Signal[]; + disabled?: boolean; }; export const OptionEditor: React.FunctionComponent = ({ @@ -108,6 +109,7 @@ export const OptionEditor: React.FunctionComponent = ({ value = '', ['data-testid']: dataTestId, insights, + disabled = false, }) => { const showInsights = usePreference('showInsights'); const editorContainerRef = useRef(null); @@ -199,6 +201,7 @@ export const OptionEditor: React.FunctionComponent = ({ onFocus={onFocus} onPaste={onPaste} onBlur={onBlur} + readOnly={disabled} /> {showInsights && insights && (
diff --git a/packages/compass-query-bar/src/components/query-ai.spec.tsx b/packages/compass-query-bar/src/components/query-ai.spec.tsx index 9f60bb53ed5..a9e311f44a0 100644 --- a/packages/compass-query-bar/src/components/query-ai.spec.tsx +++ b/packages/compass-query-bar/src/components/query-ai.spec.tsx @@ -130,6 +130,7 @@ describe('QueryAI Component', function () { store.dispatch({ type: AIQueryActionTypes.AIQuerySucceeded, fields: mapQueryToFormFields({}, DEFAULT_FIELD_VALUES), + requestId: 'pineapple', }); expect(screen.queryByTestId(feedbackPopoverTextAreaId)).to.not.exist; @@ -153,6 +154,7 @@ describe('QueryAI Component', function () { event: 'AI Query Feedback', properties: { feedback: 'positive', + request_id: 'pineapple', text: 'this is the query I was looking for', }, }, diff --git a/packages/compass-query-bar/src/components/query-ai.tsx b/packages/compass-query-bar/src/components/query-ai.tsx index dc346f4c14e..8ccfbef2201 100644 --- a/packages/compass-query-bar/src/components/query-ai.tsx +++ b/packages/compass-query-bar/src/components/query-ai.tsx @@ -12,19 +12,21 @@ import { } from '../stores/ai-query-reducer'; import { useLoggerAndTelemetry } from '@mongodb-js/compass-logging/provider'; -const useOnSubmitFeedback = () => { +const useOnSubmitFeedback = (lastAIQueryRequestId: string | null) => { const logger = useLoggerAndTelemetry('AI-QUERY-UI'); return useCallback( (feedback: 'positive' | 'negative', text: string) => { const { log, mongoLogId, track } = logger; log.info(mongoLogId(1_001_000_224), 'AIQuery', 'AI query feedback', { feedback, + requestId: lastAIQueryRequestId, text, }); track('AI Query Feedback', () => ({ feedback, text, + request_id: lastAIQueryRequestId, })); openToast('query-ai-feedback-submitted', { @@ -33,19 +35,21 @@ const useOnSubmitFeedback = () => { timeout: 10_000, }); }, - [logger] + [logger, lastAIQueryRequestId] ); }; type QueryAIProps = Omit< React.ComponentProps, 'onSubmitFeedback' ->; +> & { + lastAIQueryRequestId: string | null; +}; function QueryAI(props: QueryAIProps) { // Don't show the feedback options if telemetry is disabled. const enableTelemetry = usePreference('trackUsageStatistics'); - const onSubmitFeedback = useOnSubmitFeedback(); + const onSubmitFeedback = useOnSubmitFeedback(props.lastAIQueryRequestId); return ( ; + disabled?: boolean; }; export const QueryBarRow: React.FunctionComponent = ({ queryOptionsLayout, onApply, placeholders, + disabled, }) => { return (
@@ -35,6 +37,7 @@ export const QueryBarRow: React.FunctionComponent = ({ id={`query-bar-option-input-${queryOptionsLayout}`} onApply={onApply} placeholder={placeholders?.[queryOptionsLayout]} + disabled={disabled} /> ) : ( queryOptionsLayout.map((optionName: QueryOption) => ( @@ -44,6 +47,7 @@ export const QueryBarRow: React.FunctionComponent = ({ id={`query-bar-option-input-${optionName}`} onApply={onApply} placeholder={placeholders?.[optionName]} + disabled={disabled} /> )) )} diff --git a/packages/compass-query-bar/src/components/query-bar.spec.tsx b/packages/compass-query-bar/src/components/query-bar.spec.tsx index febd8c01456..a1c4a79249f 100644 --- a/packages/compass-query-bar/src/components/query-bar.spec.tsx +++ b/packages/compass-query-bar/src/components/query-bar.spec.tsx @@ -1,6 +1,6 @@ import React from 'react'; import type { ComponentProps } from 'react'; -import { cleanup, render, screen } from '@testing-library/react'; +import { cleanup, render, screen, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { expect } from 'chai'; import sinon from 'sinon'; @@ -10,6 +10,7 @@ import { Provider } from '../stores/context'; import { configureStore } from '../stores/query-bar-store'; import type { QueryBarExtraArgs, RootState } from '../stores/query-bar-store'; import { toggleQueryOptions } from '../stores/query-bar-reducer'; +import { AIQueryActionTypes } from '../stores/ai-query-reducer'; import type { PreferencesAccess } from 'compass-preferences-model'; import { createSandboxFromDefaultPreferences } from 'compass-preferences-model'; import { mapQueryToFormFields } from '../utils/query'; @@ -51,7 +52,7 @@ describe('QueryBar Component', function () { } as QueryBarExtraArgs); store.dispatch(toggleQueryOptions(expanded)); - render( + const component = ( @@ -60,8 +61,8 @@ describe('QueryBar Component', function () { buttonLabel="Apply" onApply={noop} onReset={noop} - showExportToLanguageButton resultId="123" + showExportToLanguageButton {...props} /> @@ -69,6 +70,16 @@ describe('QueryBar Component', function () { ); + + const result = render(component); + + return { + ...result, + store, + rerender: () => { + result.rerender(component); + }, + }; }; beforeEach(async function () { @@ -122,27 +133,162 @@ describe('QueryBar Component', function () { it('renders the expanded inputs', function () { const queryInputs = screen.getAllByRole('textbox'); - expect(queryInputs.length).to.equal(7); + expect(queryInputs.length).to.equal(8); }); }); - describe('with one query option', function () { + describe('when rendered', function () { beforeEach(function () { renderQueryBar({ - queryOptionsLayout: ['project'], - expanded: true, onApply: onApplySpy, onReset: onResetSpy, }); }); - it('renders the expanded inputs', function () { + it('renders the filter input', function () { + const filterInput = screen.getByTestId('query-bar-option-filter-input'); + expect(filterInput).to.exist; + expect(filterInput).to.have.attribute( + 'id', + 'query-bar-option-input-filter' + ); + const queryInputs = screen.getAllByRole('textbox'); - expect(queryInputs.length).to.equal(2); + expect(queryInputs.length).to.equal(1); + }); + + it('renders the query history button', function () { + const queryHistoryButton = screen.queryByTestId(queryHistoryButtonId); + expect(queryHistoryButton).to.be.visible; + }); + + it('does not render the query history popover', function () { + const queryHistory = screen.queryByTestId(queryHistoryComponentTestId); + expect(queryHistory).to.not.exist; + }); + }); + + describe('when ai is ready', function () { + beforeEach(function () { + renderQueryBar( + { + queryOptionsLayout: ['project'], + expanded: true, + onApply: onApplySpy, + onReset: onResetSpy, + }, + {} + ); + }); + + it('query controls are enabled', function () { + expect( + screen + .getByTestId('query-bar-open-export-to-language-button') + .getAttribute('aria-disabled') + ).to.equal('false'); + expect( + screen + .getByTestId('query-bar-apply-filter-button') + .getAttribute('aria-disabled') + ).to.equal('false'); + expect( + screen + .getByTestId('query-bar-open-export-to-language-button') + .getAttribute('aria-disabled') + ).to.equal('false'); + expect( + screen + .getByTestId('query-bar-open-export-to-language-button') + .getAttribute('aria-disabled') + ).to.equal('false'); + }); + + it('editors are not readonly', function () { + expect( + within(screen.getByTestId('query-bar-option-filter-input')) + .getByRole('textbox') + .getAttribute('aria-readonly') + ).to.not.exist; + expect( + within(screen.getByTestId('query-bar-option-project-input')) + .getByRole('textbox') + .getAttribute('aria-readonly') + ).to.not.exist; + }); + }); + + describe('while ai is fetching', function () { + it('query controls are disabled', function () { + const { store, rerender } = renderQueryBar( + { + queryOptionsLayout: ['project'], + expanded: true, + onApply: onApplySpy, + onReset: onResetSpy, + }, + {} + ); + + store.dispatch({ + type: AIQueryActionTypes.AIQueryStarted, + requestId: 'pineapples', + }); + rerender(); + + expect( + screen + .getByTestId('query-bar-open-export-to-language-button') + .getAttribute('aria-disabled') + ).to.equal('true'); + expect( + screen + .getByTestId('query-bar-apply-filter-button') + .getAttribute('aria-disabled') + ).to.equal('true'); + expect( + screen + .getByTestId('query-bar-open-export-to-language-button') + .getAttribute('aria-disabled') + ).to.equal('true'); + expect( + screen + .getByTestId('query-bar-open-export-to-language-button') + .getAttribute('aria-disabled') + ).to.equal('true'); + }); + + it('editors are readonly', function () { + const store = configureStore({}, { + preferences, + logger: createNoopLoggerAndTelemetry(), + } as QueryBarExtraArgs); + + store.dispatch({ + type: AIQueryActionTypes.AIQueryStarted, + requestId: 'pineapples', + }); + + render( + + + + ); + + expect( + within(screen.getByTestId('query-bar-option-filter-input')) + .getByRole('textbox') + .getAttribute('aria-readonly') + ).to.equal('true'); }); }); - describe('with ai enabled', function () { + describe('with ai is enabled', function () { beforeEach(async function () { await preferences.savePreferences({ enableGenAIFeatures: true, diff --git a/packages/compass-query-bar/src/components/query-bar.tsx b/packages/compass-query-bar/src/components/query-bar.tsx index 6f5b4225950..7ac05dceaec 100644 --- a/packages/compass-query-bar/src/components/query-bar.tsx +++ b/packages/compass-query-bar/src/components/query-bar.tsx @@ -69,13 +69,6 @@ const queryBarFirstRowStyles = css({ gap: spacing[2], }); -const moreOptionsContainerStyles = css({ - // We explicitly offset this element so we can use - // `alignItems: 'flex-start'` on the first row of the query bar. - paddingTop: 2, - paddingBottom: 2, -}); - const filterContainerStyles = css({ display: 'flex', position: 'relative', @@ -102,10 +95,6 @@ const queryAIContainerStyles = css({ margin: `0px ${spacing[2]}px`, }); -const visibleAIContainerStyles = css({ - marginTop: '2px', -}); - const QueryOptionsToggle = connect( (state: RootState) => { return { @@ -130,6 +119,10 @@ type QueryBarProps = { applyId: number; filterHasContent: boolean; showExplainButton?: boolean; + /** + * Used by Cloud only to hide the export to language functionality + * as it isn't supported. + */ showExportToLanguageButton?: boolean; valid: boolean; expanded: boolean; @@ -137,6 +130,7 @@ type QueryBarProps = { onExplain?: () => void; insights?: Signal | Signal[]; isAIInputVisible?: boolean; + isAIFetching?: boolean; onShowAIInputClick: () => void; onHideAIInputClick: () => void; }; @@ -151,6 +145,7 @@ export const QueryBar: React.FunctionComponent = ({ 'project', ['sort', 'maxTimeMS'], ['collation', 'skip', 'limit'], + 'hint', ], queryChanged, resultId, @@ -164,6 +159,7 @@ export const QueryBar: React.FunctionComponent = ({ onExplain, insights, isAIInputVisible = false, + isAIFetching = false, onShowAIInputClick, onHideAIInputClick, }) => { @@ -221,6 +217,16 @@ export const QueryBar: React.FunctionComponent = ({ data-result-id={resultId} data-apply-id={applyId} > + {isAIFeatureEnabled && ( +
+ { + onHideAIInputClick?.(); + }} + show={isAIInputVisible} + /> +
+ )}
{enableSavedAggregationsQueries && }
@@ -230,6 +236,7 @@ export const QueryBar: React.FunctionComponent = ({ onApply={onApply} placeholder={filterPlaceholder} insights={insights} + disabled={isAIFetching} /> {showAIEntryButton && (
@@ -247,7 +254,7 @@ export const QueryBar: React.FunctionComponent = ({ title="View the execution plan for the current query" data-testid="query-bar-explain-button" onClick={onExplain} - disabled={!isQueryValid} + disabled={!isQueryValid || isAIFetching} size="small" type="button" > @@ -258,7 +265,7 @@ export const QueryBar: React.FunctionComponent = ({ aria-label="Reset query" data-testid="query-bar-reset-filter-button" onClick={onReset} - disabled={!queryChanged} + disabled={!queryChanged || isAIFetching} size="small" type="button" > @@ -266,7 +273,7 @@ export const QueryBar: React.FunctionComponent = ({ )} - {queryOptionsLayout && queryOptionsLayout.length > 0 && ( -
+
= ({ queryOptionsLayout={queryOptionRowLayout} key={`query-bar-row-${rowIndex}`} onApply={onApply} + disabled={isAIFetching} placeholders={placeholders} /> ))}
)} - {isAIFeatureEnabled && ( -
- { - onHideAIInputClick?.(); - }} - show={isAIInputVisible} - /> -
- )} ); }; @@ -346,6 +339,7 @@ export default connect( valid: isQueryValid(fields), applyId: applyId, isAIInputVisible: aiQuery.isInputVisible, + isAIFetching: aiQuery.status === 'fetching', }; }, (dispatch: QueryBarThunkDispatch, ownProps: OwnProps) => { diff --git a/packages/compass-query-bar/src/components/query-option.tsx b/packages/compass-query-bar/src/components/query-option.tsx index 8a911a10a97..c4ab84b7f60 100644 --- a/packages/compass-query-bar/src/components/query-option.tsx +++ b/packages/compass-query-bar/src/components/query-option.tsx @@ -87,6 +87,7 @@ type QueryOptionProps = { placeholder?: string | HTMLElement; onApply?(): void; insights?: Signal | Signal[]; + disabled?: boolean; }; // Helper component to allow flexible computation of extra props for the TextInput @@ -118,6 +119,7 @@ const QueryOption: React.FunctionComponent = ({ value, onApply, insights, + disabled = false, }) => { const { track } = useLoggerAndTelemetry('COMPASS-QUERY-BAR-UI'); const darkMode = useDarkMode(); @@ -169,13 +171,14 @@ const QueryOption: React.FunctionComponent = ({
)} @@ -191,6 +194,7 @@ const QueryOption: React.FunctionComponent = ({ data-testid={`query-bar-option-${name}-input`} onApply={onApply} insights={insights} + disabled={disabled} /> ) : ( @@ -214,6 +218,7 @@ const QueryOption: React.FunctionComponent = ({ } onBlur={onBlurEditor} placeholder={placeholder as string} + disabled={disabled} {...props} /> )} diff --git a/packages/compass-query-bar/src/constants/query-bar-store.ts b/packages/compass-query-bar/src/constants/query-bar-store.ts index 843ef838d00..68e5bf658ae 100644 --- a/packages/compass-query-bar/src/constants/query-bar-store.ts +++ b/packages/compass-query-bar/src/constants/query-bar-store.ts @@ -16,6 +16,7 @@ const DEFAULT_FIELD_VALUES = { project: undefined, collation: undefined, sort: undefined, + hint: undefined, skip: undefined, limit: undefined, maxTimeMS: undefined, @@ -29,6 +30,7 @@ const DEFAULT_QUERY_VALUES = { project: DEFAULT_PROJECT, collation: DEFAULT_COLLATION, sort: DEFAULT_SORT, + hint: null, skip: DEFAULT_SKIP, limit: DEFAULT_LIMIT, maxTimeMS: DEFAULT_MAX_TIME_MS, diff --git a/packages/compass-query-bar/src/constants/query-option-definition.ts b/packages/compass-query-bar/src/constants/query-option-definition.ts index 0536db018cf..cffb563d608 100644 --- a/packages/compass-query-bar/src/constants/query-option-definition.ts +++ b/packages/compass-query-bar/src/constants/query-option-definition.ts @@ -33,6 +33,13 @@ export const OPTION_DEFINITION: { placeholder: "{ field: -1 } or [['field', -1]]", link: 'https://docs.mongodb.com/manual/reference/method/cursor.sort/', }, + hint: { + name: 'hint', + label: 'Index Hint', + type: 'document', + placeholder: '{ field: -1 }', + link: 'https://docs.mongodb.com/manual/reference/method/cursor.hint/', + }, collation: { name: 'collation', type: 'document', diff --git a/packages/compass-query-bar/src/constants/query-properties.ts b/packages/compass-query-bar/src/constants/query-properties.ts index 3724fff25d2..eaed016dc70 100644 --- a/packages/compass-query-bar/src/constants/query-properties.ts +++ b/packages/compass-query-bar/src/constants/query-properties.ts @@ -6,6 +6,7 @@ export const QUERY_PROPERTIES = [ 'project', 'collation', 'sort', + 'hint', 'skip', 'limit', 'maxTimeMS', @@ -26,6 +27,7 @@ export type QueryFormFields = { project: FormField; collation: FormField; sort: FormField; + hint: FormField; skip: FormField; limit: FormField; maxTimeMS: FormField; diff --git a/packages/compass-query-bar/src/stores/ai-query-reducer.spec.ts b/packages/compass-query-bar/src/stores/ai-query-reducer.spec.ts index 1ea5d8d15e8..b2130963f7b 100644 --- a/packages/compass-query-bar/src/stores/ai-query-reducer.spec.ts +++ b/packages/compass-query-bar/src/stores/ai-query-reducer.spec.ts @@ -45,7 +45,6 @@ describe('aiQueryReducer', function () { const mockDataService = { sample: sandbox.stub().resolves([{ _id: 42 }]), - getConnectionString: sandbox.stub().returns({ hosts: [] }), }; const store = createStore( @@ -81,7 +80,7 @@ describe('aiQueryReducer', function () { mockAtlasAiService.getQueryFromUserInput.getCall(0) ).to.not.have.nested.property('args[0].sampleDocuments'); - expect(store.getState().aiQuery.aiQueryFetchId).to.equal(-1); + expect(store.getState().aiQuery.aiQueryRequestId).to.equal(null); expect(store.getState().aiQuery.errorMessage).to.equal(undefined); expect(store.getState().aiQuery.status).to.equal('success'); }); @@ -108,7 +107,7 @@ describe('aiQueryReducer', function () { } as any); expect(store.getState().aiQuery.errorMessage).to.equal(undefined); await store.dispatch(runAIQuery('testing prompt') as any); - expect(store.getState().aiQuery.aiQueryFetchId).to.equal(-1); + expect(store.getState().aiQuery.aiQueryRequestId).to.equal(null); expect(store.getState().aiQuery.errorMessage).to.equal( '500 Internal Server Error' ); @@ -139,28 +138,111 @@ describe('aiQueryReducer', function () { errorMessage: undefined, errorCode: undefined, isInputVisible: false, - aiQueryFetchId: -1, + aiQueryRequestId: null, + lastAIQueryRequestId: null, }); }); }); + + describe('when the sample documents setting is enabled', function () { + beforeEach(async function () { + await preferences.savePreferences({ + enableGenAISampleDocumentPassing: true, + }); + }); + + it('includes sample documents in the request', async function () { + const mockAtlasAiService = { + getQueryFromUserInput: sandbox + .stub() + .resolves({ content: { query: { filter: '{_id: 1}' } } }), + }; + + const mockDataService = { + sample: sandbox.stub().resolves([{ _id: 42 }]), + }; + + const store = createStore( + { + namespace: 'database.collection', + }, + { + dataService: mockDataService, + atlasAuthService: { on: sandbox.stub() }, + atlasAiService: mockAtlasAiService, + preferences, + logger: createNoopLoggerAndTelemetry(), + } as any + ); + + expect(store.getState().aiQuery.status).to.equal('ready'); + + await store.dispatch(runAIQuery('testing prompt')); + + expect(mockAtlasAiService.getQueryFromUserInput).to.have.been + .calledOnce; + expect( + mockAtlasAiService.getQueryFromUserInput.getCall(0) + ).to.have.deep.nested.property('args[0].sampleDocuments', [ + { _id: 42 }, + ]); + }); + }); + + describe('when the sample documents setting is disabled', function () { + it('does not include sample documents in the request', async function () { + const mockAtlasAiService = { + getQueryFromUserInput: sandbox + .stub() + .resolves({ content: { query: { filter: '{_id: 1}' } } }), + }; + + const mockDataService = { + sample: sandbox.stub().resolves([{ _id: 42 }]), + }; + + const store = createStore( + { + namespace: 'database.collection', + }, + { + dataService: mockDataService, + atlasAuthService: { on: sandbox.stub() }, + atlasAiService: mockAtlasAiService, + preferences, + logger: createNoopLoggerAndTelemetry(), + } as any + ); + + expect(store.getState().aiQuery.status).to.equal('ready'); + + await store.dispatch(runAIQuery('testing prompt')); + + expect(mockAtlasAiService.getQueryFromUserInput).to.have.been + .calledOnce; + expect( + mockAtlasAiService.getQueryFromUserInput.getCall(0) + ).to.not.have.nested.property('args[0].sampleDocuments'); + }); + }); }); describe('cancelAIQuery', function () { - it('should unset the fetching id and set the status on the store', function () { + it('should unset the request id and set the status on the store', function () { const store = createStore({}, {} as any); - expect(store.getState().aiQuery.aiQueryFetchId).to.equal(-1); + expect(store.getState().aiQuery.aiQueryRequestId).to.equal(null); store.dispatch({ type: AIQueryActionTypes.AIQueryStarted, - fetchId: 1, + requestId: 'pineapples', }); expect(store.getState().aiQuery.status).to.equal('fetching'); - expect(store.getState().aiQuery.aiQueryFetchId).to.equal(1); + expect(store.getState().aiQuery.aiQueryRequestId).to.equal('pineapples'); store.dispatch(cancelAIQuery()); - expect(store.getState().aiQuery.aiQueryFetchId).to.equal(-1); + expect(store.getState().aiQuery.aiQueryRequestId).to.equal(null); expect(store.getState().aiQuery.status).to.equal('ready'); }); }); diff --git a/packages/compass-query-bar/src/stores/ai-query-reducer.ts b/packages/compass-query-bar/src/stores/ai-query-reducer.ts index 01355b22c54..2e18dd08132 100644 --- a/packages/compass-query-bar/src/stores/ai-query-reducer.ts +++ b/packages/compass-query-bar/src/stores/ai-query-reducer.ts @@ -1,6 +1,7 @@ import type { Reducer } from 'redux'; import { getSimplifiedSchema } from 'mongodb-schema'; import toNS from 'mongodb-ns'; +import { UUID } from 'bson'; import type { QueryBarThunkAction } from './query-bar-store'; import { isAction } from '../utils'; @@ -23,7 +24,8 @@ export type AIQueryState = { isInputVisible: boolean; aiPromptText: string; status: AIQueryStatus; - aiQueryFetchId: number; // Maps to the AbortController of the current fetch (or -1). + aiQueryRequestId: string | null; // Maps to the AbortController of the current fetch (or null). + lastAIQueryRequestId: string | null; // We store the last request id so we can pass it when a user provides feedback. }; export const initialState: AIQueryState = { @@ -32,7 +34,8 @@ export const initialState: AIQueryState = { errorMessage: undefined, errorCode: undefined, isInputVisible: false, - aiQueryFetchId: -1, + aiQueryRequestId: null, + lastAIQueryRequestId: null, }; export const enum AIQueryActionTypes { @@ -48,24 +51,22 @@ export const enum AIQueryActionTypes { const NUM_DOCUMENTS_TO_SAMPLE = 4; -const AIQueryAbortControllerMap = new Map(); - -let aiQueryFetchId = 0; +const AIQueryAbortControllerMap = new Map(); function getAbortSignal() { - const id = ++aiQueryFetchId; + const id = new UUID().toString(); const controller = new AbortController(); AIQueryAbortControllerMap.set(id, controller); return { id, signal: controller.signal }; } -function abort(id: number) { +function abort(id: string) { const controller = AIQueryAbortControllerMap.get(id); controller?.abort(); return AIQueryAbortControllerMap.delete(id); } -function cleanupAbortSignal(id: number) { +function cleanupAbortSignal(id: string) { return AIQueryAbortControllerMap.delete(id); } @@ -89,7 +90,7 @@ export const changeAIPromptText = (text: string): ChangeAIPromptTextAction => ({ type AIQueryStartedAction = { type: AIQueryActionTypes.AIQueryStarted; - fetchId: number; + requestId: string; }; type AIQueryFailedAction = { @@ -102,6 +103,7 @@ type AIQueryFailedAction = { export type AIQuerySucceededAction = { type: AIQueryActionTypes.AIQuerySucceeded; fields: QueryFormFields; + requestId: string; }; type FailedResponseTrackMessage = { @@ -111,6 +113,7 @@ type FailedResponseTrackMessage = { errorMessage: string; log: LoggerAndTelemetry['log']; track: LoggerAndTelemetry['track']; + requestId: string; }; function trackAndLogFailed({ @@ -120,18 +123,21 @@ function trackAndLogFailed({ errorMessage, log, track, + requestId, }: FailedResponseTrackMessage) { log.warn(mongoLogId(1_001_000_198), 'AIQuery', 'AI query request failed', { statusCode, errorMessage, errorName, errorCode, + requestId, }); track('AI Response Failed', () => ({ editor_view_type: 'find', error_name: errorName, status_code: statusCode, error_code: errorCode ?? '', + request_id: requestId, })); } @@ -152,27 +158,31 @@ export const runAIQuery = ( logger: { log, track }, } ) => { + const provideSampleDocuments = + preferences.getPreferences().enableGenAISampleDocumentPassing; + const abortController = new AbortController(); + const { id: requestId, signal } = getAbortSignal(); + track('AI Prompt Submitted', () => ({ editor_view_type: 'find', user_input_length: userInput.length, + has_sample_documents: provideSampleDocuments, + request_id: requestId, })); const { - aiQuery: { aiQueryFetchId: existingFetchId }, + aiQuery: { aiQueryRequestId: existingRequestId }, queryBar: { namespace }, } = getState(); - if (aiQueryFetchId !== -1) { + if (existingRequestId !== null) { // Cancel the active request as this one will override. - abort(existingFetchId); + abort(existingRequestId); } - const abortController = new AbortController(); - const { id: fetchId, signal } = getAbortSignal(); - dispatch({ type: AIQueryActionTypes.AIQueryStarted, - fetchId, + requestId, }); let jsonResponse; @@ -201,7 +211,13 @@ export const runAIQuery = ( collectionName, databaseName, schema, - // sampleDocuments, // For now we are not passing sample documents to the ai. + // Provide sample documents when the user has opted in in their settings. + ...(provideSampleDocuments + ? { + sampleDocuments, + } + : undefined), + requestId, }); } catch (err: any) { if (signal.aborted) { @@ -215,6 +231,7 @@ export const runAIQuery = ( errorMessage: (err as AtlasServiceError).message, log, track, + requestId, }); // We're going to reset input state with this error, show the error in the // toast instead @@ -236,14 +253,17 @@ export const runAIQuery = ( } finally { // Remove the AbortController from the Map as we either finished // waiting for the fetch or cancelled at this point. - cleanupAbortSignal(fetchId); + cleanupAbortSignal(requestId); } if (signal.aborted) { log.info( mongoLogId(1_001_000_197), 'AIQuery', - 'Cancelled ai query request' + 'Cancelled ai query request', + { + requestId, + } ); return; } @@ -263,6 +283,7 @@ export const runAIQuery = ( errorMessage: err?.message, log, track, + requestId, }); dispatch({ type: AIQueryActionTypes.AIQueryFailed, @@ -281,6 +302,7 @@ export const runAIQuery = ( localAppRegistry?.emit('generate-aggregation-from-query', { userInput, aggregation, + requestId, }); const msg = 'Query requires stages from aggregation framework therefore an aggregation was generated.'; @@ -289,6 +311,7 @@ export const runAIQuery = ( errorMessage: msg, log, track, + requestId, }); return; } @@ -300,6 +323,7 @@ export const runAIQuery = ( errorMessage: msg, log, track, + requestId, }); dispatch({ type: AIQueryActionTypes.AIQueryFailed, @@ -321,17 +345,20 @@ export const runAIQuery = ( 'AIQuery', 'AI query request succeeded', { + requestId, shape: Object.keys(generatedFields), } ); track('AI Response Generated', () => ({ editor_view_type: 'find', query_shape: Object.keys(generatedFields), + request_id: requestId, })); dispatch({ type: AIQueryActionTypes.AIQuerySucceeded, fields: queryFields, + requestId, }); }; }; @@ -346,7 +373,10 @@ export const cancelAIQuery = (): QueryBarThunkAction< > => { return (dispatch, getState) => { // Abort any ongoing op. - abort(getState().aiQuery.aiQueryFetchId); + const existingRequestId = getState().aiQuery.aiQueryRequestId; + if (existingRequestId !== null) { + abort(existingRequestId); + } dispatch({ type: AIQueryActionTypes.CancelAIQuery, @@ -386,7 +416,7 @@ const aiQueryReducer: Reducer = ( ...state, status: 'fetching', errorMessage: undefined, - aiQueryFetchId: action.fetchId, + aiQueryRequestId: action.requestId, }; } @@ -401,7 +431,7 @@ const aiQueryReducer: Reducer = ( return { ...state, status: 'ready', - aiQueryFetchId: -1, + aiQueryRequestId: null, errorMessage: action.errorMessage, errorCode: action.errorCode, }; @@ -416,7 +446,8 @@ const aiQueryReducer: Reducer = ( return { ...state, status: 'success', - aiQueryFetchId: -1, + aiQueryRequestId: null, + lastAIQueryRequestId: action.requestId, }; } @@ -424,7 +455,7 @@ const aiQueryReducer: Reducer = ( return { ...state, status: 'ready', - aiQueryFetchId: -1, + aiQueryRequestId: null, }; } diff --git a/packages/compass-query-bar/src/stores/query-bar-reducer.spec.ts b/packages/compass-query-bar/src/stores/query-bar-reducer.spec.ts index 773a46c539d..915523b256c 100644 --- a/packages/compass-query-bar/src/stores/query-bar-reducer.spec.ts +++ b/packages/compass-query-bar/src/stores/query-bar-reducer.spec.ts @@ -270,6 +270,7 @@ describe('queryBarReducer', function () { filter: { _id: { $exists: true } }, collation: null, sort: null, + hint: null, skip: 0, limit: 10, maxTimeMS: 60000, diff --git a/packages/compass-query-bar/src/utils/query.ts b/packages/compass-query-bar/src/utils/query.ts index 59bf4c98b87..91694a6289d 100644 --- a/packages/compass-query-bar/src/utils/query.ts +++ b/packages/compass-query-bar/src/utils/query.ts @@ -94,9 +94,7 @@ export function mapQueryToFormFields( let valueAsString = typeof _value === 'undefined' ? '' : toJSString(_value, 0) || ''; - valueAsString = prettify(valueAsString, 'javascript-expression', { - trailingComma: 'none', - }); + valueAsString = prettify(valueAsString, 'javascript-expression'); const value = validateField(key, valueAsString, preferences); const valid: boolean = value !== false; @@ -151,6 +149,12 @@ export function validateField( } } + // We don't have a validator for indexes, but indexes share the same structure as + // a sort document, so we're leveraging this to validate the hint field + if (field === 'hint') { + return validate('sort', value); + } + return validated; } diff --git a/packages/compass-saved-aggregations-queries/.eslintrc.js b/packages/compass-saved-aggregations-queries/.eslintrc.js index f06f8fc6013..b54d314ab18 100644 --- a/packages/compass-saved-aggregations-queries/.eslintrc.js +++ b/packages/compass-saved-aggregations-queries/.eslintrc.js @@ -1,3 +1,4 @@ +'use strict'; module.exports = { root: true, extends: ['@mongodb-js/eslint-config-compass/plugin'], diff --git a/packages/compass-saved-aggregations-queries/.mocharc.js b/packages/compass-saved-aggregations-queries/.mocharc.js index a7e53abc444..61091edba73 100644 --- a/packages/compass-saved-aggregations-queries/.mocharc.js +++ b/packages/compass-saved-aggregations-queries/.mocharc.js @@ -1 +1,2 @@ +'use strict'; module.exports = require('@mongodb-js/mocha-config-compass/compass-plugin'); diff --git a/packages/compass-saved-aggregations-queries/package.json b/packages/compass-saved-aggregations-queries/package.json index e872aeb8e2d..688496f9aaa 100644 --- a/packages/compass-saved-aggregations-queries/package.json +++ b/packages/compass-saved-aggregations-queries/package.json @@ -11,7 +11,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "1.24.1", + "version": "1.26.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -48,15 +48,15 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "dependencies": { - "@mongodb-js/compass-app-stores": "^7.10.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-workspaces": "^0.5.1", - "@mongodb-js/my-queries-storage": "^0.5.1", + "@mongodb-js/compass-app-stores": "^7.12.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-workspaces": "^0.7.0", + "@mongodb-js/my-queries-storage": "^0.7.0", "bson": "^6.6.0", "fuse.js": "^6.5.3", - "hadron-app-registry": "^9.1.8", + "hadron-app-registry": "^9.1.10", "mongodb-ns": "^2.4.0", "react": "^17.0.2", "react-redux": "^8.1.3", @@ -64,10 +64,10 @@ "redux-thunk": "^2.4.2" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/react-hooks": "^7.0.2", "@testing-library/user-event": "^13.5.0", diff --git a/packages/compass-saved-aggregations-queries/src/index.ts b/packages/compass-saved-aggregations-queries/src/index.ts index 3ba896ad856..cfc1defff5d 100644 --- a/packages/compass-saved-aggregations-queries/src/index.ts +++ b/packages/compass-saved-aggregations-queries/src/index.ts @@ -1,5 +1,6 @@ import { registerHadronPlugin } from 'hadron-app-registry'; import { + connectionInfoAccessLocator, dataServiceLocator, type DataService, type DataServiceLocator, @@ -26,6 +27,7 @@ const serviceLocators = { workspaces: workspacesServiceLocator, pipelineStorage: pipelineStorageLocator, favoriteQueryStorageAccess: favoriteQueryStorageAccessLocator, + connectionInfoAccess: connectionInfoAccessLocator, }; export const MyQueriesPlugin = registerHadronPlugin< diff --git a/packages/compass-saved-aggregations-queries/src/stores/index.ts b/packages/compass-saved-aggregations-queries/src/stores/index.ts index 67dcbba3c5f..5de08c2e54b 100644 --- a/packages/compass-saved-aggregations-queries/src/stores/index.ts +++ b/packages/compass-saved-aggregations-queries/src/stores/index.ts @@ -6,7 +6,10 @@ import type { ThunkAction } from 'redux-thunk'; import itemsReducer from './aggregations-queries-items'; import openItemReducer from './open-item'; import editItemReducer from './edit-item'; -import type { DataService } from '@mongodb-js/compass-connections/provider'; +import type { + ConnectionInfoAccess, + DataService, +} from '@mongodb-js/compass-connections/provider'; import type { MongoDBInstance } from '@mongodb-js/compass-app-stores/provider'; import type { LoggerAndTelemetry } from '@mongodb-js/compass-logging/provider'; import type { workspacesServiceLocator } from '@mongodb-js/compass-workspaces/provider'; @@ -24,6 +27,7 @@ type MyQueriesServices = { pipelineStorage?: PipelineStorage; workspaces: ReturnType; favoriteQueryStorageAccess?: FavoriteQueryStorageAccess; + connectionInfoAccess: ConnectionInfoAccess; }; export function configureStore({ @@ -34,6 +38,7 @@ export function configureStore({ workspaces, pipelineStorage, favoriteQueryStorageAccess, + connectionInfoAccess, }: MyQueriesServices) { return createStore( combineReducers({ @@ -50,6 +55,7 @@ export function configureStore({ pipelineStorage, queryStorage: favoriteQueryStorageAccess?.getStorage(), workspaces, + connectionInfoAccess, }) ) ); diff --git a/packages/compass-saved-aggregations-queries/src/stores/open-item.ts b/packages/compass-saved-aggregations-queries/src/stores/open-item.ts index 9a9614a38bb..ead693e9c6f 100644 --- a/packages/compass-saved-aggregations-queries/src/stores/open-item.ts +++ b/packages/compass-saved-aggregations-queries/src/stores/open-item.ts @@ -217,7 +217,11 @@ const openItem = database: string, collection: string ): SavedQueryAggregationThunkAction => - (_dispatch, _getState, { logger: { track }, workspaces }) => { + ( + _dispatch, + _getState, + { logger: { track }, workspaces, connectionInfoAccess } + ) => { track( item.type === 'aggregation' ? 'Aggregation Opened' @@ -228,15 +232,22 @@ const openItem = } ); - workspaces.openCollectionWorkspace(`${database}.${collection}`, { - initialAggregation: - item.type === 'aggregation' ? item.aggregation : undefined, - initialQuery: - item.type === 'query' || item.type === 'updatemany' - ? item.query - : undefined, - newTab: true, - }); + const { id: connectionId } = + connectionInfoAccess.getCurrentConnectionInfo(); + + workspaces.openCollectionWorkspace( + connectionId, + `${database}.${collection}`, + { + initialAggregation: + item.type === 'aggregation' ? item.aggregation : undefined, + initialQuery: + item.type === 'query' || item.type === 'updatemany' + ? item.query + : undefined, + newTab: true, + } + ); }; export const openSavedItem = diff --git a/packages/compass-schema-validation/.eslintrc.js b/packages/compass-schema-validation/.eslintrc.js index f06f8fc6013..b54d314ab18 100644 --- a/packages/compass-schema-validation/.eslintrc.js +++ b/packages/compass-schema-validation/.eslintrc.js @@ -1,3 +1,4 @@ +'use strict'; module.exports = { root: true, extends: ['@mongodb-js/eslint-config-compass/plugin'], diff --git a/packages/compass-schema-validation/.mocharc.js b/packages/compass-schema-validation/.mocharc.js index 0b24ff1d17a..43f6dacafd0 100644 --- a/packages/compass-schema-validation/.mocharc.js +++ b/packages/compass-schema-validation/.mocharc.js @@ -1,3 +1,4 @@ +'use strict'; const base = require('@mongodb-js/mocha-config-compass/compass-plugin'); module.exports = { diff --git a/packages/compass-schema-validation/package.json b/packages/compass-schema-validation/package.json index 4ebd1a63203..4e7060ae582 100644 --- a/packages/compass-schema-validation/package.json +++ b/packages/compass-schema-validation/package.json @@ -6,7 +6,7 @@ "email": "compass@mongodb.com" }, "private": true, - "version": "6.24.1", + "version": "6.26.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -48,36 +48,36 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "chai": "^4.2.0", "depcheck": "^1.4.1", - "electron": "^28.2.10", + "electron": "^29.3.1", "electron-mocha": "^12.2.0", "enzyme": "^3.11.0", "eslint": "^7.25.0", - "hadron-ipc": "^3.2.12", + "hadron-ipc": "^3.2.14", "mocha": "^10.2.0", - "mongodb-instance-model": "^12.18.1", + "mongodb-instance-model": "^12.20.0", "nyc": "^15.1.0", "react-dom": "^17.0.2", "sinon": "^8.1.1", "typescript": "^5.0.4" }, "dependencies": { - "@mongodb-js/compass-app-stores": "^7.10.1", - "@mongodb-js/compass-collection": "^4.23.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-crud": "^13.24.1", - "@mongodb-js/compass-editor": "^0.21.1", - "@mongodb-js/compass-field-store": "^9.0.19", - "@mongodb-js/compass-logging": "^1.2.14", + "@mongodb-js/compass-app-stores": "^7.12.0", + "@mongodb-js/compass-collection": "^4.25.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-crud": "^13.26.0", + "@mongodb-js/compass-editor": "^0.23.0", + "@mongodb-js/compass-field-store": "^9.2.0", + "@mongodb-js/compass-logging": "^1.2.16", "bson": "^6.6.0", - "compass-preferences-model": "^2.18.1", - "hadron-app-registry": "^9.1.8", + "compass-preferences-model": "^2.20.0", + "hadron-app-registry": "^9.1.10", "javascript-stringify": "^2.0.1", "lodash": "^4.17.21", "mongodb-ns": "^2.4.0", diff --git a/packages/compass-schema/.eslintrc.js b/packages/compass-schema/.eslintrc.js index eabb558d9c1..96c534ab2ea 100644 --- a/packages/compass-schema/.eslintrc.js +++ b/packages/compass-schema/.eslintrc.js @@ -1,3 +1,4 @@ +'use strict'; module.exports = { root: true, extends: ['@mongodb-js/eslint-config-compass/plugin'], diff --git a/packages/compass-schema/.mocharc.js b/packages/compass-schema/.mocharc.js index 1360f33057b..e80aed9a776 100644 --- a/packages/compass-schema/.mocharc.js +++ b/packages/compass-schema/.mocharc.js @@ -1,3 +1,4 @@ +'use strict'; const path = require('path'); const mochaConfig = require('@mongodb-js/mocha-config-compass/compass-plugin'); diff --git a/packages/compass-schema/package.json b/packages/compass-schema/package.json index 5b8e042ac25..6cb1d5993a5 100644 --- a/packages/compass-schema/package.json +++ b/packages/compass-schema/package.json @@ -6,7 +6,7 @@ "email": "compass@mongodb.com" }, "private": true, - "version": "6.25.1", + "version": "6.27.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -48,11 +48,11 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/my-queries-storage": "^0.5.1", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/my-queries-storage": "^0.7.0", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "@types/chai": "^4.2.21", @@ -73,25 +73,25 @@ "xvfb-maybe": "^0.2.1" }, "dependencies": { - "@mongodb-js/compass-collection": "^4.23.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-field-store": "^9.0.19", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-query-bar": "^8.25.1", - "@mongodb-js/connection-storage": "^0.8.1", + "@mongodb-js/compass-collection": "^4.25.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-field-store": "^9.2.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-query-bar": "^8.27.0", + "@mongodb-js/connection-storage": "^0.10.0", "bson": "^6.6.0", - "compass-preferences-model": "^2.18.1", + "compass-preferences-model": "^2.20.0", "d3": "^3.5.17", - "hadron-app-registry": "^9.1.8", - "hadron-document": "^8.4.9", + "hadron-app-registry": "^9.1.10", + "hadron-document": "^8.5.1", "leaflet": "^1.5.1", "leaflet-defaulticon-compatibility": "^0.1.1", "leaflet-draw": "^1.0.4", "lodash": "^4.17.21", "moment": "^2.29.4", "mongodb": "^6.5.0", - "mongodb-query-util": "^2.1.9", + "mongodb-query-util": "^2.2.1", "mongodb-schema": "^12.1.0", "numeral": "^1.5.6", "prop-types": "^15.7.2", diff --git a/packages/compass-schema/src/components/compass-schema.tsx b/packages/compass-schema/src/components/compass-schema.tsx index c59a5ba34b5..312f93079c8 100644 --- a/packages/compass-schema/src/components/compass-schema.tsx +++ b/packages/compass-schema/src/components/compass-schema.tsx @@ -32,7 +32,7 @@ import { import { HackoladePromoBanner } from './promo-banner'; import type { configureActions } from '../actions'; import { usePreference } from 'compass-preferences-model/provider'; -import { useConnectionInfo } from '@mongodb-js/connection-storage/provider'; +import { useConnectionInfo } from '@mongodb-js/compass-connections/provider'; import { getAtlasPerformanceAdvisorLink } from '../utils'; import { useLoggerAndTelemetry } from '@mongodb-js/compass-logging/provider'; diff --git a/packages/compass-schema/src/components/coordinates-minichart/constants.js b/packages/compass-schema/src/components/coordinates-minichart/constants.js index 1bc90721bfc..ffaa8f17d96 100644 --- a/packages/compass-schema/src/components/coordinates-minichart/constants.js +++ b/packages/compass-schema/src/components/coordinates-minichart/constants.js @@ -1,3 +1,4 @@ +'use strict'; const LIGHTMODE_TILE_URL = 'https://compass-maps.mongodb.com/compass/maptile/reduced.day/{z}/{x}/{y}/512'; const DARKMODE_TILE_URL = diff --git a/packages/compass-schema/src/modules/d3-tip.js b/packages/compass-schema/src/modules/d3-tip.js index 17e2c0c3190..e95a41eb71f 100644 --- a/packages/compass-schema/src/modules/d3-tip.js +++ b/packages/compass-schema/src/modules/d3-tip.js @@ -1,3 +1,4 @@ +'use strict'; /* eslint no-use-before-define: 0, one-var: 0, no-else-return: 0, no-unused-vars: 0, eqeqeq: 0, no-shadow: 0, yoda: 0, consistent-return: 0, one-var: 0, camelcase: 0 */ // d3.tip // Copyright (c) 2013 Justin Palmer diff --git a/packages/compass-schema/src/modules/detect-coordinates.js b/packages/compass-schema/src/modules/detect-coordinates.js index 61abb5f1b86..2951ae25103 100644 --- a/packages/compass-schema/src/modules/detect-coordinates.js +++ b/packages/compass-schema/src/modules/detect-coordinates.js @@ -1,3 +1,4 @@ +'use strict'; const _ = require('lodash'); /** diff --git a/packages/compass-schema/src/modules/detect-coordinates.spec.js b/packages/compass-schema/src/modules/detect-coordinates.spec.js index a0409f60e19..d3b11022e31 100644 --- a/packages/compass-schema/src/modules/detect-coordinates.spec.js +++ b/packages/compass-schema/src/modules/detect-coordinates.spec.js @@ -1,3 +1,4 @@ +'use strict'; const detect = require('./detect-coordinates'); const assert = require('assert'); const legacyPairs = require('../../test/fixtures/legacy_pairs.json'); diff --git a/packages/compass-serverstats/.eslintrc.js b/packages/compass-serverstats/.eslintrc.js index f06f8fc6013..b54d314ab18 100644 --- a/packages/compass-serverstats/.eslintrc.js +++ b/packages/compass-serverstats/.eslintrc.js @@ -1,3 +1,4 @@ +'use strict'; module.exports = { root: true, extends: ['@mongodb-js/eslint-config-compass/plugin'], diff --git a/packages/compass-serverstats/.mocharc.js b/packages/compass-serverstats/.mocharc.js index a7e53abc444..61091edba73 100644 --- a/packages/compass-serverstats/.mocharc.js +++ b/packages/compass-serverstats/.mocharc.js @@ -1 +1,2 @@ +'use strict'; module.exports = require('@mongodb-js/mocha-config-compass/compass-plugin'); diff --git a/packages/compass-serverstats/package.json b/packages/compass-serverstats/package.json index 446d8d90e03..beb8bf78f47 100644 --- a/packages/compass-serverstats/package.json +++ b/packages/compass-serverstats/package.json @@ -2,7 +2,7 @@ "name": "@mongodb-js/compass-serverstats", "description": "Compass Real Time", "private": true, - "version": "16.23.1", + "version": "16.25.0", "main": "dist/index.js", "compass:main": "src/index.ts", "exports": { @@ -30,15 +30,15 @@ }, "license": "SSPL", "dependencies": { - "@mongodb-js/compass-app-stores": "^7.10.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-workspaces": "^0.5.1", + "@mongodb-js/compass-app-stores": "^7.12.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-workspaces": "^0.7.0", "d3": "^3.5.17", "d3-timer": "^1.0.3", "debug": "^4.2.0", - "hadron-app-registry": "^9.1.8", + "hadron-app-registry": "^9.1.10", "lodash": "^4.17.21", "mongodb-ns": "^2.4.0", "prop-types": "^15.7.2", @@ -46,10 +46,10 @@ "reflux": "^0.4.1" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/d3": "^3.5.x", "@types/enzyme": "^3.10.14", "chai": "^4.1.2", diff --git a/packages/compass-serverstats/src/actions/index.js b/packages/compass-serverstats/src/actions/index.js index 07e59257e02..e9aa928b23e 100644 --- a/packages/compass-serverstats/src/actions/index.js +++ b/packages/compass-serverstats/src/actions/index.js @@ -1,3 +1,4 @@ +'use strict'; const Reflux = require('reflux'); /** diff --git a/packages/compass-serverstats/src/d3/index.js b/packages/compass-serverstats/src/d3/index.js index bd5cbe5810a..13de7de5e26 100644 --- a/packages/compass-serverstats/src/d3/index.js +++ b/packages/compass-serverstats/src/d3/index.js @@ -1,2 +1,3 @@ +'use strict'; module.exports.realTimeLineChart = require('./real-time-line-chart'); module.exports.realTimeDispatcher = require('./real-time-dispatcher'); diff --git a/packages/compass-serverstats/src/d3/real-time-chart-lines.js b/packages/compass-serverstats/src/d3/real-time-chart-lines.js index e00e8b1fcda..149e174ca21 100644 --- a/packages/compass-serverstats/src/d3/real-time-chart-lines.js +++ b/packages/compass-serverstats/src/d3/real-time-chart-lines.js @@ -1,3 +1,4 @@ +'use strict'; const d3 = require('d3'); function realTimeChartLines() { diff --git a/packages/compass-serverstats/src/d3/real-time-dispatcher.js b/packages/compass-serverstats/src/d3/real-time-dispatcher.js index 76807944051..317111b61de 100644 --- a/packages/compass-serverstats/src/d3/real-time-dispatcher.js +++ b/packages/compass-serverstats/src/d3/real-time-dispatcher.js @@ -1,3 +1,4 @@ +'use strict'; const d3 = require('d3'); /** diff --git a/packages/compass-serverstats/src/d3/real-time-legend.js b/packages/compass-serverstats/src/d3/real-time-legend.js index a07c8f4efc6..57489a6f65c 100644 --- a/packages/compass-serverstats/src/d3/real-time-legend.js +++ b/packages/compass-serverstats/src/d3/real-time-legend.js @@ -1,3 +1,4 @@ +'use strict'; const d3 = require('d3'); function realTimeLegend() { diff --git a/packages/compass-serverstats/src/d3/real-time-line-chart.js b/packages/compass-serverstats/src/d3/real-time-line-chart.js index 07cd02bfc7b..30bcaedfdd0 100644 --- a/packages/compass-serverstats/src/d3/real-time-line-chart.js +++ b/packages/compass-serverstats/src/d3/real-time-line-chart.js @@ -1,3 +1,4 @@ +'use strict'; /* eslint complexity:0 */ const d3 = require('d3'); const realTimeLegend = require('./real-time-legend'); diff --git a/packages/compass-serverstats/src/d3/real-time-mouse-overlay.js b/packages/compass-serverstats/src/d3/real-time-mouse-overlay.js index e7c8d58388c..2fb89b8e233 100644 --- a/packages/compass-serverstats/src/d3/real-time-mouse-overlay.js +++ b/packages/compass-serverstats/src/d3/real-time-mouse-overlay.js @@ -1,3 +1,4 @@ +'use strict'; const d3 = require('d3'); const { palette } = require('@mongodb-js/compass-components'); diff --git a/packages/compass-serverstats/src/stores/current-op-store.js b/packages/compass-serverstats/src/stores/current-op-store.js index df4ce0451a6..c54808199b5 100644 --- a/packages/compass-serverstats/src/stores/current-op-store.js +++ b/packages/compass-serverstats/src/stores/current-op-store.js @@ -1,3 +1,4 @@ +'use strict'; const Reflux = require('reflux'); const Actions = require('../actions'); const toNS = require('mongodb-ns'); diff --git a/packages/compass-serverstats/src/stores/dberror-store.js b/packages/compass-serverstats/src/stores/dberror-store.js index b4d87ea13d8..b989dde189c 100644 --- a/packages/compass-serverstats/src/stores/dberror-store.js +++ b/packages/compass-serverstats/src/stores/dberror-store.js @@ -1,3 +1,4 @@ +'use strict'; const Reflux = require('reflux'); const Actions = require('../actions'); const { isEqual } = require('lodash'); diff --git a/packages/compass-serverstats/src/stores/globallock-store.js b/packages/compass-serverstats/src/stores/globallock-store.js index 060a4ca7506..8ba35bf1221 100644 --- a/packages/compass-serverstats/src/stores/globallock-store.js +++ b/packages/compass-serverstats/src/stores/globallock-store.js @@ -1,3 +1,4 @@ +'use strict'; const Reflux = require('reflux'); const Actions = require('../actions'); const ServerStatsStore = require('./server-stats-graphs-store'); diff --git a/packages/compass-serverstats/src/stores/mem-store.js b/packages/compass-serverstats/src/stores/mem-store.js index 4a764943b25..705cb8cd2d2 100644 --- a/packages/compass-serverstats/src/stores/mem-store.js +++ b/packages/compass-serverstats/src/stores/mem-store.js @@ -1,3 +1,4 @@ +'use strict'; const Reflux = require('reflux'); const Actions = require('../actions'); const ServerStatsStore = require('./server-stats-graphs-store'); diff --git a/packages/compass-serverstats/src/stores/network-store.js b/packages/compass-serverstats/src/stores/network-store.js index f363fe58c2c..bbcb6d21819 100644 --- a/packages/compass-serverstats/src/stores/network-store.js +++ b/packages/compass-serverstats/src/stores/network-store.js @@ -1,3 +1,4 @@ +'use strict'; const Reflux = require('reflux'); const Actions = require('../actions'); const ServerStatsStore = require('./server-stats-graphs-store'); diff --git a/packages/compass-serverstats/src/stores/opcounters-store.js b/packages/compass-serverstats/src/stores/opcounters-store.js index a9b11701a9f..ed1422dc1c6 100644 --- a/packages/compass-serverstats/src/stores/opcounters-store.js +++ b/packages/compass-serverstats/src/stores/opcounters-store.js @@ -1,3 +1,4 @@ +'use strict'; const Reflux = require('reflux'); const Actions = require('../actions'); const ServerStatsStore = require('./server-stats-graphs-store'); diff --git a/packages/compass-serverstats/src/stores/server-stats-graphs-store.js b/packages/compass-serverstats/src/stores/server-stats-graphs-store.js index 5cabe2cd499..ec9ef21d740 100644 --- a/packages/compass-serverstats/src/stores/server-stats-graphs-store.js +++ b/packages/compass-serverstats/src/stores/server-stats-graphs-store.js @@ -1,3 +1,4 @@ +'use strict'; const Reflux = require('reflux'); const Actions = require('../actions'); diff --git a/packages/compass-serverstats/src/stores/top-store.js b/packages/compass-serverstats/src/stores/top-store.js index 36cf2ff77bd..36ca6494ff7 100644 --- a/packages/compass-serverstats/src/stores/top-store.js +++ b/packages/compass-serverstats/src/stores/top-store.js @@ -1,3 +1,4 @@ +'use strict'; const Reflux = require('reflux'); const Actions = require('../actions'); const toNS = require('mongodb-ns'); diff --git a/packages/compass-serverstats/test/enzyme/chart-component.test.js b/packages/compass-serverstats/test/enzyme/chart-component.test.js index 34216dcab01..928f4247ed7 100644 --- a/packages/compass-serverstats/test/enzyme/chart-component.test.js +++ b/packages/compass-serverstats/test/enzyme/chart-component.test.js @@ -1,3 +1,4 @@ +'use strict'; /* eslint no-unused-vars: 0, no-unused-expressions: 0 */ const chai = require('chai'); const expect = chai.expect; diff --git a/packages/compass-settings/.eslintrc.js b/packages/compass-settings/.eslintrc.js index f06f8fc6013..b54d314ab18 100644 --- a/packages/compass-settings/.eslintrc.js +++ b/packages/compass-settings/.eslintrc.js @@ -1,3 +1,4 @@ +'use strict'; module.exports = { root: true, extends: ['@mongodb-js/eslint-config-compass/plugin'], diff --git a/packages/compass-settings/.mocharc.js b/packages/compass-settings/.mocharc.js index a7e53abc444..61091edba73 100644 --- a/packages/compass-settings/.mocharc.js +++ b/packages/compass-settings/.mocharc.js @@ -1 +1,2 @@ +'use strict'; module.exports = require('@mongodb-js/mocha-config-compass/compass-plugin'); diff --git a/packages/compass-settings/package.json b/packages/compass-settings/package.json index 4f12ae6ae0e..8b5adca7685 100644 --- a/packages/compass-settings/package.json +++ b/packages/compass-settings/package.json @@ -11,7 +11,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "0.26.1", + "version": "0.28.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -49,23 +49,23 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "dependencies": { - "@mongodb-js/atlas-service": "^0.15.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-generative-ai": "^0.8.1", - "@mongodb-js/compass-logging": "^1.2.14", - "compass-preferences-model": "^2.18.1", - "hadron-app-registry": "^9.1.8", - "hadron-ipc": "^3.2.12", + "@mongodb-js/atlas-service": "^0.17.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-generative-ai": "^0.10.0", + "@mongodb-js/compass-logging": "^1.2.16", + "compass-preferences-model": "^2.20.0", + "hadron-app-registry": "^9.1.10", + "hadron-ipc": "^3.2.14", "react": "^17.0.2", "react-redux": "^8.1.3", "redux": "^4.2.1", "redux-thunk": "^2.4.2" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "@types/chai": "^4.2.21", diff --git a/packages/compass-settings/src/components/modal.tsx b/packages/compass-settings/src/components/modal.tsx index c87aae7f6d7..4d0adfb6b16 100644 --- a/packages/compass-settings/src/components/modal.tsx +++ b/packages/compass-settings/src/components/modal.tsx @@ -20,10 +20,7 @@ import Sidebar from './sidebar'; import { saveSettings, closeModal } from '../stores/settings'; import type { RootState } from '../stores'; import { getUserInfo } from '../stores/atlas-login'; -import { - useIsAIFeatureEnabled, - useHasAIFeatureCloudRolloutAccess, -} from 'compass-preferences-model/provider'; +import { useHasAIFeatureCloudRolloutAccess } from 'compass-preferences-model/provider'; type Settings = { name: string; @@ -31,6 +28,7 @@ type Settings = { }; type SettingsModalProps = { + isAIFeatureEnabled: boolean; isOpen: boolean; isOIDCEnabled: boolean; onMount?: () => void; @@ -60,6 +58,7 @@ const settingsStyles = css( ); export const SettingsModal: React.FunctionComponent = ({ + isAIFeatureEnabled, isOpen, onMount, onClose, @@ -67,7 +66,6 @@ export const SettingsModal: React.FunctionComponent = ({ isOIDCEnabled, hasChangedSettings, }) => { - const aiFeatureEnabled = useIsAIFeatureEnabled(); const aiFeatureHasCloudRolloutAccess = useHasAIFeatureCloudRolloutAccess(); const onMountRef = useRef(onMount); @@ -84,7 +82,7 @@ export const SettingsModal: React.FunctionComponent = ({ if ( isOIDCEnabled || // because oidc options overlap with atlas login used for ai feature - aiFeatureEnabled + isAIFeatureEnabled ) { settings.push({ name: 'OIDC (Preview)', @@ -151,6 +149,7 @@ export default connect( return { isOpen: state.settings.isModalOpen && state.settings.loadingState === 'ready', + isAIFeatureEnabled: !!state.settings.settings.enableGenAIFeatures, isOIDCEnabled: !!state.settings.settings.enableOidc, hasChangedSettings: state.settings.updatedFields.length > 0, }; diff --git a/packages/compass-settings/src/components/settings/gen-ai-settings.tsx b/packages/compass-settings/src/components/settings/gen-ai-settings.tsx index 81ab1647e81..2e9abac1dcb 100644 --- a/packages/compass-settings/src/components/settings/gen-ai-settings.tsx +++ b/packages/compass-settings/src/components/settings/gen-ai-settings.tsx @@ -1,16 +1,18 @@ import React from 'react'; +import { css, spacing } from '@mongodb-js/compass-components'; +import { connect } from 'react-redux'; + +import type { RootState } from '../../stores'; import SettingsList from './settings-list'; -import { useIsAIFeatureEnabled } from 'compass-preferences-model/provider'; import { ConnectedAtlasLoginSettings } from './atlas-login'; -import { css, spacing } from '@mongodb-js/compass-components'; const atlasSettingsContainerStyles = css({ marginTop: spacing[3], }); -export const GenAISettings: React.FunctionComponent = () => { - const aiFeatureEnabled = useIsAIFeatureEnabled(); - +export const GenAISettings: React.FunctionComponent<{ + isAIFeatureEnabled: boolean; +}> = ({ isAIFeatureEnabled }) => { return (
@@ -20,13 +22,24 @@ export const GenAISettings: React.FunctionComponent = () => {
- {aiFeatureEnabled && ( -
- -
+ {isAIFeatureEnabled && ( + <> +
+ +
+ {/* TODO(COMPASS-7865): We're currently sending our sample field values to the server + and into the ai prompt as regular JSON. This means the AI isn't generating good + results with certain bson types. It'll take a bit of work server + side for us to do this. In the meantime we are hiding this setting. */} + {/* */} + )}
); }; -export default GenAISettings; +const mapState = (state: RootState) => ({ + isAIFeatureEnabled: !!state.settings.settings.enableGenAIFeatures, +}); + +export default connect(mapState, null)(GenAISettings); diff --git a/packages/compass-settings/src/components/settings/settings-list.tsx b/packages/compass-settings/src/components/settings/settings-list.tsx index e2317ff3158..bb8b0ac79af 100644 --- a/packages/compass-settings/src/components/settings/settings-list.tsx +++ b/packages/compass-settings/src/components/settings/settings-list.tsx @@ -78,13 +78,6 @@ function SettingLabel({ name }: { name: SupportedPreferences }) { dev )} - {name === 'enableGenAIFeatures' && ( - - - Preview - - - )} {long && {long}} diff --git a/packages/compass-shell/.eslintrc.js b/packages/compass-shell/.eslintrc.js index f06f8fc6013..b54d314ab18 100644 --- a/packages/compass-shell/.eslintrc.js +++ b/packages/compass-shell/.eslintrc.js @@ -1,3 +1,4 @@ +'use strict'; module.exports = { root: true, extends: ['@mongodb-js/eslint-config-compass/plugin'], diff --git a/packages/compass-shell/.mocharc.js b/packages/compass-shell/.mocharc.js index a7e53abc444..61091edba73 100644 --- a/packages/compass-shell/.mocharc.js +++ b/packages/compass-shell/.mocharc.js @@ -1 +1,2 @@ +'use strict'; module.exports = require('@mongodb-js/mocha-config-compass/compass-plugin'); diff --git a/packages/compass-shell/package.json b/packages/compass-shell/package.json index 66b89a68d70..3e8836d5961 100644 --- a/packages/compass-shell/package.json +++ b/packages/compass-shell/package.json @@ -6,7 +6,7 @@ "email": "compass@mongodb.com" }, "private": true, - "version": "3.23.1", + "version": "3.25.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -49,30 +49,30 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "dependencies": { - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-user-data": "^0.1.17", - "@mongodb-js/compass-utils": "^0.6.1", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-user-data": "^0.1.19", + "@mongodb-js/compass-utils": "^0.6.3", "@mongosh/browser-repl": "^2.2.3", "@mongosh/logging": "^2.2.3", "@mongosh/node-runtime-worker-thread": "^2.2.3", - "compass-preferences-model": "^2.18.1", - "hadron-app-registry": "^9.1.8", + "compass-preferences-model": "^2.20.0", + "hadron-app-registry": "^9.1.10", "prop-types": "^15.7.2", "react": "^17.0.2", "react-redux": "^8.1.3", "redux": "^4.2.1" }, "devDependencies": { - "@mongodb-js/connection-storage": "^0.8.1", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/connection-storage": "^0.10.0", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "chai": "^4.2.0", "depcheck": "^1.4.1", - "electron": "^28.2.10", + "electron": "^29.3.1", "electron-mocha": "^12.2.0", "enzyme": "^3.11.0", "eslint": "^7.25.0", diff --git a/packages/compass-shell/src/plugin.spec.tsx b/packages/compass-shell/src/plugin.spec.tsx index 2207b3b12f4..0adacaf3160 100644 --- a/packages/compass-shell/src/plugin.spec.tsx +++ b/packages/compass-shell/src/plugin.spec.tsx @@ -10,9 +10,13 @@ import { AppRegistryProvider, globalAppRegistry } from 'hadron-app-registry'; import { ConnectionsManager, ConnectionsManagerProvider, + ConnectionInfoProvider, } from '@mongodb-js/compass-connections/provider'; -import { ConnectionInfoProvider } from '@mongodb-js/connection-storage/provider'; import { createNoopLoggerAndTelemetry } from '@mongodb-js/compass-logging/provider'; +import { + ConnectionStorageProvider, + InMemoryConnectionStorage, +} from '@mongodb-js/connection-storage/provider'; // Wait until a component is present that is rendered in a limited number // of microtask queue iterations. In particular, this does *not* wait for the @@ -24,10 +28,10 @@ async function waitForAsyncComponent(wrapper, Component, attempts = 10) { wrapper.update(); result = wrapper.find(Component); // Return immediately if we found something - if (result.length > 0) { + if (result.length > 0 && result.exists()) { return result; } - await Promise.resolve(); // wait a microtask queue iteration + await new Promise((r) => setTimeout(r)); // wait a microtask queue iteration } return result; } @@ -63,18 +67,22 @@ describe('CompassShellPlugin', function () { {/* global */} {/* local */} - - - - - + + + + + + + ); const component = await waitForAsyncComponent(wrapper, CompassShell); - expect(component.exists()).to.equal(true); + expect(component?.exists()).to.equal(true); }); it('emits an event on the app registry when it is expanded', async function () { @@ -88,11 +96,15 @@ describe('CompassShellPlugin', function () { {/* global */} {/* local */} - - - - - + + + + + + + ); @@ -102,8 +114,8 @@ describe('CompassShellPlugin', function () { CompassShell ); - const { emitShellPluginOpened } = shellComponentWrapper.props(); - emitShellPluginOpened(); + const { emitShellPluginOpened } = shellComponentWrapper?.props() ?? {}; + emitShellPluginOpened?.(); expect(eventOccured).to.equal(true); }); diff --git a/packages/compass-sidebar/.eslintrc.js b/packages/compass-sidebar/.eslintrc.js index f06f8fc6013..b54d314ab18 100644 --- a/packages/compass-sidebar/.eslintrc.js +++ b/packages/compass-sidebar/.eslintrc.js @@ -1,3 +1,4 @@ +'use strict'; module.exports = { root: true, extends: ['@mongodb-js/eslint-config-compass/plugin'], diff --git a/packages/compass-sidebar/.mocharc.js b/packages/compass-sidebar/.mocharc.js index a7e53abc444..61091edba73 100644 --- a/packages/compass-sidebar/.mocharc.js +++ b/packages/compass-sidebar/.mocharc.js @@ -1 +1,2 @@ +'use strict'; module.exports = require('@mongodb-js/mocha-config-compass/compass-plugin'); diff --git a/packages/compass-sidebar/package.json b/packages/compass-sidebar/package.json index 604a440ca66..0d780bd790f 100644 --- a/packages/compass-sidebar/package.json +++ b/packages/compass-sidebar/package.json @@ -11,7 +11,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "5.24.1", + "version": "5.26.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -48,21 +48,20 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "dependencies": { - "@mongodb-js/compass-app-stores": "^7.10.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-databases-navigation": "^1.23.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-maybe-protect-connection-string": "^0.16.1", - "@mongodb-js/compass-workspaces": "^0.5.1", - "@mongodb-js/connection-form": "^1.23.1", - "@mongodb-js/connection-info": "^0.1.5", - "@mongodb-js/connection-storage": "^0.8.1", - "compass-preferences-model": "^2.18.1", - "hadron-app-registry": "^9.1.8", + "@mongodb-js/compass-app-stores": "^7.12.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-connections-navigation": "^1.25.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-maybe-protect-connection-string": "^0.18.0", + "@mongodb-js/compass-workspaces": "^0.7.0", + "@mongodb-js/connection-form": "^1.25.0", + "@mongodb-js/connection-info": "^0.2.1", + "compass-preferences-model": "^2.20.0", + "hadron-app-registry": "^9.1.10", "lodash": "^4.17.21", "mongodb": "^6.5.0", - "mongodb-instance-model": "^12.18.1", + "mongodb-instance-model": "^12.20.0", "mongodb-ns": "^2.4.0", "react": "^17.0.2", "react-redux": "^8.1.3", @@ -70,11 +69,12 @@ "redux-thunk": "^2.4.2" }, "devDependencies": { - "mongodb-data-service": "^22.18.1", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "mongodb-data-service": "^22.19.1", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/connection-storage": "^0.10.0", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "@types/chai": "^4.2.21", diff --git a/packages/compass-sidebar/src/components/connection-info-modal.tsx b/packages/compass-sidebar/src/components/connection-info-modal.tsx index a9c10d07832..8b8895bb716 100644 --- a/packages/compass-sidebar/src/components/connection-info-modal.tsx +++ b/packages/compass-sidebar/src/components/connection-info-modal.tsx @@ -5,6 +5,8 @@ import { ServerType, TopologyType } from 'mongodb-instance-model'; import type { ConnectionInfo as ConnectionStorageConnectionInfo } from '@mongodb-js/connection-info'; import type { RootState } from '../modules'; import type { Database } from '../modules/databases'; +import type { SingleConnectionOptionsState } from '../modules/connection-options'; +import type { SingleInstanceState } from '../modules/instance'; type ConnectionInfo = { term: string; @@ -44,11 +46,12 @@ function Info({ export function ConnectionInfoModal({ isVisible, close, - infos, + infos = [], }: { + connectionInfo?: ConnectionStorageConnectionInfo; isVisible: boolean; close: () => void; - infos: ConnectionInfo[]; + infos?: ConnectionInfo[]; }) { return ( & { +type InfoParameters = { + instance: SingleInstanceState; + connectionOptions: SingleConnectionOptionsState; databases: Database[]; connectionInfo: Partial; }; @@ -239,16 +244,22 @@ function getInfos(infoParameters: InfoParameters) { return infos; } -const mapStateToProps = (state: RootState) => { - const { instance, databases, connectionOptions } = state; - const { connectionInfo } = state.connectionInfo; +const mapStateToProps = ( + state: RootState, + { connectionInfo }: { connectionInfo?: ConnectionStorageConnectionInfo } +) => { + if (!connectionInfo) return { infos: [] }; + + const instance = state.instance[connectionInfo.id]; + const databases = state.databases[connectionInfo.id]; + const connectionOptions = state.connectionOptions[connectionInfo.id]; return { infos: getInfos({ - instance, - databases: databases.databases, - connectionInfo, - connectionOptions, + instance: instance, + databases: databases.databases ?? [], + connectionInfo: connectionInfo, + connectionOptions: connectionOptions || {}, }), }; }; diff --git a/packages/compass-sidebar/src/components/db-stats.tsx b/packages/compass-sidebar/src/components/db-stats.tsx index ce639edb6a9..13825ed466e 100644 --- a/packages/compass-sidebar/src/components/db-stats.tsx +++ b/packages/compass-sidebar/src/components/db-stats.tsx @@ -62,6 +62,7 @@ export function DBStats({ refreshingStatus, databases, }: { + connectionId: string; refreshingStatus: RefreshingStatus; databases: Database[]; }) { @@ -80,11 +81,12 @@ export function DBStats({ ); } -const mapStateToProps = (state: RootState) => ({ - refreshingStatus: state.instance - ? state.instance.refreshingStatus - : 'initial', - databases: state.databases.databases, +const mapStateToProps = ( + state: RootState, + { connectionId }: { connectionId: string } +) => ({ + refreshingStatus: state.instance[connectionId]?.refreshingStatus ?? 'initial', + databases: state.databases[connectionId].databases, }); const MappedDBStats = connect(mapStateToProps, {})(DBStats); diff --git a/packages/compass-sidebar/src/components/favorite-indicator.tsx b/packages/compass-sidebar/src/components/legacy/favorite-indicator.tsx similarity index 100% rename from packages/compass-sidebar/src/components/favorite-indicator.tsx rename to packages/compass-sidebar/src/components/legacy/favorite-indicator.tsx diff --git a/packages/compass-sidebar/src/components/navigation-items.spec.tsx b/packages/compass-sidebar/src/components/legacy/navigation-items.spec.tsx similarity index 94% rename from packages/compass-sidebar/src/components/navigation-items.spec.tsx rename to packages/compass-sidebar/src/components/legacy/navigation-items.spec.tsx index c3747eb7ba6..7c9eb92bc40 100644 --- a/packages/compass-sidebar/src/components/navigation-items.spec.tsx +++ b/packages/compass-sidebar/src/components/legacy/navigation-items.spec.tsx @@ -4,11 +4,13 @@ import { render, screen, cleanup } from '@testing-library/react'; import { Provider } from 'react-redux'; import { createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; -import reducer from '../modules'; +import reducer from '../../modules'; import { NavigationItems } from './navigation-items'; import { WorkspacesProvider } from '@mongodb-js/compass-workspaces'; +const CONNECTION_ID = 'webscale'; + function renderNavigationItems( props?: Partial> ) { @@ -23,6 +25,7 @@ function renderNavigationItems( > { /* noop */ }} @@ -31,8 +34,6 @@ function renderNavigationItems( onFilterChange={() => { /* noop */ }} - currentLocation={null} - currentNamespace={null} {...props} /> diff --git a/packages/compass-sidebar/src/components/navigation-items.tsx b/packages/compass-sidebar/src/components/legacy/navigation-items.tsx similarity index 87% rename from packages/compass-sidebar/src/components/navigation-items.tsx rename to packages/compass-sidebar/src/components/legacy/navigation-items.tsx index 46a912a7076..c9afd74c066 100644 --- a/packages/compass-sidebar/src/components/navigation-items.tsx +++ b/packages/compass-sidebar/src/components/legacy/navigation-items.tsx @@ -23,14 +23,16 @@ import { withPreferences, } from 'compass-preferences-model/provider'; import type { ItemAction } from '@mongodb-js/compass-components'; -import DatabaseCollectionFilter from './database-collection-filter'; +import DatabaseCollectionFilter from '../database-collection-filter'; import SidebarDatabasesNavigation from './sidebar-databases-navigation'; -import { changeFilterRegex } from '../modules/databases'; -import type { RootState } from '../modules'; +import { changeFilterRegex } from '../../modules/databases'; +import type { RootState } from '../../modules'; import { useOpenWorkspace, useWorkspacePlugins, } from '@mongodb-js/compass-workspaces/provider'; +import type { ConnectionInfo } from '@mongodb-js/connection-info'; +import type { WorkspaceTab } from '@mongodb-js/compass-workspaces'; type DatabasesActions = 'open-create-database' | 'refresh-databases'; @@ -252,21 +254,21 @@ const PlaceholderItem = ({ forLabel }: { forLabel: string }) => { export function NavigationItems({ isReady, + connectionInfo, showCreateDatabaseAction, isPerformanceTabSupported, onFilterChange, onAction, - currentLocation, - currentNamespace, + activeWorkspace, showTooManyCollectionsInsight = false, }: { isReady?: boolean; + connectionInfo: ConnectionInfo; showCreateDatabaseAction: boolean; isPerformanceTabSupported: boolean; onFilterChange(regex: RegExp | null): void; onAction(actionName: string, ...rest: any[]): void; - currentLocation: string | null; - currentNamespace: string | null; + activeWorkspace?: WorkspaceTab; showTooManyCollectionsInsight?: boolean; }) { const { @@ -325,27 +327,27 @@ export function NavigationItems({ onClick={openMyQueriesWorkspace} glyph="CurlyBraces" label="My Queries" - isActive={currentLocation === 'My Queries'} + isActive={activeWorkspace?.type === 'My Queries'} /> )} {hasWorkspacePlugin('Performance') && ( onAction={onAction} - onClick={openPerformanceWorkspace} + onClick={() => openPerformanceWorkspace(connectionInfo.id)} glyph="Gauge" label="Performance" - isActive={currentLocation === 'Performance'} + isActive={activeWorkspace?.type === 'Performance'} disabled={!isPerformanceTabSupported} disabledMessage="Performance metrics are not available for your deployment or to your database user" /> )} onAction={onAction} - onClick={openDatabasesWorkspace} + onClick={() => openDatabasesWorkspace(connectionInfo.id)} glyph="Database" label="Databases" actions={databasesActions} - isActive={currentLocation === 'Databases'} + isActive={activeWorkspace?.type === 'Databases'} showTooManyCollectionsInsight={showTooManyCollectionsInsight} /> @@ -356,7 +358,8 @@ export function NavigationItems({ ); @@ -364,9 +367,13 @@ export function NavigationItems({ const mapStateToProps = ( state: RootState, - { readOnly: preferencesReadOnly }: { readOnly: boolean } + { + connectionInfo, + readOnly: preferencesReadOnly, + }: { connectionInfo: ConnectionInfo; readOnly: boolean } ) => { - const totalCollectionsCount = state.databases.databases.reduce( + const connectionId = connectionInfo.id; + const totalCollectionsCount = state.databases[connectionId].databases.reduce( (acc: number, db: { collectionsLength: number }) => { return acc + db.collectionsLength; }, @@ -374,11 +381,12 @@ const mapStateToProps = ( ); const isReady = - ['ready', 'refreshing'].includes(state.instance?.status ?? '') && - state.isPerformanceTabSupported !== null; + ['ready', 'refreshing'].includes( + state.instance[connectionId]?.status ?? '' + ) && state.isPerformanceTabSupported !== null; - const isDataLake = state.instance?.dataLake.isDataLake ?? false; - const isWritable = state.instance?.isWritable ?? false; + const isDataLake = state.instance[connectionId]?.dataLake.isDataLake ?? false; + const isWritable = state.instance[connectionId]?.isWritable ?? false; return { isReady, diff --git a/packages/compass-sidebar/src/components/legacy/sidebar-databases-navigation.tsx b/packages/compass-sidebar/src/components/legacy/sidebar-databases-navigation.tsx new file mode 100644 index 00000000000..3c077157c19 --- /dev/null +++ b/packages/compass-sidebar/src/components/legacy/sidebar-databases-navigation.tsx @@ -0,0 +1,220 @@ +import React, { useCallback, useEffect } from 'react'; +import { connect } from 'react-redux'; +import { ConnectionsNavigationTree } from '@mongodb-js/compass-connections-navigation'; +import type { + Actions, + Connection, +} from '@mongodb-js/compass-connections-navigation'; +import toNS from 'mongodb-ns'; +import { type Database, toggleDatabaseExpanded } from '../../modules/databases'; +import { usePreference } from 'compass-preferences-model/provider'; +import type { RootState, SidebarThunkAction } from '../../modules'; +import { useOpenWorkspace } from '@mongodb-js/compass-workspaces/provider'; +import type { ConnectionInfo } from '@mongodb-js/connection-info'; + +function findCollection(ns: string, databases: Database[]) { + const { database, collection } = toNS(ns); + + return ( + databases + .find((db) => db._id === database) + ?.collections.find((coll) => coll.name === collection) ?? null + ); +} + +function SidebarDatabasesNavigation({ + connections, + onNamespaceAction: _onNamespaceAction, + onDatabaseExpand, + activeWorkspace, + ...dbNavigationProps +}: Omit< + React.ComponentProps, + 'isReadOnly' | 'databases' +> & { + connections: Connection[]; + expanded?: Record | false>; +}) { + const { + openCollectionsWorkspace, + openCollectionWorkspace, + openEditViewWorkspace, + } = useOpenWorkspace(); + const preferencesReadOnly = usePreference('readOnly'); + const connection = connections[0]; + const isReadOnly = + preferencesReadOnly || connection.isDataLake || !connection.isWritable; + const onNamespaceAction = useCallback( + (connectionId: string, ns: string, action: Actions) => { + switch (action) { + case 'select-database': + openCollectionsWorkspace(connectionId, ns); + return; + case 'select-collection': + openCollectionWorkspace(connectionId, ns); + return; + case 'open-in-new-tab': + openCollectionWorkspace(connectionId, ns, { newTab: true }); + return; + case 'modify-view': { + const coll = findCollection( + ns, + (connection.databases as Database[]) || [] + ); + + if (coll && coll.sourceName && coll.pipeline) { + openEditViewWorkspace(connectionId, coll._id, { + sourceName: coll.sourceName, + sourcePipeline: coll.pipeline, + newTab: true, + }); + } + return; + } + default: + _onNamespaceAction(connectionId, ns, action); + return; + } + }, + [ + connection, + openCollectionsWorkspace, + openCollectionWorkspace, + openEditViewWorkspace, + _onNamespaceAction, + ] + ); + + // auto-expanding on a workspace change + useEffect(() => { + if ( + activeWorkspace && + (activeWorkspace.type === 'Collections' || + activeWorkspace.type === 'Collection') + ) { + const namespace: string = activeWorkspace.namespace; + onDatabaseExpand(connection.connectionInfo.id, namespace, true); + } + }, [activeWorkspace, onDatabaseExpand, connection.connectionInfo.id]); + + return ( + + ); +} + +function mapStateToProps( + state: RootState, + { connectionInfo }: { connectionInfo: ConnectionInfo } +): { + isReady: boolean; + connections: Connection[]; + expanded: Record | false>; +} { + const connectionId = connectionInfo.id; + const instance = state.instance[connectionId]; + const { + filterRegex, + filteredDatabases, + expandedDbList: initialExpandedDbList, + } = state.databases[connectionId] || {}; + + const status = instance?.databasesStatus; + const isReady = + status !== undefined && !['initial', 'fetching'].includes(status); + const defaultExpanded = Boolean(filterRegex); + + const expandedDbList = initialExpandedDbList ?? {}; + const expanded = Object.fromEntries( + ((filteredDatabases as any[]) || []).map(({ name }) => [ + name, + expandedDbList[name] ?? defaultExpanded, + ]) + ); + + const isDataLake = instance?.dataLake?.isDataLake ?? false; + const isWritable = instance?.isWritable ?? false; + + return { + isReady: true, + connections: [ + { + isReady, + isDataLake, + isWritable, + name: '', + connectionInfo, + databasesLength: filteredDatabases?.length || 0, + databasesStatus: (status ?? + 'fetching') as Connection['databasesStatus'], + databases: filteredDatabases ?? [], + }, + ], + expanded: { + [connectionId]: expanded, + }, + }; +} + +const onNamespaceAction = ( + connectionId: string, + namespace: string, + action: Actions +): SidebarThunkAction => { + return (_dispatch, getState, { globalAppRegistry }) => { + // TODO: COMPASS-7719 to adapt modals to be multiple connection aware + const emit = (action: string, ...rest: any[]) => { + globalAppRegistry.emit(action, ...rest); + }; + const ns = toNS(namespace); + switch (action) { + case 'drop-database': + emit('open-drop-database', ns.database, { connectionId }); + return; + case 'rename-collection': + emit('open-rename-collection', ns, { connectionId }); + return; + case 'drop-collection': + emit('open-drop-collection', ns, { connectionId }); + return; + case 'create-collection': + emit('open-create-collection', ns, { + connectionId, + }); + return; + case 'duplicate-view': { + const coll = findCollection( + namespace, + getState().databases[connectionId].databases + ); + if (coll && coll.sourceName) { + emit( + 'open-create-view', + { + source: coll.sourceName, + pipeline: coll.pipeline, + duplicate: true, + }, + { + connectionId, + } + ); + } + return; + } + default: + // no-op + } + }; +}; + +export default connect(mapStateToProps, { + onDatabaseExpand: toggleDatabaseExpanded, + onNamespaceAction, +})(SidebarDatabasesNavigation); diff --git a/packages/compass-sidebar/src/components/sidebar-title.tsx b/packages/compass-sidebar/src/components/legacy/sidebar-title.tsx similarity index 100% rename from packages/compass-sidebar/src/components/sidebar-title.tsx rename to packages/compass-sidebar/src/components/legacy/sidebar-title.tsx diff --git a/packages/compass-sidebar/src/components/sidebar.tsx b/packages/compass-sidebar/src/components/legacy/sidebar.tsx similarity index 66% rename from packages/compass-sidebar/src/components/sidebar.tsx rename to packages/compass-sidebar/src/components/legacy/sidebar.tsx index 1b647dfe6a2..65edcdb223e 100644 --- a/packages/compass-sidebar/src/components/sidebar.tsx +++ b/packages/compass-sidebar/src/components/legacy/sidebar.tsx @@ -10,20 +10,21 @@ import { useToast, } from '@mongodb-js/compass-components'; import { SaveConnectionModal } from '@mongodb-js/connection-form'; +import { useConnectionRepository } from '@mongodb-js/compass-connections/provider'; import SidebarTitle from './sidebar-title'; import NavigationItems from './navigation-items'; -import ConnectionInfoModal from './connection-info-modal'; -import NonGenuineWarningModal from './non-genuine-warning-modal'; -import CSFLEConnectionModal from './csfle-connection-modal'; -import CSFLEMarker from './csfle-marker'; -import NonGenuineMarker from './non-genuine-marker'; - -import { setConnectionIsCSFLEEnabled } from '../modules/data-service'; -import { updateAndSaveConnectionInfo } from '../modules/connection-info'; -import { toggleIsGenuineMongoDBVisible } from '../modules/is-genuine-mongodb-visible'; +import ConnectionInfoModal from '../connection-info-modal'; +import NonGenuineWarningModal from '../non-genuine-warning-modal'; +import CSFLEConnectionModal from '../csfle-connection-modal'; +import CSFLEMarker from '../csfle-marker'; +import NonGenuineMarker from '../non-genuine-marker'; + +import { setConnectionIsCSFLEEnabled } from '../../modules/data-service'; +import { toggleIsGenuineMongoDBVisible } from '../../modules/is-genuine-mongodb-visible'; import { useMaybeProtectConnectionString } from '@mongodb-js/compass-maybe-protect-connection-string'; -import type { RootState, SidebarThunkAction } from '../modules'; +import type { RootState, SidebarThunkAction } from '../../modules'; +import type { WorkspaceTab } from '@mongodb-js/compass-workspaces'; const TOAST_TIMEOUT_MS = 5000; // 5 seconds. @@ -34,8 +35,6 @@ const sidebarStyles = css({ zIndex: 0, }); -const connectionInfoContainerStyles = css({}); - const connectionBadgesContainerStyles = css({ display: 'grid', gridTemplateColumns: '100%', @@ -61,8 +60,7 @@ const navigationItemsContainerStyles = css({ export function Sidebar({ showConnectionInfo = true, activeWorkspace, - connectionInfo, - updateAndSaveConnectionInfo, + initialConnectionInfo, isGenuineMongoDBVisible, toggleIsGenuineMongoDBVisible, setConnectionIsCSFLEEnabled, @@ -71,16 +69,19 @@ export function Sidebar({ onSidebarAction, }: { showConnectionInfo?: boolean; - activeWorkspace: { type: string; namespace?: string } | null; - connectionInfo: Omit & Partial; - updateAndSaveConnectionInfo: any; + activeWorkspace: WorkspaceTab | null; + initialConnectionInfo: ConnectionInfo; isGenuineMongoDBVisible: boolean; - toggleIsGenuineMongoDBVisible: (isVisible: boolean) => void; - setConnectionIsCSFLEEnabled: (enabled: boolean) => void; + toggleIsGenuineMongoDBVisible: ( + connectionId: string, + isVisible: boolean + ) => void; + setConnectionIsCSFLEEnabled: (connectionId: string, enabled: boolean) => void; isGenuine?: boolean; csfleMode?: 'enabled' | 'disabled' | 'unavailable'; onSidebarAction(action: string, ...rest: any[]): void; }) { + const { saveConnection } = useConnectionRepository(); const [isFavoriteModalVisible, setIsFavoriteModalVisible] = useState(false); const [isConnectionInfoModalVisible, setIsConnectionInfoModalVisible] = useState(false); @@ -89,12 +90,12 @@ export function Sidebar({ (newFavoriteInfo) => { setIsFavoriteModalVisible(false); - return updateAndSaveConnectionInfo({ - ...cloneDeep(connectionInfo), + return saveConnection({ + ...cloneDeep(initialConnectionInfo), favorite: newFavoriteInfo, - }); + }) as Promise as Promise; }, - [connectionInfo, updateAndSaveConnectionInfo, setIsFavoriteModalVisible] + [initialConnectionInfo, saveConnection, setIsFavoriteModalVisible] ); const { openToast } = useToast('compass-connections'); @@ -125,7 +126,7 @@ export function Sidebar({ if (action === 'copy-connection-string') { void copyConnectionString( maybeProtectConnectionString( - connectionInfo.connectionOptions.connectionString + initialConnectionInfo.connectionOptions.connectionString ?? '' ) ); return; @@ -141,19 +142,27 @@ export function Sidebar({ return; } + if (action === 'open-create-database') { + onSidebarAction(action, ...rest, { + connectionId: initialConnectionInfo.id, + }); + return; + } + onSidebarAction(action, ...rest); }, [ + initialConnectionInfo.id, onSidebarAction, openToast, maybeProtectConnectionString, - connectionInfo.connectionOptions.connectionString, + initialConnectionInfo.connectionOptions.connectionString, ] ); const showNonGenuineModal = useCallback(() => { - toggleIsGenuineMongoDBVisible(true); - }, [toggleIsGenuineMongoDBVisible]); + toggleIsGenuineMongoDBVisible(initialConnectionInfo.id, true); + }, [initialConnectionInfo.id, toggleIsGenuineMongoDBVisible]); const [isCSFLEModalVisible, setIsCSFLEModalVisible] = useState(false); @@ -168,11 +177,11 @@ export function Sidebar({ > <> {showConnectionInfo && ( -
+
@@ -190,29 +199,34 @@ export function Sidebar({
setIsFavoriteModalVisible(false)} onSaveClicked={(favoriteInfo) => onClickSaveFavorite(favoriteInfo)} /> + toggleIsGenuineMongoDBVisible(initialConnectionInfo.id, visible) + } /> setIsCSFLEModalVisible(open)} csfleMode={csfleMode} - setConnectionIsCSFLEEnabled={setConnectionIsCSFLEEnabled} + setConnectionIsCSFLEEnabled={(enabled) => + setConnectionIsCSFLEEnabled(initialConnectionInfo.id, enabled) + } /> setIsConnectionInfoModalVisible(false)} /> @@ -221,12 +235,22 @@ export function Sidebar({ ); } -const mapStateToProps = (state: RootState) => ({ - connectionInfo: state.connectionInfo.connectionInfo, - isGenuineMongoDBVisible: state.isGenuineMongoDBVisible, - isGenuine: state.instance?.genuineMongoDB.isGenuine, - csfleMode: state.instance?.csfleMode, -}); +const mapStateToProps = ( + state: RootState, + { + initialConnectionInfo, + }: { + initialConnectionInfo: Partial & Pick; + } +) => { + return { + isGenuineMongoDBVisible: + state.isGenuineMongoDBVisible[initialConnectionInfo.id], + isGenuine: + state.instance[initialConnectionInfo.id]?.genuineMongoDB.isGenuine, + csfleMode: state.instance[initialConnectionInfo.id]?.csfleMode, + }; +}; const onSidebarAction = ( action: string, @@ -238,7 +262,6 @@ const onSidebarAction = ( }; const MappedSidebar = connect(mapStateToProps, { - updateAndSaveConnectionInfo, toggleIsGenuineMongoDBVisible, setConnectionIsCSFLEEnabled, onSidebarAction, diff --git a/packages/compass-sidebar/src/components/multiple-connections/active-connections/active-connection-list.spec.tsx b/packages/compass-sidebar/src/components/multiple-connections/active-connections/active-connection-list.spec.tsx deleted file mode 100644 index 888f17707d5..00000000000 --- a/packages/compass-sidebar/src/components/multiple-connections/active-connections/active-connection-list.spec.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import React from 'react'; -import { expect } from 'chai'; -import { render, screen, waitFor } from '@testing-library/react'; -import type { ConnectionInfo } from '@mongodb-js/connection-info'; -import { ActiveConnectionList } from './active-connection-list'; -import { ConnectionStorageContext } from '@mongodb-js/connection-storage/provider'; -import { - ConnectionsManager, - ConnectionsManagerProvider, -} from '@mongodb-js/compass-connections/provider'; -import type { ConnectionStorage } from '@mongodb-js/connection-storage/renderer'; -import Sinon from 'sinon'; -import EventEmitter from 'events'; - -const mockConnections: ConnectionInfo[] = [ - { - id: 'turtle', - connectionOptions: { - connectionString: 'mongodb://turtle', - }, - savedConnectionType: 'recent', - }, - { - id: 'oranges', - connectionOptions: { - connectionString: 'mongodb://peaches', - }, - favorite: { - name: 'peaches', - }, - savedConnectionType: 'favorite', - }, -]; - -describe('', function () { - let connectionsManager: ConnectionsManager; - let mockConnectionStorage: typeof ConnectionStorage; - - beforeEach(() => { - connectionsManager = new ConnectionsManager({} as any); - (connectionsManager as any).connectionStatuses.set('turtle', 'connected'); - (connectionsManager as any).connectionStatuses.set('oranges', 'connected'); - mockConnectionStorage = { - events: new EventEmitter(), - loadAll: Sinon.stub().resolves(mockConnections), - } as any; - - render( - - - - - - ); - }); - - it('Should render all active connections - using their correct titles', async function () { - await waitFor(() => { - expect(screen.queryByText('(2)')).to.be.visible; - expect(screen.queryByText('turtle')).to.be.visible; - expect(screen.queryByText('peaches')).to.be.visible; - }); - }); -}); diff --git a/packages/compass-sidebar/src/components/multiple-connections/active-connections/active-connection-list.tsx b/packages/compass-sidebar/src/components/multiple-connections/active-connections/active-connection-list.tsx deleted file mode 100644 index 13ee6e76aa7..00000000000 --- a/packages/compass-sidebar/src/components/multiple-connections/active-connections/active-connection-list.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { Subtitle, css, spacing } from '@mongodb-js/compass-components'; -import React, { useEffect, useState } from 'react'; -import { useActiveConnections } from '@mongodb-js/compass-connections/provider'; -import { ActiveConnection } from './active-connection'; -import { - getConnectionTitle, - type ConnectionInfo, -} from '@mongodb-js/connection-info'; - -const activeConnectionsContainerStyles = css({ - height: '100%', - padding: spacing[3], -}); - -const activeConnectionListHeaderStyles = css({ - flexGrow: 0, - display: 'flex', - flexDirection: 'row', - alignContent: 'center', - justifyContent: 'space-between', -}); - -const activeConnectionListHeaderTitleStyles = css({ - marginTop: 0, - marginBottom: 0, - textTransform: 'uppercase', - fontSize: '12px', -}); - -const activeConnectionCountStyles = css({ - fontWeight: 'normal', -}); - -const activeConnectionsListStyles = css({ - listStyle: 'none', - height: '100%', -}); - -export function ActiveConnectionList(): React.ReactElement { - const activeConnections = useActiveConnections(); - const connectionsCount = activeConnections.length; - const [collapsed, setCollapsed] = useState([]); - const [namedConnections, setNamedConnections] = useState< - (ConnectionInfo & { title: string })[] - >([]); - - const onConnectionToggle = (connectionId: string, isExpanded: boolean) => { - if (!isExpanded && !collapsed.includes(connectionId)) - setCollapsed([...collapsed, connectionId]); - if (isExpanded && collapsed.includes(connectionId)) { - const index = collapsed.indexOf(connectionId); - setCollapsed([ - ...collapsed.slice(0, index), - ...collapsed.slice(index + 1), - ]); - } - }; - - useEffect(() => { - // cleanup connections that are no longer active - // if the user connects again, the new connection should be expanded again - const newCollapsed = activeConnections - .filter(({ id }: ConnectionInfo) => collapsed.includes(id)) - .map(({ id }: ConnectionInfo) => id); - setCollapsed(newCollapsed); - - const newConnectionList = activeConnections - .map((connectionInfo) => ({ - ...connectionInfo, - title: getConnectionTitle(connectionInfo), - })) - .sort((a, b) => a.title.localeCompare(b.title)); - setNamedConnections(newConnectionList); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [activeConnections]); - - return ( -
-
- - Active connections{' '} - - ({connectionsCount}) - - -
-
    - {namedConnections.map((connection) => ( - - onConnectionToggle(connection.id, isExpanded) - } - /> - ))} -
-
- ); -} diff --git a/packages/compass-sidebar/src/components/multiple-connections/active-connections/active-connection-navigation.spec.tsx b/packages/compass-sidebar/src/components/multiple-connections/active-connections/active-connection-navigation.spec.tsx new file mode 100644 index 00000000000..f4d253120fa --- /dev/null +++ b/packages/compass-sidebar/src/components/multiple-connections/active-connections/active-connection-navigation.spec.tsx @@ -0,0 +1,326 @@ +import React from 'react'; +import { expect } from 'chai'; +import { render, screen, waitFor } from '@testing-library/react'; +import ActiveConnectionNavigation from './active-connection-navigation'; +import { + ConnectionsManager, + ConnectionsManagerProvider, + type ConnectionInfo, +} from '@mongodb-js/compass-connections/provider'; +import { createSidebarStore } from '../../../stores'; +import { Provider } from 'react-redux'; +import AppRegistry from 'hadron-app-registry'; +import { createNoopLoggerAndTelemetry } from '@mongodb-js/compass-logging/provider'; +import userEvent from '@testing-library/user-event'; +import sinon from 'sinon'; +import { createSandboxFromDefaultPreferences } from 'compass-preferences-model'; +import { + type PreferencesAccess, + PreferencesProvider, +} from 'compass-preferences-model/provider'; +import type { WorkspaceTab } from '@mongodb-js/compass-workspaces'; +import { EventEmitter } from 'events'; + +class MockInstance extends EventEmitter { + _id: string; + status: string; + databasesStatus: string; + databases: { + _id: string; + name: string; + status: string; + collectionsLength: number; + collectionsStatus: string; + collections: any[]; + }[]; + build: Record; + dataLake: Record; + genuineMongoDB: Record; + topologyDescription: Record; + + constructor() { + super(); + this._id = 'turtle'; + this.status = 'ready'; + this.databasesStatus = 'ready'; + this.databases = [ + { + _id: 'turtleDB1', + name: 'turtleDB1', + status: 'ready', + collectionsLength: 1, + collectionsStatus: 'ready', + collections: [ + { + _id: 'turtleDB1Coll1', + name: 'turtleDB1Coll1', + type: 'collection', + }, + ], + }, + ]; + this.build = {}; + this.dataLake = {}; + this.genuineMongoDB = {}; + this.topologyDescription = {}; + } +} + +const mockConnections: ConnectionInfo[] = [ + { + id: 'turtle', + connectionOptions: { + connectionString: 'mongodb://turtle', + }, + savedConnectionType: 'recent', + }, + { + id: 'oranges', + connectionOptions: { + connectionString: 'mongodb://peaches', + }, + favorite: { + name: 'peaches', + }, + savedConnectionType: 'favorite', + }, +]; + +describe('', function () { + let connectionsManager: ConnectionsManager; + let preferences: PreferencesAccess; + let store: ReturnType['store']; + let turtleInstance: EventEmitter; + let deactivate: () => void; + const globalAppRegistry = new AppRegistry(); + const onOpenConnectionInfoStub = sinon.stub(); + const onCopyConnectionStringStub = sinon.stub(); + const onToggleFavoriteConnectionStub = sinon.stub(); + + const renderActiveConnectionsNavigation = async ({ + activeWorkspace = { + type: 'Databases', + connectionId: 'turtle', + id: '12345', + }, + }: { + activeWorkspace?: WorkspaceTab; + } = {}) => { + turtleInstance = new MockInstance(); + connectionsManager = new ConnectionsManager({} as any); + (connectionsManager as any).connectionStatuses.set('turtle', 'connected'); + (connectionsManager as any).connectionStatuses.set('oranges', 'connected'); + ({ store, deactivate } = createSidebarStore( + { + globalAppRegistry, + instancesManager: { + listMongoDBInstances() { + return new Map([['turtle', turtleInstance]]); + }, + } as any, + connectionsManager, + logger: createNoopLoggerAndTelemetry(), + }, + { on() {}, cleanup() {}, addCleanup() {} } as any + )); + + preferences = await createSandboxFromDefaultPreferences(); + await preferences.savePreferences({ + enableRenameCollectionModal: true, + enableNewMultipleConnectionSystem: true, + }); + + return render( + + + + + + + + ); + }; + + afterEach(() => { + deactivate(); + sinon.resetHistory(); + }); + + it('Should render the number of connections', async function () { + await renderActiveConnectionsNavigation(); + await waitFor(() => { + expect(screen.queryByText('(2)')).to.be.visible; + }); + }); + + describe('Connection actions', () => { + beforeEach(async () => { + await renderActiveConnectionsNavigation(); + }); + + it('Calls onOpenConnectionInfo', async () => { + userEvent.hover(screen.getByText('turtle')); + + const connectionActionsBtn = screen.getByTitle('Show actions'); + expect(connectionActionsBtn).to.be.visible; + + userEvent.click(connectionActionsBtn); + + const openConnectionInfoBtn = await screen.findByText( + 'Show connection info' + ); + expect(openConnectionInfoBtn).to.be.visible; + + userEvent.click(openConnectionInfoBtn); + + expect(onOpenConnectionInfoStub).to.have.been.calledWith('turtle'); + }); + + it('Calls onCopyConnectionString', async () => { + userEvent.hover(screen.getByText('turtle')); + + const connectionActionsBtn = screen.getByTitle('Show actions'); + expect(connectionActionsBtn).to.be.visible; + + userEvent.click(connectionActionsBtn); + + const copyConnectionStringBtn = await screen.findByText( + 'Copy connection string' + ); + expect(copyConnectionStringBtn).to.be.visible; + + userEvent.click(copyConnectionStringBtn); + + expect(onCopyConnectionStringStub).to.have.been.calledWith('turtle'); + }); + + it('Calls onToggleFavoriteConnection', async () => { + userEvent.hover(screen.getByText('turtle')); + + const connectionActionsBtn = screen.getByTitle('Show actions'); + expect(connectionActionsBtn).to.be.visible; + + userEvent.click(connectionActionsBtn); + + const favoriteBtn = await screen.findByText('Favorite'); + expect(favoriteBtn).to.be.visible; + + userEvent.click(favoriteBtn); + + expect(onToggleFavoriteConnectionStub).to.have.been.calledWith('turtle'); + }); + }); + + describe('Collapse and Auto-expand', () => { + it('should collapse a connection, and expand it automatically when a child workspace is entered', async function () { + // step 1 - turtle connection is expanded at first + const { rerender } = await renderActiveConnectionsNavigation({ + activeWorkspace: { type: 'My Queries', id: 'abcd' }, + }); + + turtleInstance.emit('change:databasesStatus'); + + expect(screen.getByText('turtleDB1')).to.be.visible; + + // step 2 - user collapses the turtle connection + const connectionItem = screen.getByText('turtle'); + + userEvent.click(connectionItem); + userEvent.keyboard('[ArrowLeft]'); + + expect(screen.queryByText('turtleDB1')).not.to.exist; + + // step 3 - user entered a workspace that belongs to the turtle connection + rerender( + + + + + + + + ); + + expect(screen.getByText('turtleDB1')).to.be.visible; + }); + + it('should expand a database when a child workspace is entered', async function () { + // step 1 - turtleDB1 is collapsed at first + const { rerender } = await renderActiveConnectionsNavigation({ + activeWorkspace: { type: 'My Queries', id: 'abcd' }, + }); + + turtleInstance.emit('change:databasesStatus'); + + expect(screen.queryByText('turtleDB1Coll1')).not.to.exist; + + // step 2 - user entered a workspace that belongs to the turtleDB1 database + rerender( + + + + + + + + ); + + expect(screen.getByText('turtleDB1Coll1')).to.be.visible; + }); + + it('should collapse and expand database', async function () { + // step 1 - turtleDB1 is collapsed at first + await renderActiveConnectionsNavigation({ + activeWorkspace: { type: 'My Queries', id: 'abcd' }, + }); + + turtleInstance.emit('change:databasesStatus'); + + expect(screen.queryByText('turtleDB1Coll1')).not.to.exist; + + // step 2 - user expands the turtleDB1 database + const databaseItem = screen.getByText('turtleDB1'); + + userEvent.click(databaseItem); + userEvent.keyboard('[ArrowRight]'); + + expect(screen.getByText('turtleDB1Coll1')).to.be.visible; + + // step 2 - user collapses the turtleDB1 database + userEvent.click(databaseItem); + userEvent.keyboard('[ArrowLeft]'); + + expect(screen.queryByText('turtleDB1Coll1')).not.to.exist; + }); + }); +}); diff --git a/packages/compass-sidebar/src/components/multiple-connections/active-connections/active-connection-navigation.tsx b/packages/compass-sidebar/src/components/multiple-connections/active-connections/active-connection-navigation.tsx new file mode 100644 index 00000000000..766d2e96b4c --- /dev/null +++ b/packages/compass-sidebar/src/components/multiple-connections/active-connections/active-connection-navigation.tsx @@ -0,0 +1,357 @@ +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import { connect } from 'react-redux'; +import { + type Connection, + type Actions, + ConnectionsNavigationTree, +} from '@mongodb-js/compass-connections-navigation'; +import { + type ConnectionInfo, + getConnectionTitle, +} from '@mongodb-js/connection-info'; +import toNS from 'mongodb-ns'; +import { + type Database, + toggleDatabaseExpanded, +} from '../../../modules/databases'; +import type { RootState, SidebarThunkAction } from '../../../modules'; +import { useOpenWorkspace } from '@mongodb-js/compass-workspaces/provider'; +import { + Subtitle, + css, + palette, + spacing, +} from '@mongodb-js/compass-components'; +import type { WorkspaceTab } from '@mongodb-js/compass-workspaces'; + +function findCollection(ns: string, databases: Database[]) { + const { database, collection } = toNS(ns); + + return ( + databases + .find((db) => db._id === database) + ?.collections.find((coll) => coll.name === collection) ?? null + ); +} + +const activeConnectionsContainerStyles = css({ + height: '100%', + padding: `${spacing[2]}px ${spacing[3]}px`, + borderTop: `1px solid ${palette.gray.light2}`, +}); + +const activeConnectionListHeaderStyles = css({ + flexGrow: 0, + display: 'flex', + flexDirection: 'row', + alignContent: 'center', + justifyContent: 'space-between', +}); + +const activeConnectionListHeaderTitleStyles = css({ + marginTop: 0, + marginBottom: 0, + textTransform: 'uppercase', + fontSize: '12px', +}); + +const activeConnectionCountStyles = css({ + fontWeight: 'normal', +}); + +export function ActiveConnectionNavigation({ + activeConnections, + connections, + expanded, + activeWorkspace, + onNamespaceAction: _onNamespaceAction, + onOpenConnectionInfo, + onCopyConnectionString, + onToggleFavoriteConnection, + onDatabaseExpand, + ...navigationProps +}: Omit< + React.ComponentProps, + | 'isReadOnly' + | 'databases' + | 'connections' + | 'expanded' + | 'onConnectionExpand' + | 'isReady' +> & { + activeConnections: ConnectionInfo[]; + connections: Connection[]; + isDataLake?: boolean; + isWritable?: boolean; + expanded: Record | false>; + activeWorkspace?: WorkspaceTab; + onOpenConnectionInfo: (connectionId: string) => void; + onCopyConnectionString: (connectionId: string) => void; + onToggleFavoriteConnection: (connectionId: string) => void; +}): React.ReactElement { + const [collapsed, setCollapsed] = useState([]); + const [namedConnections, setNamedConnections] = useState< + { connectionInfo: ConnectionInfo; name: string }[] + >([]); + + const { + openDatabasesWorkspace, + openCollectionsWorkspace, + openCollectionWorkspace, + openEditViewWorkspace, + } = useOpenWorkspace(); + + const onConnectionToggle = useCallback( + (connectionId: string, forceExpand: boolean) => { + if (!forceExpand && !collapsed.includes(connectionId)) + setCollapsed((collapsed) => [...collapsed, connectionId]); + else if (forceExpand && collapsed.includes(connectionId)) { + setCollapsed((collapsed) => { + const index = collapsed.indexOf(connectionId); + return [...collapsed.slice(0, index), ...collapsed.slice(index + 1)]; + }); + } + }, + [setCollapsed, collapsed] + ); + + useEffect(() => { + // cleanup connections that are no longer active + // if the user connects again, the new connection should be expanded again + const newCollapsed = activeConnections + .filter(({ id }: ConnectionInfo) => collapsed.includes(id)) + .map(({ id }: ConnectionInfo) => id); + setCollapsed(newCollapsed); + + const newConnectionList = activeConnections + .map((connectionInfo) => ({ + connectionInfo, + name: getConnectionTitle(connectionInfo), + })) + .sort((a, b) => a.name.localeCompare(b.name)); + setNamedConnections(newConnectionList); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [activeConnections]); + + const onConnectionToggleRef = useRef(onConnectionToggle); + onConnectionToggleRef.current = onConnectionToggle; + // auto-expanding on a workspace change + useEffect(() => { + if ( + activeWorkspace && + (activeWorkspace.type === 'Databases' || + activeWorkspace.type === 'Collections' || + activeWorkspace.type === 'Collection') + ) { + const connectionId: string = activeWorkspace.connectionId; + // we're using a ref for this toggle because collapsing depends on the collapsed state, + // but we don't want to auto-expand when collapsed state changes, only workspace + onConnectionToggleRef.current(connectionId, true); + + if (activeWorkspace.type !== 'Databases') { + const namespace: string = activeWorkspace.namespace; + onDatabaseExpand(connectionId, namespace, true); + } + } + }, [activeWorkspace, onDatabaseExpand]); + + const onNamespaceAction = useCallback( + (connectionId: string, ns: string, action: Actions) => { + switch (action) { + case 'open-connection-info': + onOpenConnectionInfo(connectionId); + return; + case 'copy-connection-string': + onCopyConnectionString(connectionId); + return; + case 'connection-toggle-favorite': + onToggleFavoriteConnection(connectionId); + return; + case 'select-database': + openCollectionsWorkspace(connectionId, ns); + return; + case 'select-collection': + openCollectionWorkspace(connectionId, ns); + return; + case 'open-in-new-tab': + openCollectionWorkspace(connectionId, ns, { newTab: true }); + return; + case 'modify-view': { + const coll = findCollection( + ns, + (connections.find((conn) => conn.connectionInfo.id === connectionId) + ?.databases as Database[]) ?? [] + ); + if (coll && coll.sourceName && coll.pipeline) { + openEditViewWorkspace(connectionId, coll._id, { + sourceName: coll.sourceName, + sourcePipeline: coll.pipeline, + newTab: true, + }); + } + return; + } + default: + _onNamespaceAction(connectionId, ns, action); + return; + } + }, + [ + connections, + openCollectionsWorkspace, + openCollectionWorkspace, + openEditViewWorkspace, + onCopyConnectionString, + onOpenConnectionInfo, + onToggleFavoriteConnection, + _onNamespaceAction, + ] + ); + + return ( +
+
+ + Active connections{' '} + + ({activeConnections.length}) + + +
+ + openDatabasesWorkspace(connectionId) + } + onConnectionExpand={onConnectionToggle} + onDatabaseExpand={onDatabaseExpand} + expanded={namedConnections.reduce( + (obj, { connectionInfo: { id: connectionId } }) => { + obj[connectionId] = collapsed.includes(connectionId) + ? false + : expanded[connectionId]; + return obj; + }, + {} as Record> + )} + {...navigationProps} + /> +
+ ); +} + +function mapStateToProps( + state: RootState, + { activeConnections }: { activeConnections: ConnectionInfo[] } +): { + isReady: boolean; + connections: Connection[]; + expanded: Record | false>; +} { + const connections: Connection[] = []; + const expandedResult: Record = {}; + + for (const connectionInfo of activeConnections) { + const connectionId = connectionInfo.id; + const instance = state.instance[connectionId]; + const { + filterRegex, + filteredDatabases, + expandedDbList: initialExpandedDbList, + } = state.databases[connectionId] || {}; + + const status = instance?.databasesStatus; + const isReady = + status !== undefined && !['initial', 'fetching'].includes(status); + const defaultExpanded = Boolean(filterRegex); + + const expandedDbList = initialExpandedDbList ?? {}; + const expanded = Object.fromEntries( + ((filteredDatabases as any[]) || []).map(({ name }) => [ + name, + expandedDbList[name] ?? defaultExpanded, + ]) + ); + + const isDataLake = instance?.dataLake?.isDataLake ?? false; + const isWritable = instance?.isWritable ?? false; + + connections.push({ + isReady, + isDataLake, + isWritable, + name: getConnectionTitle(connectionInfo), + connectionInfo, + databasesLength: filteredDatabases?.length ?? 0, + databasesStatus: status as Connection['databasesStatus'], + databases: filteredDatabases ?? [], + }); + + expandedResult[connectionId] = expanded; + } + + return { + isReady: true, + connections, + expanded: expandedResult, + }; +} + +const onNamespaceAction = ( + connectionId: string, + namespace: string, + action: Actions +): SidebarThunkAction => { + return (_dispatch, getState, { globalAppRegistry }) => { + const emit = (action: string, ...rest: any[]) => { + globalAppRegistry.emit(action, ...rest); + }; + const ns = toNS(namespace); + switch (action) { + case 'drop-database': + emit('open-drop-database', ns.database, { connectionId }); + return; + case 'rename-collection': + emit('open-rename-collection', ns, { connectionId }); + return; + case 'drop-collection': + emit('open-drop-collection', ns, { connectionId }); + return; + case 'create-collection': + emit('open-create-collection', ns, { + connectionId, + }); + return; + case 'duplicate-view': { + const coll = findCollection( + namespace, + getState().databases[connectionId].databases + ); + if (coll && coll.sourceName) { + emit( + 'open-create-view', + { + source: coll.sourceName, + pipeline: coll.pipeline, + duplicate: true, + }, + { + connectionId, + } + ); + } + return; + } + default: + // no-op + } + }; +}; + +export default connect(mapStateToProps, { + onDatabaseExpand: toggleDatabaseExpanded, + onNamespaceAction, +})(ActiveConnectionNavigation); diff --git a/packages/compass-sidebar/src/components/multiple-connections/active-connections/active-connection.spec.tsx b/packages/compass-sidebar/src/components/multiple-connections/active-connections/active-connection.spec.tsx deleted file mode 100644 index 442a141f524..00000000000 --- a/packages/compass-sidebar/src/components/multiple-connections/active-connections/active-connection.spec.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import React from 'react'; -import { expect } from 'chai'; -import { render, screen } from '@testing-library/react'; -import type { ConnectionInfo } from '@mongodb-js/connection-info'; -import { ActiveConnection } from './active-connection'; -import Sinon from 'sinon'; - -const mockConnection: ConnectionInfo & { title: string } = { - id: 'turtle', - connectionOptions: { - connectionString: 'mongodb://turtle', - }, - savedConnectionType: 'recent', - title: 'Elisabeth', -}; - -describe('', function () { - let toggleSpy: Sinon.SinonSpy; - - beforeEach(() => { - toggleSpy = Sinon.spy(); - }); - - afterEach(() => { - toggleSpy.resetHistory(); - }); - - it('Should render the title', function () { - render( - - ); - expect(screen.queryByText('Elisabeth')).to.be.visible; - }); - - it('Click on the title should call onToggle(true)', function () { - render( - - ); - const title = screen.queryByText('Elisabeth'); - title?.click(); - expect(toggleSpy).to.have.been.calledWith(true); - }); - - it('Click on the collapse button should call onToggle(false)', function () { - render( - - ); - const collapseBtn = screen.queryByLabelText('Collapse'); - expect(collapseBtn).to.be.visible; - collapseBtn?.click(); - expect(toggleSpy).to.have.been.calledWith(false); - }); -}); diff --git a/packages/compass-sidebar/src/components/multiple-connections/active-connections/active-connection.tsx b/packages/compass-sidebar/src/components/multiple-connections/active-connections/active-connection.tsx deleted file mode 100644 index 0b887d4ffae..00000000000 --- a/packages/compass-sidebar/src/components/multiple-connections/active-connections/active-connection.tsx +++ /dev/null @@ -1,144 +0,0 @@ -import { - Icon, - IconButton, - css, - palette, - spacing, - useHoverState, -} from '@mongodb-js/compass-components'; -import type { ConnectionInfo } from '@mongodb-js/connection-info'; -import React, { useCallback } from 'react'; -import ServerIcon from '../icons/server-icon'; - -const iconStyles = css({ - flex: 'none', -}); - -const toggleBtnStyles = css({ - color: palette.gray.dark2, - backgroundColor: 'none', - boxShadow: 'none', - '&:hover': { - backgroundColor: 'none', - boxShadow: 'none', - }, -}); - -const activeConnectionNameStyles = css({ - overflow: 'hidden', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', -}); - -const activeConnectionTitleStyles = css({ - display: 'flex', - flexDirection: 'row', - gap: spacing[2], - alignItems: 'center', - cursor: 'pointer', - marginTop: 'auto', - padding: spacing[1], - borderRadius: spacing[1], - - '&:hover': { - backgroundColor: 'var(--item-bg-color-hover)', - }, -}); - -const activeConnectionStyles = css({ - gap: spacing[2], - alignItems: 'center', - marginTop: 'auto', -}); - -const databasesStyles = css({ - height: `calc(100% - ${spacing[4]}px)`, - display: 'flex', -}); - -export function ActiveConnection({ - connection, - isExpanded, - onToggle, -}: { - isExpanded: boolean; - connection: ConnectionInfo & { title: string }; - onToggle: (isExpanded: boolean) => void; -}): React.ReactElement { - const [hoverProps, isHovered] = useHoverState(); - - const isLocalhost = connection.connectionOptions.connectionString.startsWith( - 'mongodb://localhost' - ); - const isFavorite = connection.savedConnectionType === 'favorite'; - - const onExpand = useCallback(() => { - if (!isExpanded) onToggle(true); - }, [isExpanded, onToggle]); - - const onCollapse = useCallback(() => { - if (isExpanded) onToggle(false); - }, [isExpanded, onToggle]); - - const onKeyDown = useCallback( - (e: React.KeyboardEvent) => { - if (e.target === e.currentTarget && [' ', 'Enter'].includes(e.key)) - onToggle(!isExpanded); - }, - [isExpanded, onToggle] - ); - - const connectionIcon = isLocalhost ? ( - - ) : isFavorite ? ( - - ) : ( - - ); - return ( -
  • -
    - {isExpanded ? ( - - - - ) : ( - - - - )} - {connectionIcon}{' '} -
    {connection.title}
    -
    - - - -
    -
    - {isExpanded && ( -
    Databases placeholder
    - )} -
  • - ); -} diff --git a/packages/compass-sidebar/src/components/multiple-connections/header/sidebar-header.tsx b/packages/compass-sidebar/src/components/multiple-connections/header/sidebar-header.tsx index 1c770a6e529..bd1e4d01cf8 100644 --- a/packages/compass-sidebar/src/components/multiple-connections/header/sidebar-header.tsx +++ b/packages/compass-sidebar/src/components/multiple-connections/header/sidebar-header.tsx @@ -1,15 +1,42 @@ import React from 'react'; -import { Subtitle, spacing, css } from '@mongodb-js/compass-components'; +import { + Subtitle, + spacing, + css, + type ItemAction, + ItemActionControls, +} from '@mongodb-js/compass-components'; const sidebarHeaderStyles = css({ height: spacing[6], padding: spacing[3], + display: 'flex', }); -export function SidebarHeader(): React.ReactElement { +type Action = 'open-compass-settings'; + +const actions: ItemAction[] = [ + { + action: 'open-compass-settings', + label: 'Compass Settings', + icon: 'Settings', + }, +]; + +export function SidebarHeader({ + onAction, +}: { + onAction(actionName: Action): void; +}): React.ReactElement { return (
    Compass + + onAction={onAction} + iconSize="small" + actions={actions} + data-testid="connections-sidebar-title-actions" + >
    ); } diff --git a/packages/compass-sidebar/src/components/multiple-connections/navigation/navigation.tsx b/packages/compass-sidebar/src/components/multiple-connections/navigation/navigation.tsx new file mode 100644 index 00000000000..07641423d31 --- /dev/null +++ b/packages/compass-sidebar/src/components/multiple-connections/navigation/navigation.tsx @@ -0,0 +1,125 @@ +import { + useHoverState, + cx, + spacing, + css, + mergeProps, + useDefaultAction, + Icon, +} from '@mongodb-js/compass-components'; +import { + useOpenWorkspace, + useWorkspacePlugins, +} from '@mongodb-js/compass-workspaces/provider'; +import React from 'react'; + +const navigationContainer = css({ + padding: `0px 0px ${spacing[200]}px`, +}); + +const navigationItem = css({ + cursor: 'pointer', + color: 'var(--item-color)', + position: 'relative', + paddingLeft: spacing[400], + + '&:hover .item-background': { + display: 'block', + backgroundColor: 'var(--item-bg-color-hover)', + }, + + '&:hover': { + backgroundColor: 'var(--item-bg-color-hover)', + }, + + svg: { + flexShrink: 0, + }, +}); + +const activeNavigationItem = css({ + color: 'var(--item-color-active)', + fontWeight: 'bold', + backgroundColor: 'var(--item-bg-color-active)', +}); + +const itemWrapper = css({ + position: 'relative', + display: 'flex', + alignItems: 'center', + height: spacing[800], + gap: spacing[200], + zIndex: 1, +}); + +const itemButtonWrapper = css({ + display: 'flex', + alignItems: 'center', + minWidth: 0, +}); + +const navigationItemLabel = css({ + overflow: 'hidden', + whiteSpace: 'nowrap', + textOverflow: 'ellipsis', + marginLeft: spacing[200], +}); + +export function NavigationItem({ + onClick: onButtonClick, + glyph, + label, + isActive, +}: { + onClick(): void; + glyph: string; + label: string; + isActive: boolean; +}) { + const [hoverProps] = useHoverState(); + const defaultActionProps = useDefaultAction(onButtonClick); + + const navigationItemProps = mergeProps( + { + className: cx(navigationItem, isActive && activeNavigationItem), + role: 'button', + ['aria-label']: label, + ['aria-current']: isActive, + tabIndex: 0, + }, + hoverProps, + defaultActionProps + ) as React.HTMLProps; + + return ( +
    +
    +
    + + {label} +
    +
    +
    + ); +} + +export function Navigation({ + currentLocation, +}: { + currentLocation: string | null; +}): React.ReactElement { + const { hasWorkspacePlugin } = useWorkspacePlugins(); + const { openMyQueriesWorkspace } = useOpenWorkspace(); + return ( +
    + {hasWorkspacePlugin('My Queries') && ( + + )} +
    + ); +} diff --git a/packages/compass-sidebar/src/components/multiple-connections/saved-connections/saved-connection-list.spec.tsx b/packages/compass-sidebar/src/components/multiple-connections/saved-connections/saved-connection-list.spec.tsx index 636290941e7..3d6ccfe5e84 100644 --- a/packages/compass-sidebar/src/components/multiple-connections/saved-connections/saved-connection-list.spec.tsx +++ b/packages/compass-sidebar/src/components/multiple-connections/saved-connections/saved-connection-list.spec.tsx @@ -4,9 +4,11 @@ import { spy, stub } from 'sinon'; import { render, screen, cleanup, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { SavedConnectionList } from './saved-connection-list'; -import type { ConnectionInfo } from '@mongodb-js/connection-info'; -import { ConnectionStorageContext } from '@mongodb-js/connection-storage/provider'; -import { ConnectionStorageBus } from '@mongodb-js/connection-storage/renderer'; +import { + InMemoryConnectionStorage, + ConnectionStorageProvider, + type ConnectionInfo, +} from '@mongodb-js/connection-storage/provider'; import { ConnectionsManagerProvider, @@ -51,15 +53,10 @@ describe('SavedConnectionList Component', function () { favoriteInfo: ConnectionInfo[], nonFavoriteInfo: ConnectionInfo[] ) { - const connectionStorage = { - events: new ConnectionStorageBus(), - loadAll() { - return Promise.resolve([ - FAVOURITE_CONNECTION_INFO, - NON_FAVOURITE_CONNECTION_INFO, - ]); - }, - } as any; + const connectionStorage = new InMemoryConnectionStorage([ + FAVOURITE_CONNECTION_INFO, + NON_FAVOURITE_CONNECTION_INFO, + ]); const connectionManager = new ConnectionsManager({ logger: {} as any, @@ -67,7 +64,7 @@ describe('SavedConnectionList Component', function () { }); return render( - + - + ); } diff --git a/packages/compass-sidebar/src/components/multiple-connections/saved-connections/saved-connection.tsx b/packages/compass-sidebar/src/components/multiple-connections/saved-connections/saved-connection.tsx index aff388c3338..f96ad785436 100644 --- a/packages/compass-sidebar/src/components/multiple-connections/saved-connections/saved-connection.tsx +++ b/packages/compass-sidebar/src/components/multiple-connections/saved-connections/saved-connection.tsx @@ -15,7 +15,6 @@ import { ItemActionControls, useHoverState, useToast, - useDarkMode, palette, Tooltip, } from '@mongodb-js/compass-components'; @@ -24,6 +23,7 @@ import type { ItemAction } from '@mongodb-js/compass-components'; import { useConnectionColor } from '@mongodb-js/connection-form'; import { useMaybeProtectConnectionString } from '@mongodb-js/compass-maybe-protect-connection-string'; import type { ItemSeparator } from '@mongodb-js/compass-components/lib/components/item-action-controls'; +import { ServerIcon } from '@mongodb-js/compass-components'; const TOAST_TIMEOUT_MS = 5000; // 5 seconds. const iconStyles = css({ @@ -74,58 +74,6 @@ const WarningIcon = () => { ); }; -const ServerIcon = () => { - const darkMode = useDarkMode(); - const stroke = darkMode ? palette.white : palette.gray.dark2; - - return ( - - - - - - - - - - - - - - ); -}; - type SavedConnectionProps = { canOpenNewConnection: boolean; canNotOpenReason?: CanNotOpenConnectionReason; @@ -155,7 +103,7 @@ export function SavedConnection({ const isLocalhost = connectionInfo.connectionOptions.connectionString.startsWith( 'mongodb://localhost' - ); + ); // TODO(COMPASS-7832) const maybeProtectConnectionString = useMaybeProtectConnectionString(); const [hoverProps, isHovered] = useHoverState(); @@ -275,6 +223,7 @@ export function SavedConnection({ }} className={savedConnectionStyles} data-testid={`saved-connection-${connectionInfo.id}`} + onDoubleClick={() => onConnect(connectionInfo)} > {icon}{' '}
    diff --git a/packages/compass-sidebar/src/components/multiple-connections/sidebar.spec.tsx b/packages/compass-sidebar/src/components/multiple-connections/sidebar.spec.tsx index d3415a8b695..f0a139c66b3 100644 --- a/packages/compass-sidebar/src/components/multiple-connections/sidebar.spec.tsx +++ b/packages/compass-sidebar/src/components/multiple-connections/sidebar.spec.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { expect } from 'chai'; -import { stub } from 'sinon'; +import { stub, spy, type SinonStub } from 'sinon'; import { render, screen, @@ -9,19 +9,32 @@ import { within, } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { MultipleConnectionSidebar } from './sidebar'; +import MultipleConnectionSidebar from './sidebar'; import type { ConnectionInfo } from '@mongodb-js/connection-info'; import { ToastArea } from '@mongodb-js/compass-components'; import { - ConnectionStorageContext, - type ConnectionStorage, + InMemoryConnectionStorage, + ConnectionStorageProvider, } from '@mongodb-js/connection-storage/provider'; -import { ConnectionStorageBus } from '@mongodb-js/connection-storage/renderer'; import type { DataService } from 'mongodb-data-service'; import { ConnectionsManagerProvider, ConnectionsManager, } from '@mongodb-js/compass-connections/provider'; +import { createSidebarStore } from '../../stores'; +import { Provider } from 'react-redux'; +import AppRegistry from 'hadron-app-registry'; +import { + type PreferencesAccess, + createSandboxFromDefaultPreferences, +} from 'compass-preferences-model'; +import { PreferencesProvider } from 'compass-preferences-model/provider'; +import { + type WorkspacesService, + WorkspacesServiceProvider, +} from '@mongodb-js/compass-workspaces/provider'; +import type { WorkspaceTab } from '@mongodb-js/compass-workspaces'; +import { WorkspacesProvider } from '@mongodb-js/compass-workspaces'; type PromiseFunction = ( resolve: (dataService: DataService) => void, @@ -54,58 +67,85 @@ const savedConnection: ConnectionInfo = { savedConnectionType: 'favorite', }; -type ItselfAndStub = { - // eslint-disable-next-line @typescript-eslint/ban-types - [K in keyof T]: T[K] extends Function ? ReturnType : T[K]; -}; - describe('Multiple Connections Sidebar Component', function () { - const connectionStorage: ItselfAndStub< - Pick< - typeof ConnectionStorage, - 'events' | 'loadAll' | 'load' | 'save' | 'delete' - > - > = { - events: new ConnectionStorageBus(), - loadAll: stub(), - load: stub(), - save: stub(), - delete: stub(), - }; + let preferences: PreferencesAccess; + + const connectionStorage = new InMemoryConnectionStorage([savedConnection]); + const globalAppRegistry = new AppRegistry(); + const emitSpy = spy(globalAppRegistry, 'emit'); + let store: ReturnType['store']; + let deactivate: () => void; + let openMyQueriesWorkspaceStub: SinonStub; const connectFn = stub(); function doRender() { - const storage = connectionStorage as any; const connectionManager = new ConnectionsManager({ logger: { debug: stub() } as any, __TEST_CONNECT_FN: connectFn, }); + ({ store, deactivate } = createSidebarStore( + { + globalAppRegistry, + instancesManager: { + listMongoDBInstances() { + return []; + }, + }, + logger: { log: { warn() {} }, mongoLogId() {} }, + } as any, + { on() {}, cleanup() {}, addCleanup() {} } as any + )); + openMyQueriesWorkspaceStub = stub(); return render( - - - - - + + + null }]} + > + + + + + + + + + + ); } - beforeEach(function () { - connectionStorage.loadAll.returns([savedConnection]); + beforeEach(async function () { + preferences = await createSandboxFromDefaultPreferences(); + await preferences.savePreferences({ + enableNewMultipleConnectionSystem: true, + }); doRender(); }); afterEach(function () { - connectionStorage.loadAll.reset(); - connectionStorage.load.reset(); - connectionStorage.save.reset(); - connectionStorage.delete.reset(); - + deactivate(); cleanup(); + emitSpy.resetHistory(); + openMyQueriesWorkspaceStub.resetHistory(); }); describe('opening a new connection', function () { @@ -135,7 +175,6 @@ describe('Multiple Connections Sidebar Component', function () { describe('when failing to connect', function () { it('calls the connection function and renders the error toast', async function () { - connectionStorage.loadAll.returns([savedConnection]); connectFn.returns(slowConnection(andFail('Expected failure'))); parentSavedConnection = screen.getByTestId('saved-connection-12345'); @@ -156,4 +195,24 @@ describe('Multiple Connections Sidebar Component', function () { }); }); }); + + describe('actions', () => { + it('when clicking on the Settings btn, it emits open-compass-settings', () => { + const settingsBtn = screen.getByTitle('Compass Settings'); + expect(settingsBtn).to.be.visible; + + userEvent.click(settingsBtn); + + expect(emitSpy).to.have.been.calledWith('open-compass-settings'); + }); + + it('when clicking on "My Queries", it opens the workspace', () => { + const navItem = screen.getByText('My Queries'); + expect(navItem).to.be.visible; + + userEvent.click(navItem); + + expect(openMyQueriesWorkspaceStub).to.have.been.called; + }); + }); }); diff --git a/packages/compass-sidebar/src/components/multiple-connections/sidebar.tsx b/packages/compass-sidebar/src/components/multiple-connections/sidebar.tsx index 5825849db41..4a155c4c027 100644 --- a/packages/compass-sidebar/src/components/multiple-connections/sidebar.tsx +++ b/packages/compass-sidebar/src/components/multiple-connections/sidebar.tsx @@ -1,22 +1,39 @@ import React, { useCallback, useMemo, useRef, useState } from 'react'; -import { useConnections } from '@mongodb-js/compass-connections/provider'; +import { connect } from 'react-redux'; +import { + useActiveConnections, + useConnections, +} from '@mongodb-js/compass-connections/provider'; import { type ConnectionInfo, getConnectionTitle, } from '@mongodb-js/connection-info'; import { SavedConnectionList } from './saved-connections/saved-connection-list'; -import { ActiveConnectionList } from './active-connections/active-connection-list'; import { ResizableSidebar, css, Link, useToast, spacing, + openToast, } from '@mongodb-js/compass-components'; import { SidebarHeader } from './header/sidebar-header'; import { ConnectionFormModal } from '@mongodb-js/connection-form'; import { cloneDeep } from 'lodash'; import { usePreference } from 'compass-preferences-model/provider'; +import ActiveConnectionNavigation from './active-connections/active-connection-navigation'; +import type { SidebarThunkAction } from '../../modules'; +import { Navigation } from './navigation/navigation'; +import ConnectionInfoModal from '../connection-info-modal'; +import { useMaybeProtectConnectionString } from '@mongodb-js/compass-maybe-protect-connection-string'; +import type { WorkspaceTab } from '@mongodb-js/compass-workspaces'; + +const TOAST_TIMEOUT_MS = 5000; // 5 seconds. + +type MultipleConnectionSidebarProps = { + activeWorkspace: WorkspaceTab | null; + onSidebarAction(action: string, ...rest: any[]): void; +}; const sidebarStyles = css({ // Sidebar internally has z-indexes higher than zero. We set zero on the @@ -67,11 +84,55 @@ function ConnectionErrorToastBody({ ); } -export function MultipleConnectionSidebar() { +function activeConnectionNotFoundError( + description = 'Connection not found. Please try again' +) { + openToast('active-connection-not-found', { + title: 'Error', + description, + variant: 'warning', + timeout: TOAST_TIMEOUT_MS, + }); +} + +async function copyConnectionString(connectionString: string) { + try { + await navigator.clipboard.writeText(connectionString); + openToast('copy-to-clipboard', { + title: 'Success', + description: 'Copied to clipboard.', + variant: 'success', + timeout: TOAST_TIMEOUT_MS, + }); + } catch { + openToast('copy-to-clipboard', { + title: 'Error', + description: + 'An error occurred when copying to clipboard. Please try again.', + variant: 'warning', + timeout: TOAST_TIMEOUT_MS, + }); + } +} + +export function MultipleConnectionSidebar({ + activeWorkspace, + onSidebarAction, +}: MultipleConnectionSidebarProps) { const { openToast, closeToast } = useToast('multiple-connection-status'); const cancelCurrentConnectionRef = useRef<(id: string) => Promise>(); + const activeConnections = useActiveConnections(); + const maybeProtectConnectionString = useMaybeProtectConnectionString(); const [isConnectionFormOpen, setIsConnectionFormOpen] = useState(false); + const [connectionInfoModalConnectionId, setConnectionInfoModalConnectionId] = + useState(); + + const findActiveConnection = useCallback( + (connectionId: string) => + activeConnections.find(({ id }) => id === connectionId), + [activeConnections] + ); const onConnected = useCallback( (info: ConnectionInfo) => { @@ -207,7 +268,7 @@ export function MultipleConnectionSidebar() { [duplicateConnection, setIsConnectionFormOpen] ); - const onToggleFavoriteConnection = useCallback( + const onToggleFavoriteConnectionInfo = useCallback( (info: ConnectionInfo) => { info.savedConnectionType = info.savedConnectionType === 'favorite' ? 'recent' : 'favorite'; @@ -217,6 +278,48 @@ export function MultipleConnectionSidebar() { [saveConnection] ); + const onToggleFavoriteActiveConnection = useCallback( + (connectionId: ConnectionInfo['id']) => { + const connectionInfo = findActiveConnection(connectionId); + if (!connectionInfo) { + activeConnectionNotFoundError( + 'Favorite/Unfavorite action failed - Connection not found. Please try again.' + ); + return; + } + onToggleFavoriteConnectionInfo(connectionInfo); + }, + [onToggleFavoriteConnectionInfo, findActiveConnection] + ); + + const onOpenConnectionInfo = useCallback( + (connectionId: string) => setConnectionInfoModalConnectionId(connectionId), + [] + ); + + const onCloseConnectionInfo = useCallback( + () => setConnectionInfoModalConnectionId(undefined), + [] + ); + + const onCopyActiveConnectionString = useCallback( + (connectionId: string) => { + const connectionInfo = findActiveConnection(connectionId); + if (!connectionInfo) { + activeConnectionNotFoundError( + 'Copying to clipboard failed - Connection not found. Please try again.' + ); + return; + } + void copyConnectionString( + maybeProtectConnectionString( + connectionInfo?.connectionOptions.connectionString + ) + ); + }, + [findActiveConnection, maybeProtectConnectionString] + ); + const protectConnectionStrings = usePreference('protectConnectionStrings'); const forceConnectionOptions = usePreference('forceConnectionOptions'); const showKerberosPasswordField = usePreference('showKerberosPasswordField'); @@ -251,10 +354,17 @@ export function MultipleConnectionSidebar() { ); return ( - + ); } + +const onSidebarAction = ( + action: string, + ...rest: any[] +): SidebarThunkAction => { + return (_dispatch, _getState, { globalAppRegistry }) => { + globalAppRegistry.emit(action, ...rest); + }; +}; + +const MappedMultipleConnectionSidebar = connect(undefined, { + onSidebarAction, +})(MultipleConnectionSidebar); + +export default MappedMultipleConnectionSidebar; diff --git a/packages/compass-sidebar/src/components/sidebar-databases-navigation.tsx b/packages/compass-sidebar/src/components/sidebar-databases-navigation.tsx deleted file mode 100644 index 22b8b033b5f..00000000000 --- a/packages/compass-sidebar/src/components/sidebar-databases-navigation.tsx +++ /dev/null @@ -1,158 +0,0 @@ -import React, { useCallback } from 'react'; -import { connect } from 'react-redux'; -import DatabasesNavigationTree from '@mongodb-js/compass-databases-navigation'; -import type { Actions } from '@mongodb-js/compass-databases-navigation'; -import toNS from 'mongodb-ns'; -import type { Database } from '../modules/databases'; -import { toggleDatabaseExpanded } from '../modules/databases'; -import { usePreference } from 'compass-preferences-model/provider'; -import type { RootState, SidebarThunkAction } from '../modules'; -import { useOpenWorkspace } from '@mongodb-js/compass-workspaces/provider'; - -function findCollection(ns: string, databases: Database[]) { - const { database, collection } = toNS(ns); - - return ( - databases - .find((db) => db._id === database) - ?.collections.find((coll) => coll.name === collection) ?? null - ); -} - -function SidebarDatabasesNavigation({ - isDataLake, - isWritable, - onNamespaceAction: _onNamespaceAction, - databases, - ...dbNavigationProps -}: Omit< - React.ComponentProps, - 'isReadOnly' | 'databases' -> & { - databases: Database[]; - isDataLake?: boolean; - isWritable?: boolean; -}) { - const { - openCollectionsWorkspace, - openCollectionWorkspace, - openEditViewWorkspace, - } = useOpenWorkspace(); - const preferencesReadOnly = usePreference('readOnly'); - const isReadOnly = preferencesReadOnly || isDataLake || !isWritable; - const onNamespaceAction = useCallback( - (ns: string, action: Actions) => { - switch (action) { - case 'select-database': - openCollectionsWorkspace(ns); - return; - case 'select-collection': - openCollectionWorkspace(ns); - return; - case 'open-in-new-tab': - openCollectionWorkspace(ns, { newTab: true }); - return; - case 'modify-view': { - const coll = findCollection(ns, databases); - if (coll && coll.sourceName && coll.pipeline) { - openEditViewWorkspace(coll._id, { - sourceName: coll.sourceName, - sourcePipeline: coll.pipeline, - newTab: true, - }); - } - return; - } - default: - _onNamespaceAction(ns, action); - return; - } - }, - [ - databases, - openCollectionsWorkspace, - openCollectionWorkspace, - openEditViewWorkspace, - _onNamespaceAction, - ] - ); - - return ( - - ); -} - -function mapStateToProps(state: RootState) { - const { - databases: { filterRegex, filteredDatabases, expandedDbList }, - instance, - } = state; - const status = instance?.databasesStatus; - const isReady = - status !== undefined && !['initial', 'fetching'].includes(status); - const defaultExpanded = Boolean(filterRegex); - const expanded = Object.fromEntries( - (filteredDatabases as any[]).map(({ name }) => [ - name, - expandedDbList[name] ?? defaultExpanded, - ]) - ); - const isDataLake = instance?.dataLake.isDataLake; - const isWritable = instance?.isWritable; - return { - isReady, - isDataLake, - isWritable, - databases: filteredDatabases, - expanded, - }; -} - -const onNamespaceAction = ( - namespace: string, - action: Actions -): SidebarThunkAction => { - return (_dispatch, getState, { globalAppRegistry }) => { - const emit = (action: string, ...rest: any[]) => { - globalAppRegistry.emit(action, ...rest); - }; - const ns = toNS(namespace); - switch (action) { - case 'drop-database': - emit('open-drop-database', ns.database); - return; - case 'rename-collection': - emit('open-rename-collection', ns); - return; - case 'drop-collection': - emit('open-drop-collection', ns); - return; - case 'create-collection': - emit('open-create-collection', ns); - return; - case 'duplicate-view': { - const coll = findCollection(namespace, getState().databases.databases); - if (coll && coll.sourceName) { - emit('open-create-view', { - source: coll.sourceName, - pipeline: coll.pipeline, - duplicate: true, - }); - } - return; - } - default: - // no-op - } - }; -}; - -export default connect(mapStateToProps, { - onDatabaseExpand: toggleDatabaseExpanded, - onNamespaceAction, -})(SidebarDatabasesNavigation); diff --git a/packages/compass-sidebar/src/index.ts b/packages/compass-sidebar/src/index.ts index fa163ff4f3f..76107c7a22a 100644 --- a/packages/compass-sidebar/src/index.ts +++ b/packages/compass-sidebar/src/index.ts @@ -3,20 +3,22 @@ import { registerHadronPlugin, type AppRegistry } from 'hadron-app-registry'; import type { SidebarPluginProps } from './plugin'; import SidebarPlugin from './plugin'; import { createSidebarStore } from './stores'; -import { mongoDBInstanceLocator } from '@mongodb-js/compass-app-stores/provider'; import { - dataServiceLocator, - type DataService, + type MongoDBInstancesManager, + mongoDBInstancesManagerLocator, +} from '@mongodb-js/compass-app-stores/provider'; +import { + type ConnectionsManager, + connectionsManagerLocator, } from '@mongodb-js/compass-connections/provider'; -import type { MongoDBInstance } from 'mongodb-instance-model'; import type { LoggerAndTelemetry } from '@mongodb-js/compass-logging/provider'; import { createLoggerAndTelemetryLocator } from '@mongodb-js/compass-logging/provider'; export const CompassSidebarPlugin = registerHadronPlugin< SidebarPluginProps, { - instance: () => MongoDBInstance; - dataService: () => DataService; + connectionsManager: () => ConnectionsManager; + instancesManager: () => MongoDBInstancesManager; logger: () => LoggerAndTelemetry; } >( @@ -24,16 +26,17 @@ export const CompassSidebarPlugin = registerHadronPlugin< name: 'CompassSidebar', component: SidebarPlugin, activate( - { initialConnectionInfo }: SidebarPluginProps, + // @eslint-ignore-next-line + _, { globalAppRegistry, - instance, - dataService, + connectionsManager, + instancesManager, logger, }: { globalAppRegistry: AppRegistry; - instance: MongoDBInstance; - dataService: DataService; + connectionsManager: ConnectionsManager; + instancesManager: MongoDBInstancesManager; logger: LoggerAndTelemetry; }, helpers: ActivateHelpers @@ -41,9 +44,8 @@ export const CompassSidebarPlugin = registerHadronPlugin< const { store, deactivate } = createSidebarStore( { globalAppRegistry, - instance, - dataService, - connectionInfo: initialConnectionInfo, + connectionsManager, + instancesManager, logger, }, helpers @@ -55,8 +57,8 @@ export const CompassSidebarPlugin = registerHadronPlugin< }, }, { - instance: mongoDBInstanceLocator, - dataService: dataServiceLocator, + connectionsManager: connectionsManagerLocator, + instancesManager: mongoDBInstancesManagerLocator, logger: createLoggerAndTelemetryLocator('COMPASS-SIDEBAR-UI'), } ); diff --git a/packages/compass-sidebar/src/modules/connection-info.spec.ts b/packages/compass-sidebar/src/modules/connection-info.spec.ts deleted file mode 100644 index 7cec7a65946..00000000000 --- a/packages/compass-sidebar/src/modules/connection-info.spec.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { expect } from 'chai'; -import sinon from 'sinon'; - -import reducer, { - INITIAL_STATE, - changeConnectionInfo, - updateAndSaveConnectionInfo, -} from './connection-info'; - -describe('connection info module', function () { - const connectionInfoNotFavorite = { - connectionOptions: { - connectionString: 'mongodb://outerspace:27000', - }, - id: '123', - }; - - describe('reducer', function () { - context('when the action is changeConnectionInfo', function () { - it('returns the new state', function () { - expect( - reducer(undefined, changeConnectionInfo(connectionInfoNotFavorite)) - .connectionInfo - ).to.deep.equal(connectionInfoNotFavorite); - }); - - it('does not call the connection storage to save', function () { - const saveSpy = sinon.spy(); - reducer( - { - connectionStorage: { - save: saveSpy, - }, - } as any, - changeConnectionInfo(connectionInfoNotFavorite) - ); - - expect(saveSpy.callCount).to.equal(0); - }); - }); - - context('when the action is updateAndSaveConnectionInfo', function () { - it('returns the new state', function () { - const newConnection = updateAndSaveConnectionInfo({ - ...connectionInfoNotFavorite, - favorite: { - name: 'My Favorite', - color: '#d4366e', - }, - }); - const state = reducer( - { - connectionStorage: { - save: function () {}, - }, - } as any, - newConnection - ); - - expect(state.connectionInfo.favorite?.name).to.equal('My Favorite'); - expect(state.connectionInfo.favorite?.color).to.equal('#d4366e'); - }); - - it('calls to save the connection info in the connection storage', function () { - const newConnection = updateAndSaveConnectionInfo({ - ...connectionInfoNotFavorite, - favorite: { - name: 'My Favorite', - color: '#d4366e', - }, - }); - const saveSpy = sinon.spy(); - reducer( - { - connectionStorage: { - save: saveSpy, - }, - } as any, - newConnection - ); - - expect(saveSpy.callCount).to.equal(1); - expect(saveSpy.firstCall.args[0]).to.deep.equal({ - connectionInfo: { - connectionOptions: { - connectionString: 'mongodb://outerspace:27000', - }, - id: '123', - favorite: { - name: 'My Favorite', - color: '#d4366e', - }, - }, - }); - }); - }); - - context('when an action is not provided', function () { - it('returns the default state', function () { - expect(reducer(undefined, {} as any)).to.equal(INITIAL_STATE); - }); - }); - }); -}); diff --git a/packages/compass-sidebar/src/modules/connection-info.ts b/packages/compass-sidebar/src/modules/connection-info.ts deleted file mode 100644 index 51e5517b3a3..00000000000 --- a/packages/compass-sidebar/src/modules/connection-info.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { ConnectionStorage } from '@mongodb-js/connection-storage/renderer'; -import type { ConnectionInfo } from '@mongodb-js/connection-info'; -import type { RootAction } from '.'; - -/** - * Change connection action name. - */ -const CHANGE_CONNECTION_INFO = - 'sidebar/connection/CHANGE_CONNECTION_INFO' as const; - -interface ChangeConnectionInfoAction { - type: typeof CHANGE_CONNECTION_INFO; - connectionInfo: ConnectionInfo; -} - -/** - * Save favorite connection action name. - */ -const SAVE_CONNECTION_INFO = 'sidebar/connection/SAVE_CONNECTION_INFO' as const; - -interface SaveConnectionInfoAction { - type: typeof SAVE_CONNECTION_INFO; - connectionInfo: ConnectionInfo; -} - -/** - * The initial state of the connection. - */ -export const INITIAL_STATE: ConnectionInfoState = { - connectionInfo: { - connectionOptions: { - connectionString: 'mongodb://localhost:27017', - }, - }, - connectionStorage: ConnectionStorage, -}; - -export interface ConnectionInfoState { - connectionInfo: Omit & Partial; - connectionStorage: typeof ConnectionStorage; -} - -export type ConnectionInfoAction = - | ChangeConnectionInfoAction - | SaveConnectionInfoAction; - -async function saveConnectionInfo( - connectionInfo: ConnectionInfo, - connectionStorage: typeof ConnectionStorage -) { - try { - await connectionStorage.save({ connectionInfo }); - } catch { - // Currently we silently fail if saving the favorite fails. - } -} - -/** - * Changes the connection. - * - * @param {Object} state - The state. - * @param {Object} action - The action. - * - * @returns {Object} The new state. - */ -const doChangeConnectionInfo = ( - state: ConnectionInfoState, - action: ChangeConnectionInfoAction -) => { - return { ...state, connectionInfo: action.connectionInfo }; -}; - -/** - * Saves the new favorite connection info. - * - * @param {Object} state - The state. - * @param {Object} action - The action. - * - * @returns {Object} The new state. - */ -const doSaveConnectionInfo = ( - state: ConnectionInfoState, - action: SaveConnectionInfoAction -) => { - void saveConnectionInfo(action.connectionInfo, state.connectionStorage); - - return { ...state, connectionInfo: action.connectionInfo }; -}; - -/** - * To not have a huge switch statement in the reducer. - */ -const MAPPINGS: { - [Type in ConnectionInfoAction['type']]: ( - state: ConnectionInfoState, - action: ConnectionInfoAction & { type: Type } - ) => ConnectionInfoState; -} = { - [CHANGE_CONNECTION_INFO]: doChangeConnectionInfo, - [SAVE_CONNECTION_INFO]: doSaveConnectionInfo, -}; - -/** - * Reducer function for handle state changes. - * - * @param {String} state - The status state. - * @param {Object} action - The action. - * - */ -export default function reducer( - state: ConnectionInfoState = INITIAL_STATE, - action: RootAction -) { - const fn = MAPPINGS[action.type as ConnectionInfoAction['type']]; - - return fn ? fn(state, action as any) : state; -} - -/** - * Change connection action creator. - * - * @param {Object} connectionInfo - The connection info object. - * - * @returns {Object} The action. - */ -export const changeConnectionInfo = ( - connectionInfo: ConnectionInfo -): ChangeConnectionInfoAction => ({ - type: CHANGE_CONNECTION_INFO, - connectionInfo, -}); - -/** - * Save connection info action creator. - * - * @param {Object} connectionInfo - The connection info object. - * - * @returns {Object} The action. - */ -export const updateAndSaveConnectionInfo = ( - connectionInfo: ConnectionInfo -): SaveConnectionInfoAction => ({ - type: SAVE_CONNECTION_INFO, - connectionInfo, -}); diff --git a/packages/compass-sidebar/src/modules/connection-options.spec.ts b/packages/compass-sidebar/src/modules/connection-options.spec.ts index 0ea8f372a54..ca226550567 100644 --- a/packages/compass-sidebar/src/modules/connection-options.spec.ts +++ b/packages/compass-sidebar/src/modules/connection-options.spec.ts @@ -5,6 +5,8 @@ import reducer, { changeConnectionOptions, } from './connection-options'; +const CONNECTION_ID = 'webscale'; + describe('connection options module', function () { it('correctly sets the initial state', function () { expect(reducer(undefined, {} as any)).to.deep.equal(INITIAL_STATE); @@ -14,7 +16,7 @@ describe('connection options module', function () { expect( reducer( undefined, - changeConnectionOptions({ + changeConnectionOptions(CONNECTION_ID, { sshTunnel: { host: 'foo', port: '1234', @@ -22,10 +24,12 @@ describe('connection options module', function () { }) ) ).to.deep.equal({ - sshTunnel: true, - sshTunnelHostPortString: 'foo:1234', - sshTunnelHostname: 'foo', - sshTunnelPort: '1234', + [CONNECTION_ID]: { + sshTunnel: true, + sshTunnelHostPortString: 'foo:1234', + sshTunnelHostname: 'foo', + sshTunnelPort: '1234', + }, }); }); @@ -33,7 +37,7 @@ describe('connection options module', function () { expect( reducer( undefined, - changeConnectionOptions({ + changeConnectionOptions(CONNECTION_ID, { sshTunnel: { host: 'abcdefghijklmnopqrstuvwxyz', port: '2345', @@ -41,19 +45,25 @@ describe('connection options module', function () { }) ) ).to.deep.equal({ - sshTunnel: true, - sshTunnelHostPortString: 'abcdefghi...rstuvwxyz:2345', - sshTunnelHostname: 'abcdefghijklmnopqrstuvwxyz', - sshTunnelPort: '2345', + [CONNECTION_ID]: { + sshTunnel: true, + sshTunnelHostPortString: 'abcdefghi...rstuvwxyz:2345', + sshTunnelHostname: 'abcdefghijklmnopqrstuvwxyz', + sshTunnelPort: '2345', + }, }); }); it('sets sshTunnelHostPortString to a blank string if sshTunnel is not set', function () { - expect(reducer(undefined, changeConnectionOptions({}))).to.deep.equal({ - sshTunnel: false, - sshTunnelHostPortString: '', - sshTunnelHostname: '', - sshTunnelPort: '', + expect( + reducer(undefined, changeConnectionOptions(CONNECTION_ID, {})) + ).to.deep.equal({ + [CONNECTION_ID]: { + sshTunnel: false, + sshTunnelHostPortString: '', + sshTunnelHostname: '', + sshTunnelPort: '', + }, }); }); }); diff --git a/packages/compass-sidebar/src/modules/connection-options.ts b/packages/compass-sidebar/src/modules/connection-options.ts index 9047819802d..2ef3f56e5c3 100644 --- a/packages/compass-sidebar/src/modules/connection-options.ts +++ b/packages/compass-sidebar/src/modules/connection-options.ts @@ -1,3 +1,4 @@ +import type { ConnectionInfo } from '@mongodb-js/connection-info'; import type { RootAction } from '.'; const HOST_STRING_LENGTH = 25; @@ -5,31 +6,40 @@ export const CHANGE_CONNECTION_OPTIONS = 'sidebar/connection-options/CHANGE_CONNECTION_OPTIONS' as const; interface ChangeConnectionOptionsAction { type: typeof CHANGE_CONNECTION_OPTIONS; - options: ConnectionOptionsState; + connectionId: ConnectionInfo['id']; + options: SingleConnectionOptionsState; } export type ConnectionOptionsAction = ChangeConnectionOptionsAction; -export const INITIAL_STATE: ConnectionOptionsState = { - sshTunnel: false, - sshTunnelHostname: '', - sshTunnelPort: '', - sshTunnelHostPortString: '', -}; - -export type ConnectionOptionsState = { +export const INITIAL_STATE: ConnectionOptionsState = {}; +export type SingleConnectionOptionsState = { sshTunnel: boolean; sshTunnelHostname: string; sshTunnelPort: string | number; sshTunnelHostPortString: string; }; +export type ConnectionOptionsState = Record< + ConnectionInfo['id'], + { + sshTunnel: boolean; + sshTunnelHostname: string; + sshTunnelPort: string | number; + sshTunnelHostPortString: string; + } +>; + export default function reducer( state = INITIAL_STATE, action: RootAction ): ConnectionOptionsState { if (action.type === CHANGE_CONNECTION_OPTIONS) { - return action.options; + return { + ...state, + [action.connectionId]: action.options, + }; } + return state; } @@ -41,9 +51,12 @@ function combineHostPort(host: string, port: string | number): string { return `${host}:${port}`; } -export function changeConnectionOptions(connectionOptions: { - sshTunnel?: { host: string; port: string | number }; -}): ConnectionOptionsAction { +export function changeConnectionOptions( + connectionId: ConnectionInfo['id'], + connectionOptions: { + sshTunnel?: { host: string; port: string | number }; + } +): ConnectionOptionsAction { const sshTunnel = !!connectionOptions.sshTunnel; const sshTunnelHostname = connectionOptions?.sshTunnel?.host ?? ''; const sshTunnelPort = connectionOptions?.sshTunnel?.port ?? ''; @@ -53,6 +66,7 @@ export function changeConnectionOptions(connectionOptions: { return { type: CHANGE_CONNECTION_OPTIONS, + connectionId, options: { sshTunnel, sshTunnelHostname, diff --git a/packages/compass-sidebar/src/modules/data-service.ts b/packages/compass-sidebar/src/modules/data-service.ts index 0d3b3cfc43d..75b036fe0b4 100644 --- a/packages/compass-sidebar/src/modules/data-service.ts +++ b/packages/compass-sidebar/src/modules/data-service.ts @@ -1,46 +1,21 @@ -import type { DataService } from '@mongodb-js/compass-connections/provider'; -import type { RootAction, SidebarThunkAction } from '.'; - -export const SET_DATASERVICE = 'sidebar/SET_DATASERVICE' as const; -interface SetDataServiceAction { - type: typeof SET_DATASERVICE; - dataService: DataService; -} -export const SET_CSFLE_ENABLED = 'sidebar/SET_CSFLE_ENABLED' as const; -interface SetCSFLEEnabledAction { - type: typeof SET_CSFLE_ENABLED; - enable: boolean; -} -export type DataServiceAction = SetDataServiceAction | SetCSFLEEnabledAction; -export type DataServiceState = DataService | null; - -export const INITIAL_STATE: DataServiceState = null; - -export default function reducer( - state: DataServiceState = INITIAL_STATE, - action: RootAction -): DataServiceState { - if (action.type === SET_DATASERVICE) { - return action.dataService; - } - if (action.type === SET_CSFLE_ENABLED) { - state?.setCSFLEEnabled(action.enable); - } - return state; -} - -export const setDataService = ( - dataService: DataService -): SetDataServiceAction => ({ - type: SET_DATASERVICE, - dataService, -}); +import type { SidebarThunkAction } from '.'; +import type { ConnectionInfo } from '@mongodb-js/connection-info'; export const setConnectionIsCSFLEEnabled = ( + connectionId: ConnectionInfo['id'], enable: boolean -): SidebarThunkAction => { - return (dispatch, _getState, { globalAppRegistry }) => { - dispatch({ type: SET_CSFLE_ENABLED, enable }); +): SidebarThunkAction => { + return (_dispatch, _getState, { globalAppRegistry, connectionsManager }) => { + const dataService = + connectionsManager.getDataServiceForConnection(connectionId); + + if (!dataService) { + throw new Error( + 'unreachable: This is only visible when we are connected.' + ); + } + + dataService.setCSFLEEnabled(enable); queueMicrotask(() => { globalAppRegistry?.emit('refresh-data'); }); diff --git a/packages/compass-sidebar/src/modules/databases.spec.ts b/packages/compass-sidebar/src/modules/databases.spec.ts index 71a33f05b8a..5a8a6764bf4 100644 --- a/packages/compass-sidebar/src/modules/databases.spec.ts +++ b/packages/compass-sidebar/src/modules/databases.spec.ts @@ -9,10 +9,17 @@ import databasesReducer, { import { createInstance } from '../../test/helpers'; +const CONNECTION_ID = 'webscale'; + function createGetState(dbs: any[] = []) { return function () { return { - instance: createInstance(dbs).toJSON(), + instance: { + [CONNECTION_ID]: createInstance(dbs).toJSON(), + }, + databases: { + [CONNECTION_ID]: {}, + }, appRegistry: { localAppRegistry: null, globalAppRegistry: null }, }; }; @@ -45,13 +52,15 @@ describe('sidebar databases', function () { it('sets databases as-is', function () { const dbs = createDatabases([{ _id: 'foo' }, { _id: 'bar' }]); - expect(databasesReducer(undefined, changeDatabases(dbs))).to.deep.equal( - { + expect( + databasesReducer(undefined, changeDatabases(CONNECTION_ID, dbs)) + ).to.deep.equal({ + [CONNECTION_ID]: { ...INITIAL_STATE, databases: dbs, filteredDatabases: dbs, - } - ); + }, + }); }); }); @@ -71,11 +80,16 @@ describe('sidebar databases', function () { ]); expect( - databasesReducer(initialState, changeDatabases(dbs)) + databasesReducer( + { [CONNECTION_ID]: { ...initialState } as any }, + changeDatabases(CONNECTION_ID, dbs) + ) ).to.deep.equal({ - ...initialState, - filteredDatabases: dbs.filter((db) => db._id === 'foo'), - databases: dbs, + [CONNECTION_ID]: { + ...initialState, + filteredDatabases: dbs.filter((db) => db._id === 'foo'), + databases: dbs, + }, }); }); } @@ -92,26 +106,35 @@ describe('sidebar databases', function () { ]); const slice = createMockStoreSlice({ - databases: dbs, + [CONNECTION_ID]: { + databases: dbs, + }, }); - changeFilterRegex(/^foo$/)(slice.dispatch, getState as any, { - globalAppRegistry: { - emit() { - // noop + changeFilterRegex(/^foo$/)( + slice.dispatch, + getState as any, + { + globalAppRegistry: { + emit() { + // noop + }, }, - } as any, - }); + } as any + ); expect(slice.state).to.deep.eq({ - ...INITIAL_STATE, - filterRegex: /^foo$/, - databases: dbs, - filteredDatabases: dbs.filter( - (db) => - db._id === 'foo' || - db.collections.find((coll) => coll._id === 'bar.foo') - ), + [CONNECTION_ID]: { + ...INITIAL_STATE, + filterRegex: /^foo$/, + databases: dbs, + expandedDbList: {}, + filteredDatabases: dbs.filter( + (db) => + db._id === 'foo' || + db.collections.find((coll) => coll._id === 'bar.foo') + ), + }, }); }); }); diff --git a/packages/compass-sidebar/src/modules/databases.ts b/packages/compass-sidebar/src/modules/databases.ts index f44644d865e..ffeb4331a71 100644 --- a/packages/compass-sidebar/src/modules/databases.ts +++ b/packages/compass-sidebar/src/modules/databases.ts @@ -1,27 +1,32 @@ import type { MongoDBInstance } from 'mongodb-instance-model'; import type { RootAction, SidebarThunkAction } from '.'; import toNS from 'mongodb-ns'; +import { type ConnectionInfo } from '@mongodb-js/connection-info'; /** * Databases actions. */ export const CHANGE_FILTER_REGEX = 'sidebar/databases/CHANGE_FILTER_REGEX' as const; + interface ChangeFilterRegexAction { type: typeof CHANGE_FILTER_REGEX; + connectionId: ConnectionInfo['id']; filterRegex: null | RegExp; } export const CHANGE_DATABASES = 'sidebar/databases/CHANGE_DATABASES' as const; interface ChangeDatabasesAction { type: typeof CHANGE_DATABASES; + connectionId: ConnectionInfo['id']; databases: Database[]; } export const TOGGLE_DATABASE = 'sidebar/databases/TOGGLE_DATABASE' as const; interface ToggleDatabaseAction { type: typeof TOGGLE_DATABASE; - id: string; + connectionId: ConnectionInfo['id']; + database: string; expanded: boolean; } @@ -30,10 +35,6 @@ export type DatabasesAction = | ChangeDatabasesAction | ToggleDatabaseAction; -const NO_REGEX = null; - -export const NO_ACTIVE_NAMESPACE = ''; - type DatabaseRaw = MongoDBInstance['databases'][number]; export type Database = Pick< @@ -45,7 +46,11 @@ export type Database = Pick< '_id' | 'name' | 'type' | 'sourceName' | 'pipeline' >[]; }; -export interface DatabaseState { +export type AllDatabasesState = Record< + ConnectionInfo['id'], + ConnectionDatabasesState +>; +export interface ConnectionDatabasesState { databases: Database[]; filteredDatabases: Database[]; expandedDbList: Record; @@ -55,12 +60,7 @@ export interface DatabaseState { /** * The initial state of the sidebar databases. */ -export const INITIAL_STATE: DatabaseState = { - databases: [], - filteredDatabases: [], - expandedDbList: Object.create(null), - filterRegex: NO_REGEX, -}; +export const INITIAL_STATE: AllDatabasesState = {}; /** * Reducer function for handle state changes to sidebar databases. @@ -71,25 +71,38 @@ export const INITIAL_STATE: DatabaseState = { * @returns {Object} The new state. */ export default function reducer( - state: DatabaseState = INITIAL_STATE, + state: AllDatabasesState = INITIAL_STATE, action: RootAction -): DatabaseState { +): AllDatabasesState { if (action.type === TOGGLE_DATABASE) { + if ( + state[action.connectionId] && + state[action.connectionId].expandedDbList + ) { + const previousExpandedState = + state[action.connectionId]?.expandedDbList[action.database]; + + if (previousExpandedState === action.expanded) { + return state; + } + } + return { ...state, - expandedDbList: { - ...state.expandedDbList, - [action.id]: action.expanded, + [action.connectionId]: { + ...state[action.connectionId], + expandedDbList: { + ...state[action.connectionId].expandedDbList, + [action.database]: action.expanded, + }, }, }; - } - - if (action.type === CHANGE_FILTER_REGEX) { + } else if (action.type === CHANGE_FILTER_REGEX) { const filterModeStatusChange = - Boolean(state.filterRegex && !action.filterRegex) || - Boolean(!state.filterRegex && action.filterRegex); + Boolean(state[action.connectionId]?.filterRegex && !action.filterRegex) || + Boolean(!state[action.connectionId]?.filterRegex && action.filterRegex); - let expandedDbList = state.expandedDbList; + let expandedDbList = state[action.connectionId]?.expandedDbList ?? {}; // On filter mode status change (when either entering "filter mode" when no // regex was in the search box before or exiting it) we want to filter out @@ -105,64 +118,78 @@ export default function reducer( return { ...state, - filterRegex: action.filterRegex, - filteredDatabases: filterDatabases(state.databases, action.filterRegex), - expandedDbList, + [action.connectionId]: { + ...state[action.connectionId], + filterRegex: action.filterRegex, + filteredDatabases: filterDatabases( + state[action.connectionId]?.databases ?? [], + action.filterRegex + ), + expandedDbList, + }, }; - } - - if (action.type === CHANGE_DATABASES) { + } else if (action.type === CHANGE_DATABASES) { return { ...state, - databases: action.databases, - filteredDatabases: filterDatabases(action.databases, state.filterRegex), + [action.connectionId]: { + ...state[action.connectionId], + databases: action.databases, + filteredDatabases: filterDatabases( + action.databases, + state[action.connectionId]?.filterRegex + ), + }, }; } + return state; } -/** - * The change databases action creator. - * - * @param {Array} databases - * - * @returns {Object} The action. - */ -export const changeDatabases = (databases: Database[]) => ({ +export const changeDatabases = ( + connectionId: ConnectionInfo['id'], + databases: Database[] +) => ({ type: CHANGE_DATABASES, + connectionId, databases, }); export const toggleDatabaseExpanded = ( - id: string, + connectionId: ConnectionInfo['id'], + databaseId: string, forceExpand: boolean ): SidebarThunkAction => (dispatch, getState, { globalAppRegistry }) => { - const { database } = toNS(id); - const { databases } = getState(); - const expanded = forceExpand ?? !databases.expandedDbList[database]; + const { database } = toNS(databaseId); + const { expandedDbList } = getState().databases[connectionId]; + const expanded = forceExpand ?? !expandedDbList[database]; + if (expanded) { // Fetch collections list on expand if we haven't done it yet (this is // relevant only for the code path that has global overlay disabled) globalAppRegistry.emit('sidebar-expand-database', database); } - dispatch({ type: TOGGLE_DATABASE, id: database, expanded }); + dispatch({ type: TOGGLE_DATABASE, connectionId, database, expanded }); }; export const changeFilterRegex = (filterRegex: RegExp | null): SidebarThunkAction => - (dispatch, _getState, { globalAppRegistry }) => { + (dispatch, getState, { globalAppRegistry }) => { if (filterRegex) { // When filtering, emit an event so that we can fetch all collections. This // is required as a workaround for the syncronous nature of the current // filtering feature globalAppRegistry.emit('sidebar-filter-navigation-list'); } - dispatch({ - type: CHANGE_FILTER_REGEX, - filterRegex: filterRegex, - }); + + for (const connectionId of Object.keys(getState().databases)) { + dispatch({ + type: CHANGE_FILTER_REGEX, + connectionId, + filterRegex, + }); + } }; const filterDatabases = (databases: Database[], re: RegExp | null) => { diff --git a/packages/compass-sidebar/src/modules/index.ts b/packages/compass-sidebar/src/modules/index.ts index aaff9356d80..db2bd461e7e 100644 --- a/packages/compass-sidebar/src/modules/index.ts +++ b/packages/compass-sidebar/src/modules/index.ts @@ -1,64 +1,54 @@ import type { Action, AnyAction } from 'redux'; import { combineReducers } from 'redux'; -import type { DatabaseState, DatabasesAction } from './databases'; +import type { AllDatabasesState, DatabasesAction } from './databases'; import databases from './databases'; import type { InstanceAction, InstanceState } from './instance'; import instance from './instance'; -import type { - IsDetailsExpandedState, - ToggleIsDetailsExpandedAction, -} from './is-details-expanded'; -import isDetailsExpanded from './is-details-expanded'; import type { IsGenuineMongoDBVisibleAction, IsGenuineMongoDBVisibleState, } from './is-genuine-mongodb-visible'; import isGenuineMongoDBVisible from './is-genuine-mongodb-visible'; -import type { - ConnectionInfoAction, - ConnectionInfoState, -} from './connection-info'; -import connectionInfo from './connection-info'; import type { ConnectionOptionsAction, ConnectionOptionsState, } from './connection-options'; import connectionOptions from './connection-options'; import type { AppRegistry } from 'hadron-app-registry'; -import type { DataServiceAction, DataServiceState } from './data-service'; -import dataService from './data-service'; import type { IsPerformanceTabSupportedState, SetIsPerformanceTabSupportedAction, } from './is-performance-tab-supported'; import isPerformanceTabSupported from './is-performance-tab-supported'; import type { ThunkAction } from 'redux-thunk'; +import type { ConnectionsManager } from '@mongodb-js/compass-connections/provider'; +import type { MongoDBInstancesManager } from '@mongodb-js/compass-app-stores/provider'; +import type { LoggerAndTelemetry } from '@mongodb-js/compass-logging/provider'; export interface RootState { - dataService: DataServiceState; - connectionInfo: ConnectionInfoState; connectionOptions: ConnectionOptionsState; - databases: DatabaseState; + databases: AllDatabasesState; instance: InstanceState; - isDetailsExpanded: IsDetailsExpandedState; isGenuineMongoDBVisible: IsGenuineMongoDBVisibleState; isPerformanceTabSupported: IsPerformanceTabSupportedState; } export type RootAction = - | ConnectionInfoAction | ConnectionOptionsAction | DatabasesAction | InstanceAction - | ToggleIsDetailsExpandedAction | IsGenuineMongoDBVisibleAction - | DataServiceAction | SetIsPerformanceTabSupportedAction; export type SidebarThunkAction = ThunkAction< R, RootState, - { globalAppRegistry: AppRegistry }, + { + globalAppRegistry: AppRegistry; + connectionsManager: ConnectionsManager; + instancesManager: MongoDBInstancesManager; + logger: LoggerAndTelemetry; + }, A >; @@ -67,11 +57,8 @@ export type SidebarThunkAction = ThunkAction< */ const reducer = combineReducers({ databases, - dataService, - connectionInfo, connectionOptions, instance, - isDetailsExpanded, isGenuineMongoDBVisible, isPerformanceTabSupported, }); diff --git a/packages/compass-sidebar/src/modules/instance.spec.ts b/packages/compass-sidebar/src/modules/instance.spec.ts index 9ca6b38003e..b3b30d58eea 100644 --- a/packages/compass-sidebar/src/modules/instance.spec.ts +++ b/packages/compass-sidebar/src/modules/instance.spec.ts @@ -1,38 +1,84 @@ import { expect } from 'chai'; - -import reducer, { - INITIAL_STATE, - changeInstance, - CHANGE_INSTANCE, -} from './instance'; - -const instance: any = { - _id: '123', -}; +import { createInstance } from '../../test/helpers'; +import { spy, stub, type SinonSpy, type SinonStub } from 'sinon'; +import type { DataService } from 'mongodb-data-service'; +import { setupInstance } from './instance'; +import type { RootState } from '.'; +import type { ConnectionsManager } from '@mongodb-js/compass-connections/provider'; +import type AppRegistry from 'hadron-app-registry'; +import type { LoggerAndTelemetry } from '@mongodb-js/compass-logging'; +import type { MongoDBInstancesManager } from '@mongodb-js/compass-app-stores/provider'; describe('sidebar instance', function () { - describe('#reducer', function () { - context('when an action is provided', function () { - it('returns the new state', function () { - expect(reducer(undefined, changeInstance(instance))).to.deep.equal({ - _id: '123', - }); - }); - }); + const instance = createInstance(); + let instanceOnSpy: SinonSpy; + const globalAppRegistry = {} as any as AppRegistry; + const connectionsManager = { + getDataServiceForConnection() { + return { + getConnectionOptions() { + return {}; + }, + currentOp() { + return Promise.resolve(null); + }, + top() { + return Promise.resolve(null); + }, + } as unknown as DataService; + }, + } as any as ConnectionsManager; + let instancesManager: MongoDBInstancesManager; + let logger: LoggerAndTelemetry; + let listMongoDBInstancesStub: SinonStub; - context('when an action is not provided', function () { - it('returns the default state', function () { - expect(reducer(undefined, {} as any)).to.equal(INITIAL_STATE); - }); - }); + beforeEach(function () { + instanceOnSpy = spy(); + instance.on = instanceOnSpy; + instancesManager = { + listMongoDBInstances: listMongoDBInstancesStub, + } as any; + logger = { + log: { warn() {} }, + mongoLogId() {}, + } as any as LoggerAndTelemetry; }); - describe('#changeInstance', function () { - it('returns the action', function () { - expect(changeInstance('new instance w action' as any)).to.deep.equal({ - type: CHANGE_INSTANCE, - instance: 'new instance w action', - }); + for (const event of [ + 'change:status', + 'change:refreshingStatus', + 'change:databasesStatus', + 'change:csfleMode', + 'change:topologyDescription', + 'change:isWritable', + 'change:env', + 'change:databasesStatus', + 'add:databases', + 'remove:databases', + 'change:databases', + 'change:databases.collectionsStatus', + 'add:collections', + 'remove:collections', + 'change:collections._id', + 'change:collections.status', + 'change:genuineMongoDB.isGenuine', + ]) { + it(`subscribes to an existing instance event ${event}`, function () { + setupInstance(instance._id, instance)( + stub(), + () => + ({ + instance: [], + } as any as RootState), + { + globalAppRegistry, + connectionsManager, + instancesManager, + logger, + } + ); + + expect(instanceOnSpy).to.have.been.calledWith(event); }); - }); + } }); diff --git a/packages/compass-sidebar/src/modules/instance.ts b/packages/compass-sidebar/src/modules/instance.ts index e15008c542a..e652aaa7aa8 100644 --- a/packages/compass-sidebar/src/modules/instance.ts +++ b/packages/compass-sidebar/src/modules/instance.ts @@ -1,20 +1,38 @@ import type { MongoDBInstance } from 'mongodb-instance-model'; -import type { RootAction } from '.'; +import type { RootAction, SidebarThunkAction } from '.'; +import { type ConnectionInfo } from '@mongodb-js/connection-info'; +import throttle from 'lodash/throttle'; +import { type Database, changeDatabases } from './databases'; +import { changeConnectionOptions } from './connection-options'; +import { toggleIsGenuineMongoDBVisible } from './is-genuine-mongodb-visible'; +import { setIsPerformanceTabSupported } from './is-performance-tab-supported'; +import type { MongoServerError } from 'mongodb'; /** * Instance action. */ -export const CHANGE_INSTANCE = 'sidebar/instance/CHANGE_INSTANCE' as const; -export interface ChangeInstanceAction { - type: typeof CHANGE_INSTANCE; - instance: InstanceState; +export const SETUP_INSTANCE = 'sidebar/instance/SETUP_INSTANCE' as const; +export interface SetupInstanceAction { + type: typeof SETUP_INSTANCE; + connectionId: ConnectionInfo['id']; + instance: SingleInstanceState; +} + +/** + * Instance action. + */ +export const CLOSE_INSTANCE = 'sidebar/instance/CLOSE_INSTANCE' as const; +export interface CloseInstanceAction { + type: typeof CLOSE_INSTANCE; + connectionId: ConnectionInfo['id']; } /** * The initial state of the sidebar instance. */ -export const INITIAL_STATE: InstanceState = null; -export type InstanceState = null | Pick< +export const INITIAL_STATE: InstanceState = {}; +export type InstanceState = Record; +export type SingleInstanceState = null | Pick< MongoDBInstance, | 'status' | 'refreshingStatus' @@ -29,7 +47,7 @@ export type InstanceState = null | Pick< | 'isAtlas' | 'isLocalAtlas' >; -export type InstanceAction = ChangeInstanceAction; +export type InstanceAction = SetupInstanceAction | CloseInstanceAction; /** * Reducer function for handle state changes to sidebar instance. @@ -43,20 +61,186 @@ export default function reducer( state: InstanceState = INITIAL_STATE, action: RootAction ): InstanceState { - if (action.type === CHANGE_INSTANCE) { - return action.instance; + if (action.type === SETUP_INSTANCE) { + return { + ...state, + [action.connectionId]: action.instance, + }; + } else if (action.type === CLOSE_INSTANCE) { + const result = { ...state }; + delete result[action.connectionId]; + return result; } return state; } -/** - * The change instance action creator. - * - * @param {String} instance - The instance. - * - * @returns {Object} The action. - */ -export const changeInstance = (instance: InstanceState) => ({ - type: CHANGE_INSTANCE, - instance: instance, -}); +export const setupInstance = + ( + connectionId: ConnectionInfo['id'], + instance: MongoDBInstance + ): SidebarThunkAction => + (dispatch, getState, { connectionsManager, logger: { log, mongoLogId } }) => { + const { instance: instanceList } = getState(); + + if (instanceList[connectionId]) { + // We only need to track the initial set up of the instance here. + // The reason is that any other change relevant to the instance will + // be received through instance events. + return; + } + + const onInstanceChange = throttle( + () => { + dispatch({ + type: SETUP_INSTANCE, + connectionId, + instance: { + status: instance.status, + refreshingStatus: instance.refreshingStatus, + databasesStatus: instance.databasesStatus, + csfleMode: instance.csfleMode, + build: { + isEnterprise: instance.build.isEnterprise, + version: instance.build.version, + }, + dataLake: { + isDataLake: instance.dataLake.isDataLake, + version: instance.dataLake.version, + }, + genuineMongoDB: { + dbType: instance.genuineMongoDB.dbType, + isGenuine: instance.genuineMongoDB.isGenuine, + }, + topologyDescription: { + servers: instance.topologyDescription.servers, + setName: instance.topologyDescription.setName, + type: instance.topologyDescription.type, + }, + isWritable: instance.isWritable, + env: instance.env, + isAtlas: instance.isAtlas, + isLocalAtlas: instance.isLocalAtlas, + }, + }); + }, + 300, + { leading: true, trailing: true } + ); + + function getDatabaseInfo(db: Database) { + return { + _id: db._id, + name: db.name, + collectionsStatus: db.collectionsStatus, + collectionsLength: db.collectionsLength, + }; + } + + function getCollectionInfo(coll: Database['collections'][number]) { + return { + _id: coll._id, + name: coll.name, + type: coll.type, + sourceName: coll.sourceName, + pipeline: coll.pipeline, + }; + } + + const onDatabasesChange = throttle( + () => { + const dbs = instance.databases.map((db) => { + return { + ...getDatabaseInfo(db), + collections: db.collections.map((coll) => { + return getCollectionInfo(coll); + }), + }; + }); + + dispatch(changeDatabases(connectionId, dbs)); + }, + 300, + { leading: true, trailing: true } + ); + + instance.on('change:status', onInstanceChange); + instance.on('change:refreshingStatus', onInstanceChange); + instance.on('change:databasesStatus', onInstanceChange); + instance.on('change:csfleMode', onInstanceChange); + instance.on('change:topologyDescription', onInstanceChange); + instance.on('change:isWritable', onInstanceChange); + instance.on('change:env', onInstanceChange); + + instance.on('change:databasesStatus', onDatabasesChange); + instance.on('add:databases', onDatabasesChange); + instance.on('remove:databases', onDatabasesChange); + instance.on('change:databases', onDatabasesChange); + instance.on('change:databases.collectionsStatus', onDatabasesChange); + + instance.on('add:collections', onDatabasesChange); + instance.on('remove:collections', onDatabasesChange); + instance.on('change:collections._id', onDatabasesChange); + instance.on('change:collections.status', onDatabasesChange); + + const dataService = + connectionsManager.getDataServiceForConnection(connectionId); + if (!dataService) { + // This should be unreachable, because the instance state is only available + // whenever we have a Data Service connected, as it reads server metadata. + return; + } + + const connectionOptions = dataService.getConnectionOptions(); + dispatch(changeConnectionOptions(connectionId, connectionOptions)); // stores ssh tunnel status + + dispatch( + toggleIsGenuineMongoDBVisible( + connectionId, + !instance.genuineMongoDB.isGenuine + ) + ); + + instance.on( + 'change:genuineMongoDB.isGenuine', + (_model: unknown, isGenuine: boolean) => { + dispatch(toggleIsGenuineMongoDBVisible(connectionId, !isGenuine)); + } + ); + + void Promise.all([dataService.currentOp(), dataService.top()]).then( + () => { + dispatch(setIsPerformanceTabSupported(connectionId, true)); + }, + (err) => { + log.info( + mongoLogId(1_001_000_278), + 'Sidebar', + 'Performance tab required commands failed', + { error: (err as Error).message } + ); + // Only disable performance tab if encountered Atlas error + const isSupported = + (err as MongoServerError).codeName === 'AtlasError' ? false : true; + dispatch(setIsPerformanceTabSupported(connectionId, isSupported)); + } + ); + + onInstanceChange(); + onDatabasesChange(); + }; + +export const closeInstance = + (connectionId: ConnectionInfo['id']): SidebarThunkAction => + (dispatch, getState) => { + const { instance: instanceList } = getState(); + + if (!instanceList[connectionId]) { + // Don't do anything if we don't have the instance here. + return; + } + + dispatch({ + type: CLOSE_INSTANCE, + connectionId, + }); + }; diff --git a/packages/compass-sidebar/src/modules/is-details-expanded.spec.ts b/packages/compass-sidebar/src/modules/is-details-expanded.spec.ts deleted file mode 100644 index ffa2daddab9..00000000000 --- a/packages/compass-sidebar/src/modules/is-details-expanded.spec.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { expect } from 'chai'; - -import reducer, { - INITIAL_STATE, - toggleIsDetailsExpanded, - TOGGLE_IS_DETAILS_EXPANDED, -} from './is-details-expanded'; - -describe('sidebar isDetailsExpanded', function () { - describe('#reducer', function () { - context('when an action is provided', function () { - it('returns the new state', function () { - expect(reducer(undefined, toggleIsDetailsExpanded(true))).to.equal( - true - ); - }); - }); - - context('when an action is not provided', function () { - it('returns the default state', function () { - expect(reducer(undefined, {} as any)).to.equal(INITIAL_STATE); - }); - }); - }); - - describe('#toggleIsExpanded', function () { - it('returns the action', function () { - expect(toggleIsDetailsExpanded(false)).to.deep.equal({ - type: TOGGLE_IS_DETAILS_EXPANDED, - isExpanded: false, - }); - }); - }); -}); diff --git a/packages/compass-sidebar/src/modules/is-details-expanded.ts b/packages/compass-sidebar/src/modules/is-details-expanded.ts deleted file mode 100644 index 436057bd6c9..00000000000 --- a/packages/compass-sidebar/src/modules/is-details-expanded.ts +++ /dev/null @@ -1,49 +0,0 @@ -import type { RootAction } from '.'; - -/** - * Change is details expanded - */ -export const TOGGLE_IS_DETAILS_EXPANDED = - 'sidebar/is-details-expanded/TOGGLE_IS_DETAILS_EXPANDED' as const; -export interface ToggleIsDetailsExpandedAction { - type: typeof TOGGLE_IS_DETAILS_EXPANDED; - isExpanded: boolean; -} - -/** - * The initial state of the is expanded attribute. - */ -export const INITIAL_STATE: IsDetailsExpandedState = true; -export type IsDetailsExpandedState = boolean; - -/** - * Reducer function for handle state changes to is details expanded. - * - * @param {Boolean} state - The is expanded state. - * @param {Object} action - The action. - * - * @returns {Array} The new state. - */ -export default function reducer( - state: IsDetailsExpandedState = INITIAL_STATE, - action: RootAction -): IsDetailsExpandedState { - if (action.type === TOGGLE_IS_DETAILS_EXPANDED) { - return action.isExpanded; - } - return state; -} - -/** - * The toggle is details expanded action creator. - * - * @param {Boolean} isExpanded - Is the details section expanded - * - * @returns {Object} The action. - */ -export const toggleIsDetailsExpanded = ( - isExpanded: boolean -): ToggleIsDetailsExpandedAction => ({ - type: TOGGLE_IS_DETAILS_EXPANDED, - isExpanded: isExpanded, -}); diff --git a/packages/compass-sidebar/src/modules/is-genuine-mongodb-visible.spec.ts b/packages/compass-sidebar/src/modules/is-genuine-mongodb-visible.spec.ts index 9beade36fbf..6b5bf2674f1 100644 --- a/packages/compass-sidebar/src/modules/is-genuine-mongodb-visible.spec.ts +++ b/packages/compass-sidebar/src/modules/is-genuine-mongodb-visible.spec.ts @@ -1,34 +1,38 @@ import { expect } from 'chai'; import reducer, { - INITIAL_STATE, toggleIsGenuineMongoDBVisible, TOGGLE_IS_GENUINE_MONGODB_VISIBLE, } from './is-genuine-mongodb-visible'; +const CONNECTION_ID = 'webscale'; + describe('is genuine mongodb visible module', function () { describe('#reducer', function () { context('when an action is provided', function () { it('returns the new state', function () { expect( - reducer(undefined, toggleIsGenuineMongoDBVisible(true)) - ).to.equal(true); + reducer(undefined, toggleIsGenuineMongoDBVisible(CONNECTION_ID, true)) + ).to.deep.equal({ [CONNECTION_ID]: true }); }); }); context('when an action is not provided', function () { it('returns the default state', function () { - expect(reducer(undefined, {} as any)).to.equal(INITIAL_STATE); + expect(reducer(undefined, {} as any)).to.deep.equal({}); }); }); }); describe('#toggleIsGenuineMongoDBVisible', function () { it('returns the action', function () { - expect(toggleIsGenuineMongoDBVisible(false)).to.deep.equal({ - type: TOGGLE_IS_GENUINE_MONGODB_VISIBLE, - isVisible: false, - }); + expect(toggleIsGenuineMongoDBVisible(CONNECTION_ID, false)).to.deep.equal( + { + type: TOGGLE_IS_GENUINE_MONGODB_VISIBLE, + connectionId: CONNECTION_ID, + isVisible: false, + } + ); }); }); }); diff --git a/packages/compass-sidebar/src/modules/is-genuine-mongodb-visible.ts b/packages/compass-sidebar/src/modules/is-genuine-mongodb-visible.ts index 9adef78e42d..0acbd1e9425 100644 --- a/packages/compass-sidebar/src/modules/is-genuine-mongodb-visible.ts +++ b/packages/compass-sidebar/src/modules/is-genuine-mongodb-visible.ts @@ -1,3 +1,4 @@ +import type { ConnectionInfo } from '@mongodb-js/connection-info'; import type { RootAction } from '.'; /** @@ -12,15 +13,20 @@ export const TOGGLE_IS_GENUINE_MONGODB_VISIBLE = `${PREFIX}/TOGGLE_IS_GENUINE_MONGODB_VISIBLE` as const; interface ToggleIsGenuineMongoDBVisibleAction { type: typeof TOGGLE_IS_GENUINE_MONGODB_VISIBLE; + connectionId: ConnectionInfo['id']; isVisible: boolean; } export type IsGenuineMongoDBVisibleAction = ToggleIsGenuineMongoDBVisibleAction; -export type IsGenuineMongoDBVisibleState = boolean; +export type IsGenuineMongoDBVisibleState = Record< + ConnectionInfo['id'], + boolean +>; +export type IsGenuineMongoDBVisibleSingleState = boolean; /** * The initial state of the is visible attribute. */ -export const INITIAL_STATE: IsGenuineMongoDBVisibleState = false; +export const INITIAL_STATE: IsGenuineMongoDBVisibleState = {}; /** * Reducer function for handle state changes to is visible. @@ -35,7 +41,10 @@ export default function reducer( action: RootAction ): IsGenuineMongoDBVisibleState { if (action.type === TOGGLE_IS_GENUINE_MONGODB_VISIBLE) { - return action.isVisible; + return { + ...state, + [action.connectionId]: action.isVisible, + }; } return state; } @@ -48,8 +57,10 @@ export default function reducer( * @returns {Object} The action. */ export const toggleIsGenuineMongoDBVisible = ( + connectionId: ConnectionInfo['id'], isVisible: boolean ): ToggleIsGenuineMongoDBVisibleAction => ({ type: TOGGLE_IS_GENUINE_MONGODB_VISIBLE, - isVisible: isVisible, + connectionId, + isVisible, }); diff --git a/packages/compass-sidebar/src/modules/is-performance-tab-supported.ts b/packages/compass-sidebar/src/modules/is-performance-tab-supported.ts index cdda2179d81..a2e8942a0cc 100644 --- a/packages/compass-sidebar/src/modules/is-performance-tab-supported.ts +++ b/packages/compass-sidebar/src/modules/is-performance-tab-supported.ts @@ -1,3 +1,4 @@ +import type { ConnectionInfo } from '@mongodb-js/connection-info'; import type { RootAction } from '.'; const SET_IS_PERFORMANCE_TAB_SUPPORTED = @@ -5,27 +6,38 @@ const SET_IS_PERFORMANCE_TAB_SUPPORTED = export type SetIsPerformanceTabSupportedAction = { type: typeof SET_IS_PERFORMANCE_TAB_SUPPORTED; + connectionId: ConnectionInfo['id']; isSupported: boolean; }; export function setIsPerformanceTabSupported( + connectionId: ConnectionInfo['id'], isSupported: boolean ): SetIsPerformanceTabSupportedAction { return { type: SET_IS_PERFORMANCE_TAB_SUPPORTED, + connectionId, isSupported, }; } -export type IsPerformanceTabSupportedState = boolean | null; +export type IsPerformanceTabSupportedSingleState = boolean | null; +export type IsPerformanceTabSupportedState = Record< + ConnectionInfo['id'], + IsPerformanceTabSupportedSingleState +>; const reducer = ( - state: IsPerformanceTabSupportedState = null, + state: IsPerformanceTabSupportedState = {}, action: RootAction ): IsPerformanceTabSupportedState => { if (action.type === SET_IS_PERFORMANCE_TAB_SUPPORTED) { - return action.isSupported; + return { + ...state, + [action.connectionId]: action.isSupported, + }; } + return state; }; diff --git a/packages/compass-sidebar/src/plugin.tsx b/packages/compass-sidebar/src/plugin.tsx index 516df65af9c..9e2473bee0e 100644 --- a/packages/compass-sidebar/src/plugin.tsx +++ b/packages/compass-sidebar/src/plugin.tsx @@ -7,9 +7,10 @@ import { } from '@mongodb-js/compass-components'; import type { ConnectionInfo } from '@mongodb-js/connection-info'; import { useActiveWorkspace } from '@mongodb-js/compass-workspaces/provider'; -import Sidebar from './components/sidebar'; +import Sidebar from './components/legacy/sidebar'; import { usePreference } from 'compass-preferences-model/provider'; -import { MultipleConnectionSidebar } from './components/multiple-connections/sidebar'; +import MultipleConnectionSidebar from './components/multiple-connections/sidebar'; +import { ConnectionInfoProvider } from '@mongodb-js/compass-connections/provider'; const errorBoundaryStyles = css({ width: defaultSidebarWidth, @@ -17,13 +18,14 @@ const errorBoundaryStyles = css({ export interface SidebarPluginProps { showConnectionInfo?: boolean; - // TODO(COMPASS-7397): the need for passing this directly to sidebar should go - // away with refactoring compoass-conneciton to a plugin - initialConnectionInfo?: ConnectionInfo | null | undefined; + singleConnectionConnectionInfo?: ConnectionInfo; } const SidebarPlugin: React.FunctionComponent = ({ showConnectionInfo, + // TODO(COMPASS-7397): the need for passing this directly to sidebar should go + // away with refactoring compass-connection to a plugin + singleConnectionConnectionInfo, }) => { const isMultiConnectionEnabled = usePreference( 'enableNewMultipleConnectionSystem' @@ -32,14 +34,26 @@ const SidebarPlugin: React.FunctionComponent = ({ const activeWorkspace = useActiveWorkspace(); const { log, mongoLogId } = useLoggerAndTelemetry('COMPASS-SIDEBAR-UI'); - const sidebar = isMultiConnectionEnabled ? ( - - ) : ( - - ); + let sidebar; + if (isMultiConnectionEnabled) { + sidebar = ; + } else { + sidebar = ( + + {(connectionInfo) => { + return ( + + ); + }} + + ); + } return ( { - return { - _id: db._id, - name: db.name, - collectionsStatus: db.collectionsStatus, - collectionsLength: db.collectionsLength, - collections: db.collections.map((coll) => { - return { - _id: coll._id, - name: coll.name, - type: coll.type, - pipeline: coll.pipeline, - sourceName: coll.sourceName, - }; - }), - }; - }); -} +const CONNECTION_ID = 'webscale'; +const ALL_EVENTS = [ + 'change:status', + 'change:refreshingStatus', + 'change:databasesStatus', + 'change:csfleMode', + 'change:topologyDescription', + 'change:isWritable', + 'change:env', + 'change:databasesStatus', + 'add:databases', + 'remove:databases', + 'change:databases', + 'change:databases.collectionsStatus', + 'add:collections', + 'remove:collections', + 'change:collections._id', + 'change:collections.status', + 'change:genuineMongoDB.isGenuine', +]; describe('SidebarStore [Store]', function () { - const globalAppRegistry = new AppRegistry(); - let store: ReturnType['store']; + const instance = createInstance(); + const globalAppRegistry = {} as any; + let instanceOnSpy: SinonSpy; + let instanceOffSpy: SinonSpy; + let deactivate: () => void; + let listMongoDBInstancesStub: SinonStub; + let instancesManager: MongoDBInstancesManager; beforeEach(function () { - ({ store, deactivate } = createSidebarStore( + instanceOnSpy = spy(); + instanceOffSpy = spy(); + instance.on = instanceOnSpy; + (instance as any).off = instanceOffSpy; + + listMongoDBInstancesStub = stub().returns( + new Map([[CONNECTION_ID, instance]]) + ); + + instancesManager = new EventEmitter() as MongoDBInstancesManager; + instancesManager.listMongoDBInstances = listMongoDBInstancesStub; + + ({ deactivate } = createSidebarStore( { globalAppRegistry, - dataService: { - getConnectionOptions() { - return {}; + connectionsManager: { + getDataServiceForConnection() { + return { + getConnectionOptions() { + return {}; + }, + currentOp() { + return Promise.resolve(null); + }, + top() { + return Promise.resolve(null); + }, + } as unknown as DataService; }, - currentOp() {}, - top() {}, - }, - instance, - logger: { log: { warn() {} }, mongoLogId() {} }, - } as any, + } as any, + instancesManager: instancesManager, + logger: createNoopLoggerAndTelemetry(), + }, { on() {}, cleanup() {}, addCleanup() {} } as any )); }); @@ -54,47 +83,25 @@ describe('SidebarStore [Store]', function () { deactivate(); }); - context('when instance created', function () { - it('updates the instance and databases state', function () { - const state = store.getState(); + for (const event of ALL_EVENTS) { + it(`subscribes to an existing instance event ${event}`, function () { + expect(instanceOnSpy).to.have.been.calledWith(event); + }); + } - expect(state) - .to.have.property('instance') - .deep.equal({ - build: { - isEnterprise: undefined, - version: undefined, - }, - csfleMode: 'unavailable', - dataLake: { - isDataLake: false, - version: undefined, - }, - databasesStatus: 'initial', - env: 'on-prem', - genuineMongoDB: { - dbType: undefined, - isGenuine: true, - }, - isAtlas: false, - isLocalAtlas: false, - isWritable: false, - refreshingStatus: 'initial', - status: 'initial', - topologyDescription: { - servers: [], - setName: 'foo', - type: 'Unknown', - }, - }); - expect(state) - .to.have.property('databases') - .deep.equal({ - databases: getDatabases(instance), - filteredDatabases: getDatabases(instance), - expandedDbList: {}, - filterRegex: null, - }); + describe('when a new instance is created', function () { + beforeEach(function () { + instancesManager.emit( + MongoDBInstancesManagerEvents.InstanceCreated, + 'newConnection', + createInstance() + ); }); + + for (const event of ALL_EVENTS) { + it(`subscribes to an existing instance event ${event}`, function () { + expect(instanceOnSpy).to.have.been.calledWith(event); + }); + } }); }); diff --git a/packages/compass-sidebar/src/stores/store.ts b/packages/compass-sidebar/src/stores/store.ts index ef7e61d1c40..bad2870fc9b 100644 --- a/packages/compass-sidebar/src/stores/store.ts +++ b/packages/compass-sidebar/src/stores/store.ts @@ -1,180 +1,57 @@ import { createStore, applyMiddleware } from 'redux'; -import throttle from 'lodash/throttle'; import thunk from 'redux-thunk'; import reducer from '../modules'; -import { changeInstance } from '../modules/instance'; -import type { Database } from '../modules/databases'; -import { changeDatabases } from '../modules/databases'; -import { toggleIsGenuineMongoDBVisible } from '../modules/is-genuine-mongodb-visible'; -import { changeConnectionInfo } from '../modules/connection-info'; -import { changeConnectionOptions } from '../modules/connection-options'; -import { setDataService } from '../modules/data-service'; +import { closeInstance, setupInstance } from '../modules/instance'; import type { ActivateHelpers, AppRegistry } from 'hadron-app-registry'; -import type { MongoDBInstance } from 'mongodb-instance-model'; -import type { DataService } from '@mongodb-js/compass-connections/provider'; -import type { ConnectionInfo } from '@mongodb-js/connection-info'; -import { setIsPerformanceTabSupported } from '../modules/is-performance-tab-supported'; -import type { MongoServerError } from 'mongodb'; +import type { ConnectionsManager } from '@mongodb-js/compass-connections/provider'; import type { LoggerAndTelemetry } from '@mongodb-js/compass-logging/provider'; +import { + type MongoDBInstancesManager, + MongoDBInstancesManagerEvents, +} from '@mongodb-js/compass-app-stores/provider'; export function createSidebarStore( { globalAppRegistry, - instance, - dataService, - connectionInfo, - logger: { log, mongoLogId }, + connectionsManager, + instancesManager, + logger, }: { globalAppRegistry: AppRegistry; - instance: MongoDBInstance; - dataService: DataService; - connectionInfo: ConnectionInfo | null | undefined; + connectionsManager: ConnectionsManager; + instancesManager: MongoDBInstancesManager; logger: LoggerAndTelemetry; }, - { on, cleanup, addCleanup }: ActivateHelpers + { on, cleanup }: ActivateHelpers ) { const store = createStore( reducer, - applyMiddleware(thunk.withExtraArgument({ globalAppRegistry })) + applyMiddleware( + thunk.withExtraArgument({ + globalAppRegistry, + connectionsManager, + instancesManager, + logger, + }) + ) ); - const onInstanceChange = throttle( - () => { - store.dispatch( - changeInstance({ - status: instance.status, - refreshingStatus: instance.refreshingStatus, - databasesStatus: instance.databasesStatus, - csfleMode: instance.csfleMode, - build: { - isEnterprise: instance.build.isEnterprise, - version: instance.build.version, - }, - dataLake: { - isDataLake: instance.dataLake.isDataLake, - version: instance.dataLake.version, - }, - genuineMongoDB: { - dbType: instance.genuineMongoDB.dbType, - isGenuine: instance.genuineMongoDB.isGenuine, - }, - topologyDescription: { - servers: instance.topologyDescription.servers, - setName: instance.topologyDescription.setName, - type: instance.topologyDescription.type, - }, - isWritable: instance.isWritable, - env: instance.env, - isAtlas: instance.isAtlas, - isLocalAtlas: instance.isLocalAtlas, - }) - ); - }, - 300, - { leading: true, trailing: true } - ); - - addCleanup(() => { - onInstanceChange.cancel(); - }); - - function getDatabaseInfo(db: Database) { - return { - _id: db._id, - name: db.name, - collectionsStatus: db.collectionsStatus, - collectionsLength: db.collectionsLength, - }; - } - - function getCollectionInfo(coll: Database['collections'][number]) { - return { - _id: coll._id, - name: coll.name, - type: coll.type, - sourceName: coll.sourceName, - pipeline: coll.pipeline, - }; + const instances = instancesManager.listMongoDBInstances(); + for (const [connectionId, instance] of instances) { + store.dispatch(setupInstance(connectionId, instance)); } - const onDatabasesChange = throttle( - () => { - const dbs = instance.databases.map((db) => { - return { - ...getDatabaseInfo(db), - collections: db.collections.map((coll) => { - return getCollectionInfo(coll); - }), - }; - }); - - store.dispatch(changeDatabases(dbs)); - }, - 300, - { leading: true, trailing: true } - ); - - addCleanup(() => { - onDatabasesChange.cancel(); - }); - - store.dispatch(setDataService(dataService)); - if (connectionInfo) store.dispatch(changeConnectionInfo(connectionInfo)); - const connectionOptions = dataService.getConnectionOptions(); - store.dispatch(changeConnectionOptions(connectionOptions)); // stores ssh tunnel status - - onInstanceChange(); - onDatabasesChange(); - - on(instance, 'change:status', onInstanceChange); - on(instance, 'change:refreshingStatus', onInstanceChange); - on(instance, 'change:databasesStatus', onInstanceChange); - on(instance, 'change:csfleMode', onInstanceChange); - on(instance, 'change:topologyDescription', onInstanceChange); - on(instance, 'change:isWritable', onInstanceChange); - on(instance, 'change:env', onInstanceChange); - - on(instance, 'change:databasesStatus', onDatabasesChange); - on(instance, 'add:databases', onDatabasesChange); - on(instance, 'remove:databases', onDatabasesChange); - on(instance, 'change:databases', onDatabasesChange); - on(instance, 'change:databases.collectionsStatus', onDatabasesChange); - - on(instance, 'add:collections', onDatabasesChange); - on(instance, 'remove:collections', onDatabasesChange); - on(instance, 'change:collections._id', onDatabasesChange); - on(instance, 'change:collections.status', onDatabasesChange); - - store.dispatch( - toggleIsGenuineMongoDBVisible(!instance.genuineMongoDB.isGenuine) - ); - on( - instance, - 'change:genuineMongoDB.isGenuine', - (_model: unknown, isGenuine: boolean) => { - store.dispatch(toggleIsGenuineMongoDBVisible(!isGenuine)); - } + instancesManager, + MongoDBInstancesManagerEvents.InstanceCreated, + (connectionId, instance) => + store.dispatch(setupInstance(connectionId, instance)) ); - // Checking if "Performance" tab is supported by running commands required for - // the "Performance" tab to function - void Promise.all([dataService.currentOp(), dataService.top()]).then( - () => { - store.dispatch(setIsPerformanceTabSupported(true)); - }, - (err) => { - log.info( - mongoLogId(1_001_000_278), - 'Sidebar', - 'Performance tab requied commands failed', - { error: (err as Error).message } - ); - // Only disable performance tab if encountered Atlas error - const isSupported = - (err as MongoServerError).codeName === 'AtlasError' ? false : true; - store.dispatch(setIsPerformanceTabSupported(isSupported)); - } + on( + instancesManager, + MongoDBInstancesManagerEvents.InstanceRemoved, + (connectionId) => store.dispatch(closeInstance(connectionId)) ); return { diff --git a/packages/compass-test-server/.eslintrc.js b/packages/compass-test-server/.eslintrc.js index e4cf824b6ac..9c3ab95632f 100644 --- a/packages/compass-test-server/.eslintrc.js +++ b/packages/compass-test-server/.eslintrc.js @@ -1,3 +1,4 @@ +'use strict'; module.exports = { root: true, extends: ['@mongodb-js/eslint-config-compass'], diff --git a/packages/compass-test-server/.mocharc.js b/packages/compass-test-server/.mocharc.js index 7e473d17b76..e7eaccd61fa 100644 --- a/packages/compass-test-server/.mocharc.js +++ b/packages/compass-test-server/.mocharc.js @@ -1 +1,2 @@ +'use strict'; module.exports = require('@mongodb-js/mocha-config-compass'); diff --git a/packages/compass-test-server/package.json b/packages/compass-test-server/package.json index 2ed6ab7ac91..e2116b09715 100644 --- a/packages/compass-test-server/package.json +++ b/packages/compass-test-server/package.json @@ -13,7 +13,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "0.1.13", + "version": "0.1.15", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -53,10 +53,10 @@ "mongodb-runner": "^5.4.4" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/mocha": "^9.0.0", "@types/sinon-chai": "^3.2.5", "depcheck": "^1.4.1", diff --git a/packages/compass-user-data/.eslintrc.js b/packages/compass-user-data/.eslintrc.js index e4cf824b6ac..9c3ab95632f 100644 --- a/packages/compass-user-data/.eslintrc.js +++ b/packages/compass-user-data/.eslintrc.js @@ -1,3 +1,4 @@ +'use strict'; module.exports = { root: true, extends: ['@mongodb-js/eslint-config-compass'], diff --git a/packages/compass-user-data/.mocharc.js b/packages/compass-user-data/.mocharc.js index 7e473d17b76..e7eaccd61fa 100644 --- a/packages/compass-user-data/.mocharc.js +++ b/packages/compass-user-data/.mocharc.js @@ -1 +1,2 @@ +'use strict'; module.exports = require('@mongodb-js/mocha-config-compass'); diff --git a/packages/compass-user-data/package.json b/packages/compass-user-data/package.json index 0fbefc0ff69..0b642814e55 100644 --- a/packages/compass-user-data/package.json +++ b/packages/compass-user-data/package.json @@ -12,7 +12,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "0.1.17", + "version": "0.1.19", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -49,16 +49,16 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "dependencies": { - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-utils": "^0.6.1", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-utils": "^0.6.3", "write-file-atomic": "^5.0.1", "zod": "^3.22.3" }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/sinon-chai": "^3.2.5", diff --git a/packages/compass-utils/.eslintrc.js b/packages/compass-utils/.eslintrc.js index e4cf824b6ac..9c3ab95632f 100644 --- a/packages/compass-utils/.eslintrc.js +++ b/packages/compass-utils/.eslintrc.js @@ -1,3 +1,4 @@ +'use strict'; module.exports = { root: true, extends: ['@mongodb-js/eslint-config-compass'], diff --git a/packages/compass-utils/.mocharc.js b/packages/compass-utils/.mocharc.js index 7e473d17b76..e7eaccd61fa 100644 --- a/packages/compass-utils/.mocharc.js +++ b/packages/compass-utils/.mocharc.js @@ -1 +1,2 @@ +'use strict'; module.exports = require('@mongodb-js/mocha-config-compass'); diff --git a/packages/compass-utils/package.json b/packages/compass-utils/package.json index 5177c4e1996..9bd92efbadc 100644 --- a/packages/compass-utils/package.json +++ b/packages/compass-utils/package.json @@ -13,7 +13,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "0.6.1", + "version": "0.6.3", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -50,10 +50,10 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "devDependencies": { - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/sinon-chai": "^3.2.5", @@ -69,6 +69,6 @@ }, "dependencies": { "@electron/remote": "^2.1.2", - "electron": "^28.2.10" + "electron": "^29.3.1" } } diff --git a/packages/compass-web/.depcheckrc b/packages/compass-web/.depcheckrc index 9c46b1934ab..0c28bd6220f 100644 --- a/packages/compass-web/.depcheckrc +++ b/packages/compass-web/.depcheckrc @@ -8,14 +8,17 @@ ignores: - '@types/chai-dom' - '@types/react' - '@types/react-dom' +# Used in electron-proxy through @ts-check, but depcheckrc can't detect it + - '@types/express-http-proxy' # Used in webpack config as polyfills, depcheck can't detect that because of `/` # at the end of require - 'buffer' - 'events' - 'process' - 'util' -# Already a dependency of mongodb-data-service -> devtools-connect, only -# referenced in webpack for weird polyfilling reasons (see config) - - 'resolve-mongodb-srv' +# Not explicitly used, but helpful to have them in devDeps to make sure +# compass-web is on the same version as other package in the monorepo + - 'mongodb' + - 'bson' ignore-patterns: - 'dist' diff --git a/packages/compass-web/.eslintrc.js b/packages/compass-web/.eslintrc.js index e4cf824b6ac..9c3ab95632f 100644 --- a/packages/compass-web/.eslintrc.js +++ b/packages/compass-web/.eslintrc.js @@ -1,3 +1,4 @@ +'use strict'; module.exports = { root: true, extends: ['@mongodb-js/eslint-config-compass'], diff --git a/packages/compass-web/.mocharc.js b/packages/compass-web/.mocharc.js index a7e53abc444..61091edba73 100644 --- a/packages/compass-web/.mocharc.js +++ b/packages/compass-web/.mocharc.js @@ -1 +1,2 @@ +'use strict'; module.exports = require('@mongodb-js/mocha-config-compass/compass-plugin'); diff --git a/packages/compass-web/package.json b/packages/compass-web/package.json index 55544ebf87e..46c93cac76c 100644 --- a/packages/compass-web/package.json +++ b/packages/compass-web/package.json @@ -14,7 +14,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "0.2.7", + "version": "0.4.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -53,66 +53,76 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "peerDependencies": { - "bson": "^6.2.0", - "mongodb": "^6.5.0", "react": "^17.0.2", "react-dom": "^17.0.2" }, "devDependencies": { - "@gribnoysup/mongodb-browser": "^1.3.0", - "@mongodb-js/atlas-service": "^0.15.1", - "@mongodb-js/compass-aggregations": "^9.26.1", - "@mongodb-js/compass-app-stores": "^7.10.1", - "@mongodb-js/compass-collection": "^4.23.1", - "@mongodb-js/compass-components": "^1.22.1", - "@mongodb-js/compass-connections": "^1.25.1", - "@mongodb-js/compass-crud": "^13.24.1", - "@mongodb-js/compass-databases-collections": "^1.23.1", - "@mongodb-js/compass-explain-plan": "^6.24.1", - "@mongodb-js/compass-export-to-language": "^9.0.1", - "@mongodb-js/compass-field-store": "^9.0.19", - "@mongodb-js/compass-generative-ai": "^0.8.1", - "@mongodb-js/compass-indexes": "^5.23.1", - "@mongodb-js/compass-logging": "^1.2.14", - "@mongodb-js/compass-query-bar": "^8.25.1", - "@mongodb-js/compass-schema": "^6.25.1", - "@mongodb-js/compass-schema-validation": "^6.24.1", - "@mongodb-js/compass-sidebar": "^5.24.1", - "@mongodb-js/compass-workspaces": "^0.5.1", - "@mongodb-js/connection-storage": "^0.8.1", - "@mongodb-js/eslint-config-compass": "^1.0.17", - "@mongodb-js/mocha-config-compass": "^1.3.7", - "@mongodb-js/prettier-config-compass": "^1.0.1", - "@mongodb-js/tsconfig-compass": "^1.0.3", - "@mongodb-js/webpack-config-compass": "^1.3.5", + "@mongodb-js/atlas-service": "^0.17.0", + "@mongodb-js/compass-aggregations": "^9.28.0", + "@mongodb-js/compass-app-stores": "^7.12.0", + "@mongodb-js/compass-collection": "^4.25.0", + "@mongodb-js/compass-components": "^1.24.0", + "@mongodb-js/compass-connections": "^1.27.0", + "@mongodb-js/compass-crud": "^13.26.0", + "@mongodb-js/compass-databases-collections": "^1.25.0", + "@mongodb-js/compass-explain-plan": "^6.26.0", + "@mongodb-js/compass-export-to-language": "^9.2.0", + "@mongodb-js/compass-field-store": "^9.2.0", + "@mongodb-js/compass-generative-ai": "^0.10.0", + "@mongodb-js/compass-indexes": "^5.25.0", + "@mongodb-js/compass-logging": "^1.2.16", + "@mongodb-js/compass-query-bar": "^8.27.0", + "@mongodb-js/compass-schema": "^6.27.0", + "@mongodb-js/compass-schema-validation": "^6.26.0", + "@mongodb-js/compass-sidebar": "^5.26.0", + "@mongodb-js/compass-workspaces": "^0.7.0", + "@mongodb-js/connection-storage": "^0.10.0", + "@mongodb-js/eslint-config-compass": "^1.1.1", + "@mongodb-js/mocha-config-compass": "^1.3.9", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/tsconfig-compass": "^1.0.4", + "@mongodb-js/webpack-config-compass": "^1.3.7", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", "@types/chai": "^4.2.21", "@types/chai-dom": "^0.0.10", + "@types/express-http-proxy": "^1.6.6", "@types/mocha": "^9.0.0", "@types/react": "^17.0.5", "@types/react-dom": "^17.0.10", "@types/sinon-chai": "^3.2.5", + "browser-process-hrtime": "^1.0.0", + "bson": "^6.2.0", "buffer": "^6.0.3", "chai": "^4.3.6", - "compass-preferences-model": "^2.18.1", + "compass-preferences-model": "^2.20.0", + "crypto-browserify": "^3.12.0", "debug": "^4.2.0", "depcheck": "^1.4.1", + "dns-query": "^0.11.2", + "electron": "^29.3.1", "eslint": "^7.25.0", "events": "^3.3.0", - "hadron-app-registry": "^9.1.8", + "express": "^4.19.2", + "express-http-proxy": "^2.0.0", + "hadron-app-registry": "^9.1.10", + "is-ip": "^5.0.1", "mocha": "^10.2.0", + "mongodb": "^6.5.0", "mongodb-connection-string-url": "^2.6.0", - "mongodb-data-service": "^22.18.1", + "mongodb-data-service": "^22.19.1", "mongodb-log-writer": "^1.3.0", "nyc": "^15.1.0", + "os-browserify": "^0.3.0", "path-browserify": "^1.0.1", "prettier": "^2.7.1", "process": "^0.11.10", "readable-stream": "^4.5.0", "sinon": "^17.0.1", + "timers-browserify": "^2.0.12", "util": "^0.12.5", "vm-browserify": "^1.1.2", - "whatwg-url": "^13.0.0" + "whatwg-url": "^13.0.0", + "ws": "^8.16.0" } } diff --git a/packages/compass-web/polyfills/@mongodb-js/connection-form/index.ts b/packages/compass-web/polyfills/@mongodb-js/connection-form/index.ts new file mode 100644 index 00000000000..2c7ac3f00b3 --- /dev/null +++ b/packages/compass-web/polyfills/@mongodb-js/connection-form/index.ts @@ -0,0 +1,4 @@ +function ConnectionForm() { + return null; +} +export default ConnectionForm; diff --git a/packages/compass-web/polyfills/crypto/index.ts b/packages/compass-web/polyfills/crypto/index.ts deleted file mode 100644 index df3b1efc117..00000000000 --- a/packages/compass-web/polyfills/crypto/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* global crypto */ -export function randomBytes( - n: number, - cb: (err: any | null, res?: Buffer) => void -): Buffer | void { - const vals = crypto.getRandomValues(new Uint32Array(n)); - if (cb) { - cb(null, Buffer.from(vals)); - return; - } - return Buffer.from(vals); -} -export default { randomBytes }; diff --git a/packages/compass-web/polyfills/dns/index.ts b/packages/compass-web/polyfills/dns/index.ts new file mode 100644 index 00000000000..7151958c6c2 --- /dev/null +++ b/packages/compass-web/polyfills/dns/index.ts @@ -0,0 +1,51 @@ +import { query, lookupTxt } from 'dns-query'; +import { promisify } from 'util'; + +const dohEndpoints = process.env.COMPASS_WEB_DOH_ENDPOINT ?? [ + 'dns.google', + 'dns.cloudflare.com', +]; + +export function resolveSrv( + hostname: string, + cb: (err: null | any, answers?: any[]) => void +) { + query( + { question: { type: 'SRV', name: hostname } }, + { endpoints: dohEndpoints } + ).then(({ answers }) => { + const res = + answers?.flatMap((answer) => { + if (answer.type === 'SRV') { + return { + ...answer.data, + name: answer.data.target, + }; + } + return []; + }) ?? []; + cb(null, res); + }, cb); +} +export function resolveTxt( + hostname: string, + cb: (err: null | any, answers?: string[][]) => void +) { + lookupTxt(hostname, { endpoints: dohEndpoints }).then(({ entries }) => { + const res = entries.map((entry) => { + return [entry.data]; + }); + cb(null, res); + }, cb); +} + +export const promises = { + resolveSrv: promisify(resolveSrv), + resolveTxt: promisify(resolveTxt), +}; + +export default { + resolveSrv, + resolveTxt, + promises, +}; diff --git a/packages/compass-web/polyfills/fs/index.ts b/packages/compass-web/polyfills/fs/index.ts index 4c6c7ce18f6..145b9b8afeb 100644 --- a/packages/compass-web/polyfills/fs/index.ts +++ b/packages/compass-web/polyfills/fs/index.ts @@ -2,6 +2,12 @@ export const promises = { chmod() { return Promise.resolve(); }, + access() { + return Promise.reject(new Error('Not supported in browser environment')); + }, + readFile() { + return Promise.reject(new Error('Not supported in browser environment')); + }, }; export function rmSync() { // noop diff --git a/packages/compass-web/polyfills/fs/promises/index.ts b/packages/compass-web/polyfills/fs/promises/index.ts new file mode 100644 index 00000000000..6f0722f8aa1 --- /dev/null +++ b/packages/compass-web/polyfills/fs/promises/index.ts @@ -0,0 +1,2 @@ +import { promises } from 'fs'; +export default promises; diff --git a/packages/compass-web/polyfills/net/index.ts b/packages/compass-web/polyfills/net/index.ts new file mode 100644 index 00000000000..e75dd2c422f --- /dev/null +++ b/packages/compass-web/polyfills/net/index.ts @@ -0,0 +1,128 @@ +import { ipVersion } from 'is-ip'; +import { Duplex } from 'stream'; + +const PROXY_PORT = process.env.COMPASS_WEB_WS_PROXY_PORT + ? Number(process.env.COMPASS_WEB_WS_PROXY_PORT) + : 1337; + +/** + * net.Socket interface that works over the WebSocket connection. For now, only + * used when running compass-web in a local sandbox, mms has their own + * implementation + */ +class Socket extends Duplex { + private _ws: WebSocket | null = null; + private _setOptions: { + setKeepAlive?: { enabled?: boolean; initialDelay?: number }; + setTimeout?: { timeout?: number }; + setNoDelay?: { noDelay?: boolean }; + } = {}; + constructor() { + super(); + } + connect(options: { host: string; port: number; tls?: boolean }) { + this._ws = new WebSocket(`ws://localhost:${PROXY_PORT}`); + this._ws.binaryType = 'arraybuffer'; + this._ws.addEventListener( + 'open', + () => { + const connectMsg = JSON.stringify({ + connectOptions: options, + setOptions: this._setOptions, + }); + setTimeout(() => { + this._ws?.send(connectMsg); + }); + }, + { once: true } + ); + this._ws.addEventListener('close', () => { + this.emit('close'); + }); + this._ws.addEventListener('error', () => { + this.emit('error', 'WebSocket connection was closed due to an error'); + }); + this._ws.addEventListener( + 'message', + ({ data }: MessageEvent) => { + if (typeof data === 'string') { + try { + const { evt, error } = JSON.parse(data) as { + evt: string; + error?: Error; + }; + setTimeout(() => { + this.emit( + evt, + evt === 'error' ? Object.assign(new Error(), error) : undefined + ); + }); + } catch (err) { + // eslint-disable-next-line no-console + console.error('error parsing proxy message "%s":', data, err); + } + } else { + setTimeout(() => { + this.emit('data', Buffer.from(data)); + }); + } + } + ); + return this; + } + _read() { + // noop + } + _write(chunk: ArrayBufferLike, _encoding: BufferEncoding, cb: () => void) { + this._ws?.send(chunk); + setTimeout(() => { + cb(); + }); + } + destroy() { + this._ws?.close(); + return this; + } + end(fn?: () => void) { + if (this._ws?.readyState === this._ws?.CLOSED) { + setTimeout(() => { + fn?.(); + }); + return this; + } + this._ws?.addEventListener( + 'close', + () => { + fn?.(); + }, + { once: true } + ); + this._ws?.close(); + return this; + } + setKeepAlive(enabled = false, initialDelay = 0) { + this._setOptions.setKeepAlive = { enabled, initialDelay }; + return this; + } + setTimeout(timeout: number, cb?: () => void) { + this._setOptions.setTimeout = { timeout }; + if (cb) { + this.once('timeout', cb); + } + return this; + } + setNoDelay(noDelay = true) { + this._setOptions.setNoDelay = { noDelay }; + return this; + } +} + +export { isIPv4, isIPv6 } from 'is-ip'; +export const isIP = (input: string) => ipVersion(input) ?? 0; +export const createConnection = (options: { host: string; port: number }) => { + const socket = new Socket(); + setTimeout(() => { + socket.connect(options); + }); + return socket; +}; diff --git a/packages/compass-web/polyfills/process/index.ts b/packages/compass-web/polyfills/process/index.ts new file mode 100644 index 00000000000..985779aa49d --- /dev/null +++ b/packages/compass-web/polyfills/process/index.ts @@ -0,0 +1,8 @@ +import process from 'process/browser'; +import hrtime from 'browser-process-hrtime'; +(process as any).hrtime ??= hrtime; +// eslint-disable-next-line no-console +(process as any).emitWarning ??= console.warn; +(process as any).platform = 'Unknown'; +(process as any).arch = 'Unknown'; +export { process }; diff --git a/packages/compass-web/polyfills/throwError/index.ts b/packages/compass-web/polyfills/throwError/index.ts new file mode 100644 index 00000000000..c86f32efbf7 --- /dev/null +++ b/packages/compass-web/polyfills/throwError/index.ts @@ -0,0 +1 @@ +throw new Error('Module not supported in the browser environment'); diff --git a/packages/compass-web/polyfills/tls/index.ts b/packages/compass-web/polyfills/tls/index.ts new file mode 100644 index 00000000000..f738ba06b66 --- /dev/null +++ b/packages/compass-web/polyfills/tls/index.ts @@ -0,0 +1,15 @@ +/** + * This polyfill that is using net.createConnection directly is only used when + * running compass-web in a local sandbox. This establishes a connection to a + * simple passthrough Node.js proxy through a websocket. See + * polyfills/net/index.ts and scripts/ws-proxy.js for more info + */ +import type { SocketConnectOpts } from 'net'; +import { createConnection } from 'net'; + +export const connect = (options: { host: string; port: number }) => { + return createConnection({ + ...options, + tls: true, + } as unknown as SocketConnectOpts); +}; diff --git a/packages/compass-web/polyfills/zlib/index.ts b/packages/compass-web/polyfills/zlib/index.ts new file mode 100644 index 00000000000..32f7ec26ae8 --- /dev/null +++ b/packages/compass-web/polyfills/zlib/index.ts @@ -0,0 +1,7 @@ +export function inflate() { + // noop +} +export function deflate() { + // noop +} +export default { inflate, deflate }; diff --git a/packages/compass-web/sandbox/atlas-cluster-connections.tsx b/packages/compass-web/sandbox/atlas-cluster-connections.tsx new file mode 100644 index 00000000000..fa461af0c8c --- /dev/null +++ b/packages/compass-web/sandbox/atlas-cluster-connections.tsx @@ -0,0 +1,149 @@ +import { Banner, Button, Label } from '@mongodb-js/compass-components'; +import type { ConnectionInfo } from '@mongodb-js/connection-storage/renderer'; +import React, { useCallback, useState } from 'react'; +import { ConnectionsList } from './connections-list'; + +type NdsCluster = { + uniqueId: string; + name: string; + srvAddress: string; +}; + +type SignInStatus = 'initial' | 'fetching' | 'updating' | 'success' | 'error'; + +type AtlasClusterConnectionsListReturnValue = ( + | { signInStatus: 'initial'; signInError: null; groupId: null } + | { + signInStatus: 'fetching' | 'updating'; + signInError: string | null; + groupId: string | null; + } + | { signInStatus: 'success'; signInError: null; groupId: string } + | { signInStatus: 'error'; signInError: string; groupId: null } +) & { + connections: ConnectionInfo[]; + signIn(this: void): Promise; +}; + +export function useAtlasClusterConnectionsList(): AtlasClusterConnectionsListReturnValue { + const [signInStatus, setSignInStatus] = useState('initial'); + const [signInError, setSignInError] = useState(null); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [groupId, setGroupId] = useState(null); + const [connections, setConnections] = useState([]); + + const signIn = useCallback(async () => { + try { + setSignInStatus((status) => { + return status === 'initial' ? 'fetching' : 'updating'; + }); + const { groupId } = await fetch('/authenticate', { method: 'POST' }).then( + (res) => { + return res.json() as Promise<{ groupId: string }>; + } + ); + setGroupId(groupId); + const clusters = await fetch( + `/cloud-mongodb-com/nds/clusters/${groupId}` + ).then((res) => { + return res.json() as Promise; + }); + setConnections( + clusters.map((cluster: NdsCluster): ConnectionInfo => { + return { + id: cluster.uniqueId, + connectionOptions: { + connectionString: `mongodb+srv://:@${cluster.srvAddress}`, + }, + atlasMetadata: { + orgId: '', + projectId: groupId, + clusterId: cluster.uniqueId, + clusterName: cluster.name, + clusterType: 'replicaSet', + regionalBaseUrl: '', + }, + }; + }) + ); + setSignInStatus('success'); + } catch (err) { + setSignInError((err as Error).message); + setSignInStatus('error'); + } + }, []); + + // This is all pretty useless to check, but typescript will complain otherwise + if (signInStatus === 'initial') { + return { + signIn, + signInStatus, + signInError: null, + groupId: null, + connections, + }; + } + + if (signInStatus === 'fetching' || signInStatus === 'updating') { + return { signIn, signInStatus, signInError, groupId, connections }; + } + + if (signInStatus === 'error' && signInError) { + return { signIn, signInStatus, signInError, groupId: null, connections }; + } + + if (signInStatus === 'success' && groupId) { + return { signIn, signInStatus, signInError: null, groupId, connections }; + } + + throw new Error('Weird state, ask for help in Compass dev channel'); +} + +export function AtlasClusterConnectionsList({ + connections, + onConnectionClick, + onConnectionDoubleClick, + signInStatus, + signInError, + onSignInClick, +}: { + connections: ConnectionInfo[]; + onConnectionClick(connectionInfo: ConnectionInfo): void; + onConnectionDoubleClick(connectionInfo: ConnectionInfo): void; + signInStatus: string; + signInError: string | null; + onSignInClick(): void; +}) { + if (signInStatus === 'initial' || signInStatus === 'fetching') { + return ( + + ); + } + + if (signInStatus === 'error' && signInError) { + return {signInError}; + } + + return ( +
    + + { + return connectionInfo.atlasMetadata?.clusterName; + }} + > +
    + ); +} diff --git a/packages/compass-web/sandbox/connections-list.tsx b/packages/compass-web/sandbox/connections-list.tsx new file mode 100644 index 00000000000..9916bd2c2f7 --- /dev/null +++ b/packages/compass-web/sandbox/connections-list.tsx @@ -0,0 +1,77 @@ +import React from 'react'; +import { + css, + spacing, + palette, + KeylineCard, +} from '@mongodb-js/compass-components'; +import type { ConnectionInfo } from '@mongodb-js/connection-storage/main'; + +const historyListStyles = css({ + all: 'unset', + marginTop: spacing[1], + display: 'grid', + gridTemplateColumns: '100%', + gridAutoRows: 'auto', + gap: spacing[2], +}); + +const historyListItemStyles = css({ + listStyle: 'none', + paddingTop: spacing[2], + paddingBottom: spacing[2], + paddingLeft: spacing[2], + paddingRight: spacing[2], +}); + +const historyItemButtonStyles = css({ + all: 'unset', + display: 'block', + width: '100%', + cursor: 'pointer', + color: palette.black, + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', +}); + +export function ConnectionsList({ + connections, + onConnectionClick, + onConnectionDoubleClick, + renderConnectionLabel, + ...props +}: { + connections: ConnectionInfo[]; + onConnectionClick(connectionInfo: ConnectionInfo): void; + onConnectionDoubleClick(connectionInfo: ConnectionInfo): void; + renderConnectionLabel(connectionInfo: ConnectionInfo): React.ReactNode; +} & React.HTMLProps) { + return ( +
      + {connections.map((connectionInfo) => { + return ( + + + + ); + })} +
    + ); +} diff --git a/packages/compass-web/sandbox/index.tsx b/packages/compass-web/sandbox/index.tsx index faba0b613cf..12400cda61b 100644 --- a/packages/compass-web/sandbox/index.tsx +++ b/packages/compass-web/sandbox/index.tsx @@ -1,35 +1,34 @@ -import React, { useCallback, useState, useRef } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import ReactDOM from 'react-dom'; import { TextArea, Button, resetGlobalCSS, Card, - KeylineCard, css, spacing, - palette, - Label, - ErrorBoundary, Banner, Body, + SpinLoaderWithLabel, } from '@mongodb-js/compass-components'; import { redactConnectionString, ConnectionString, } from 'mongodb-connection-string-url'; -import createDebug from 'debug'; import { CompassWeb } from '../src/index'; -import type { - OpenWorkspaceOptions, - CollectionSubtab, -} from '@mongodb-js/compass-workspaces'; import { LoggerAndTelemetryProvider } from '@mongodb-js/compass-logging/provider'; -import { mongoLogId } from '@mongodb-js/compass-logging'; -import type { LoggerAndTelemetry } from '@mongodb-js/compass-logging'; -import type { MongoLogWriter } from 'mongodb-log-writer'; import type { ConnectionInfo } from '@mongodb-js/connection-storage/renderer'; +import { sandboxLogger } from './sandbox-logger'; +import { useWorkspaceTabRouter } from './use-workspace-tab-router'; +import { + StoredConnectionsList, + useConnectionsHistory, +} from './stored-connections-history'; +import { + AtlasClusterConnectionsList, + useAtlasClusterConnectionsList, +} from './atlas-cluster-connections'; const sandboxContainerStyles = css({ width: '100%', @@ -57,75 +56,51 @@ const connectionFormStyles = css({ gap: spacing[3], }); -const historyListStyles = css({ - all: 'unset', - marginTop: spacing[1], - display: 'grid', - gridTemplateColumns: '100%', - gridAutoRows: 'auto', - gap: spacing[2], -}); - -const historyListItemStyles = css({ - listStyle: 'none', - paddingTop: spacing[2], - paddingBottom: spacing[2], - paddingLeft: spacing[2], - paddingRight: spacing[2], -}); +resetGlobalCSS(); -const historyItemButtonStyles = css({ - all: 'unset', - display: 'block', +const loadingContainerStyles = css({ width: '100%', - cursor: 'pointer', - color: palette.black, - whiteSpace: 'nowrap', - overflow: 'hidden', - textOverflow: 'ellipsis', + height: '100%', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', }); -resetGlobalCSS(); +const spinnerStyles = css({ + flex: 'none', +}); -function getHistory(): ConnectionInfo[] { - try { - const b64Str = localStorage.getItem('CONNECTIONS_HISTORY'); - if (!b64Str) { - return []; +function LoadingScreen({ connectionString }: { connectionString?: string }) { + const host = useMemo(() => { + try { + const url = new ConnectionString(connectionString ?? ''); + return url.hosts[0]; + } catch { + return 'cluster'; } - const binStr = window.atob(b64Str); - const bytes = Uint8Array.from(binStr, (v) => v.codePointAt(0) ?? 0); - const str = new TextDecoder().decode(bytes); - return JSON.parse(str); - } catch (err) { - return []; - } -} + }, [connectionString]); -function getCollectionSubTab(subTab: string): CollectionSubtab { - switch (subTab.toLowerCase()) { - case 'schema': - return 'Schema'; - case 'indexes': - return 'Indexes'; - case 'aggregations': - return 'Aggregations'; - case 'validation': - return 'Validation'; - default: - return 'Documents'; - } + return ( +
    + +
    + ); } -function saveHistory(history: any) { - try { - const bytes = new TextEncoder().encode(JSON.stringify(history)); - const binStr = String.fromCodePoint(...bytes); - const b64Str = window.btoa(binStr); - localStorage.setItem('CONNECTIONS_HISTORY', b64Str); - } catch (err) { - // noop - } +const errorContainerStyles = css({ + width: '100%', + padding: spacing[3], +}); + +function ErrorScreen({ error }: { error: string }) { + return ( +
    + {error} +
    + ); } function validateConnectionString(str: string) { @@ -137,40 +112,23 @@ function validateConnectionString(str: string) { } } -const tracking: { event: string; properties: any }[] = []; -const logging: { name: string; component: string; args: any[] }[] = []; - -(globalThis as any).tracking = tracking; -(globalThis as any).logging = logging; - const App = () => { - const [initialTab] = useState(() => { - const [, tab, namespace = '', subTab] = window.location.pathname.split('/'); - if (tab === 'databases') { - return { type: 'Databases' }; - } - if (tab === 'collections' && namespace) { - return { type: 'Collections', namespace }; - } - if (tab === 'collection' && namespace) { - return { - type: 'Collection', - namespace, - initialSubtab: getCollectionSubTab(subTab), - }; - } - return { type: 'Databases' }; - }); - const [connectionsHistory, setConnectionsHistory] = useState< - ConnectionInfo[] - >(() => { - return getHistory(); - }); + const [connectionsHistory, updateConnectionsHistory] = + useConnectionsHistory(); + const { + signIn, + signInStatus, + signInError, + connections: atlasConnections, + } = useAtlasClusterConnectionsList(); const [focused, setFocused] = useState(false); const [connectionString, setConnectionString] = useState(''); const [connectionInfo, setConnectionInfo] = useState( null ); + const [initialCurrentTab, updateCurrentTab] = useWorkspaceTabRouter( + connectionInfo?.id + ); const [openCompassWeb, setOpenCompassWeb] = useState(false); const [ connectionStringValidationResult, @@ -181,115 +139,81 @@ const App = () => { setOpenCompassWeb(false); }; - const canSubmit = - connectionStringValidationResult === null && connectionString !== ''; + const canConnect = + connectionStringValidationResult === null && + connectionString !== '' && + connectionInfo; const onChangeConnectionString = useCallback((str: string) => { - setConnectionStringValidationResult(validateConnectionString(str)); setConnectionString(str); - }, []); - - const onConnectClick = useCallback(() => { - setOpenCompassWeb(true); - setConnectionsHistory((history) => { - const info = history.find( - (info) => info.connectionOptions.connectionString === connectionString - ); - if (info) { - setConnectionInfo(info); - return history; - } - - const newInfo: ConnectionInfo = { - id: Math.random().toString(36).slice(2), + setConnectionStringValidationResult(validateConnectionString(str)); + setConnectionInfo((connectionInfo) => { + return { + ...connectionInfo, + id: connectionInfo?.id ?? str, connectionOptions: { - connectionString, + ...connectionInfo?.connectionOptions, + connectionString: str, }, }; - setConnectionInfo(newInfo); - history.unshift(newInfo); - if (history.length > 10) { - history.pop(); - } - saveHistory(history); - return [...history]; }); - }, [connectionString]); - - const onConnectionItemDoubleClick = useCallback( - (info: ConnectionInfo) => { - setConnectionString(info.connectionOptions.connectionString); - onConnectClick(); - }, - [onConnectClick] - ); - - const loggerProvider = useRef({ - createLogger: (component = 'SANDBOX-LOGGER'): LoggerAndTelemetry => { - const logger = (name: 'debug' | 'info' | 'warn' | 'error' | 'fatal') => { - return (...args: any[]) => { - logging.push({ name, component, args }); - }; - }; + }, []); - const track = (event: string, properties: any) => { - tracking.push({ event, properties }); - }; + const onSelectFromList = useCallback((connectionInfo: ConnectionInfo) => { + const str = connectionInfo.connectionOptions.connectionString; + setConnectionString(str); + setConnectionStringValidationResult(validateConnectionString(str)); + setConnectionInfo(connectionInfo); + }, []); - const debug = createDebug(`mongodb-compass:${component.toLowerCase()}`); + const onConnect = useCallback(async () => { + if (canConnect) { + if (connectionInfo.atlasMetadata) { + await signIn(); + } - return { - log: { - component, - get unbound() { - return this as unknown as MongoLogWriter; - }, - write: () => true, - debug: logger('debug'), - info: logger('info'), - warn: logger('warn'), - error: logger('error'), - fatal: logger('fatal'), - }, - debug, - track, - mongoLogId, - }; - }, - }); + updateConnectionsHistory(connectionInfo); + setOpenCompassWeb(true); + } + }, [canConnect, connectionInfo, signIn, updateConnectionsHistory]); if (openCompassWeb && connectionInfo) { + const isAtlasConnection = !!connectionInfo.atlasMetadata; + return ( - - - { - let newPath: string; - switch (tab?.type) { - case 'Databases': - newPath = '/databases'; - break; - case 'Collections': - newPath = `/collections/${tab.namespace}`; - break; - case 'Collection': - newPath = `/collection/${ - tab.namespace - }/${tab.subTab.toLowerCase()}`; - break; - default: - newPath = '/'; - } - if (newPath) { - window.history.replaceState(null, '', newPath); - } - }} - > - + + { + return Promise.resolve(connectionInfo); + }} + initialWorkspaceTabs={ + initialCurrentTab ? [initialCurrentTab] : undefined + } + onActiveWorkspaceTabChange={updateCurrentTab} + initialPreferences={{ + enablePerformanceAdvisorBanner: isAtlasConnection, + enableAtlasSearchIndexes: !isAtlasConnection, + maximumNumberOfActiveConnections: isAtlasConnection ? 1 : 10, + }} + stackedElementsZIndex={5} + renderConnecting={(connectionInfo) => { + return ( + + ); + }} + renderError={(_connectionInfo, err) => { + return ( + + ); + }} + > ); @@ -303,7 +227,7 @@ const App = () => { className={connectionFormStyles} onSubmit={(evt) => { evt.preventDefault(); - onConnectClick(); + void onConnect(); }} >