diff --git a/configs/webpack-config-compass/src/index.ts b/configs/webpack-config-compass/src/index.ts index c5774688797..3a7ae22794d 100644 --- a/configs/webpack-config-compass/src/index.ts +++ b/configs/webpack-config-compass/src/index.ts @@ -276,10 +276,10 @@ export function createElectronRendererConfig( writeToDisk: true, }, client: { - overlay: { - errors: true, - warnings: false, - }, + overlay: + process.env.DISABLE_DEVSERVER_OVERLAY === 'true' + ? false + : { warnings: false, errors: true, runtimeErrors: true }, }, https: false, hot: opts.hot, diff --git a/package-lock.json b/package-lock.json index 38e96a97eae..5c15f701a10 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8598,6 +8598,118 @@ "url": "https://opencollective.com/node-fetch" } }, + "node_modules/@mongodb-js/diagramming": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@mongodb-js/diagramming/-/diagramming-1.3.5.tgz", + "integrity": "sha512-s4mrOUj10Fpole5V5zUSgj6GqD+CYsxSQuTo3oi2gu8zGR6GHlKrYilIj3mC6mO7UHktyPeHEE8ARmF8aHUcQA==", + "license": "MIT", + "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", + "@leafygreen-ui/icon": "^13.2.0", + "@leafygreen-ui/leafygreen-provider": "^5.0.0", + "@leafygreen-ui/palette": "^5.0.0", + "@leafygreen-ui/tokens": "^3.0.0", + "@leafygreen-ui/typography": "^20.1.4", + "@xyflow/react": "12.5.1", + "d3-path": "^3.1.0", + "elkjs": "^0.10.0", + "react": "17.0.2", + "react-dom": "17.0.2" + } + }, + "node_modules/@mongodb-js/diagramming/node_modules/@leafygreen-ui/emotion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/emotion/-/emotion-5.0.0.tgz", + "integrity": "sha512-MOfouBCmHuFa6UObhUl03CUFqXvD2PP+nI7CLk0ny8/UKOLgAX4N+JuuSX606u+Efxk4lI2m3FZiyCrfi6oeFQ==", + "license": "Apache-2.0", + "dependencies": { + "@emotion/css": "^11.1.3", + "@emotion/server": "^11.4.0" + } + }, + "node_modules/@mongodb-js/diagramming/node_modules/@leafygreen-ui/hooks": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/hooks/-/hooks-9.1.1.tgz", + "integrity": "sha512-WVAu5NgFo5eALb7Z2E8v2mEaUtiGXsOrOGX8fLHSU75Xs343SGWllwxqqGnhf+bbUNlSXBAbprHAD3/Yn4QcyQ==", + "license": "Apache-2.0", + "dependencies": { + "@leafygreen-ui/lib": "^15.2.0", + "@leafygreen-ui/tokens": "^3.1.2", + "lodash": "^4.17.21" + } + }, + "node_modules/@mongodb-js/diagramming/node_modules/@leafygreen-ui/leafygreen-provider": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/leafygreen-provider/-/leafygreen-provider-5.0.2.tgz", + "integrity": "sha512-mLD7ziluM0ZoTlzoauu6AeA3vGVlf9JilUjmWZEcZeRfzJcIyF48PoL7Mj23AqY1k1PNcJHhlK9ALpIzpI33ug==", + "license": "Apache-2.0", + "dependencies": { + "@leafygreen-ui/hooks": "^9.1.1", + "@leafygreen-ui/lib": "^15.2.0", + "react-transition-group": "^4.4.5" + } + }, + "node_modules/@mongodb-js/diagramming/node_modules/@leafygreen-ui/lib": { + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/lib/-/lib-15.2.0.tgz", + "integrity": "sha512-wrVJGaqACcYWE/xPHHJREpRvkoy4Biwim1SUuq0hs/lXf6cEMg7MD9x2fUDJ9v6tQmLiFuwRXbJiXrvVXkz4Lg==", + "license": "Apache-2.0", + "dependencies": { + "lodash": "^4.17.21" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0" + } + }, + "node_modules/@mongodb-js/diagramming/node_modules/@leafygreen-ui/palette": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/palette/-/palette-5.0.0.tgz", + "integrity": "sha512-RHQy165X7lKMlNU+2BkvGCNuo8fP3bS5NVOJ6thSKingoksYrz1a6SNAzuHDIkww+njf0GaKiXYT64og2Xm4Fw==", + "license": "Apache-2.0" + }, + "node_modules/@mongodb-js/diagramming/node_modules/@leafygreen-ui/tokens": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/tokens/-/tokens-3.2.1.tgz", + "integrity": "sha512-FJwgN9zRFa/1Lrw3teuBdTF+Fi/IAdpaNuUUEiVIissHK4Py8Dsc6HJhWKBOocBj5dEw78cRDgnqSVFvU6EjMg==", + "license": "Apache-2.0", + "dependencies": { + "@leafygreen-ui/emotion": "^5.0.0", + "@leafygreen-ui/lib": "^15.2.0", + "@leafygreen-ui/palette": "^5.0.0", + "polished": "^4.2.2" + } + }, + "node_modules/@mongodb-js/diagramming/node_modules/@xyflow/react": { + "version": "12.5.1", + "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.5.1.tgz", + "integrity": "sha512-jMKQVqGwCz0x6pUyvxTIuCMbyehfua7CfEEWDj29zQSHigQpCy0/5d8aOmZrqK4cwur/pVHLQomT6Rm10gXfHg==", + "license": "MIT", + "dependencies": { + "@xyflow/system": "0.0.53", + "classcat": "^5.0.3", + "zustand": "^4.4.0" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@mongodb-js/diagramming/node_modules/@xyflow/system": { + "version": "0.0.53", + "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.53.tgz", + "integrity": "sha512-QTWieiTtvNYyQAz1fxpzgtUGXNpnhfh6vvZa7dFWpWS2KOz6bEHODo/DTK3s07lDu0Bq0Db5lx/5M5mNjb9VDQ==", + "license": "MIT", + "dependencies": { + "@types/d3-drag": "^3.0.7", + "@types/d3-selection": "^3.0.10", + "@types/d3-transition": "^3.0.8", + "@types/d3-zoom": "^3.0.8", + "d3-drag": "^3.0.0", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0" + } + }, "node_modules/@mongodb-js/dl-center": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@mongodb-js/dl-center/-/dl-center-1.3.0.tgz", @@ -15510,38 +15622,6 @@ "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" }, - "node_modules/@xyflow/react": { - "version": "12.8.1", - "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.8.1.tgz", - "integrity": "sha512-t5Rame4Gc/540VcOZd28yFe9Xd8lyjKUX+VTiyb1x4ykNXZH5zyDmsu+lj9je2O/jGBVb0pj1Vjcxrxyn+Xk2g==", - "license": "MIT", - "dependencies": { - "@xyflow/system": "0.0.65", - "classcat": "^5.0.3", - "zustand": "^4.4.0" - }, - "peerDependencies": { - "react": ">=17", - "react-dom": ">=17" - } - }, - "node_modules/@xyflow/system": { - "version": "0.0.65", - "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.65.tgz", - "integrity": "sha512-AliQPQeurQMoNlOdySnRoDQl9yDSA/1Lqi47Eo0m98lHcfrTdD9jK75H0tiGj+0qRC10SKNUXyMkT0KL0opg4g==", - "license": "MIT", - "dependencies": { - "@types/d3-drag": "^3.0.7", - "@types/d3-interpolate": "^3.0.4", - "@types/d3-selection": "^3.0.10", - "@types/d3-transition": "^3.0.8", - "@types/d3-zoom": "^3.0.8", - "d3-drag": "^3.0.0", - "d3-interpolate": "^3.0.1", - "d3-selection": "^3.0.0", - "d3-zoom": "^3.0.0" - } - }, "node_modules/@yarnpkg/lockfile": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", @@ -45296,7 +45376,7 @@ "@mongodb-js/compass-user-data": "^0.9.0", "@mongodb-js/compass-utils": "^0.9.10", "@mongodb-js/compass-workspaces": "^0.51.0", - "@mongodb-js/diagramming": "^1.3.3", + "@mongodb-js/diagramming": "^1.3.5", "bson": "^6.10.4", "compass-preferences-model": "^2.50.0", "html-to-image": "1.11.11", @@ -45331,112 +45411,6 @@ "xvfb-maybe": "^0.2.1" } }, - "packages/compass-data-modeling/node_modules/@leafygreen-ui/emotion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/emotion/-/emotion-5.0.0.tgz", - "integrity": "sha512-MOfouBCmHuFa6UObhUl03CUFqXvD2PP+nI7CLk0ny8/UKOLgAX4N+JuuSX606u+Efxk4lI2m3FZiyCrfi6oeFQ==", - "license": "Apache-2.0", - "dependencies": { - "@emotion/css": "^11.1.3", - "@emotion/server": "^11.4.0" - } - }, - "packages/compass-data-modeling/node_modules/@leafygreen-ui/hooks": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/hooks/-/hooks-9.1.1.tgz", - "integrity": "sha512-WVAu5NgFo5eALb7Z2E8v2mEaUtiGXsOrOGX8fLHSU75Xs343SGWllwxqqGnhf+bbUNlSXBAbprHAD3/Yn4QcyQ==", - "license": "Apache-2.0", - "dependencies": { - "@leafygreen-ui/lib": "^15.2.0", - "@leafygreen-ui/tokens": "^3.1.2", - "lodash": "^4.17.21" - } - }, - "packages/compass-data-modeling/node_modules/@leafygreen-ui/hooks/node_modules/@leafygreen-ui/lib": { - "version": "15.2.0", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/lib/-/lib-15.2.0.tgz", - "integrity": "sha512-wrVJGaqACcYWE/xPHHJREpRvkoy4Biwim1SUuq0hs/lXf6cEMg7MD9x2fUDJ9v6tQmLiFuwRXbJiXrvVXkz4Lg==", - "license": "Apache-2.0", - "dependencies": { - "lodash": "^4.17.21" - }, - "peerDependencies": { - "react": "^17.0.0 || ^18.0.0" - } - }, - "packages/compass-data-modeling/node_modules/@leafygreen-ui/leafygreen-provider": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/leafygreen-provider/-/leafygreen-provider-5.0.2.tgz", - "integrity": "sha512-mLD7ziluM0ZoTlzoauu6AeA3vGVlf9JilUjmWZEcZeRfzJcIyF48PoL7Mj23AqY1k1PNcJHhlK9ALpIzpI33ug==", - "license": "Apache-2.0", - "dependencies": { - "@leafygreen-ui/hooks": "^9.1.1", - "@leafygreen-ui/lib": "^15.2.0", - "react-transition-group": "^4.4.5" - } - }, - "packages/compass-data-modeling/node_modules/@leafygreen-ui/leafygreen-provider/node_modules/@leafygreen-ui/lib": { - "version": "15.2.0", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/lib/-/lib-15.2.0.tgz", - "integrity": "sha512-wrVJGaqACcYWE/xPHHJREpRvkoy4Biwim1SUuq0hs/lXf6cEMg7MD9x2fUDJ9v6tQmLiFuwRXbJiXrvVXkz4Lg==", - "license": "Apache-2.0", - "dependencies": { - "lodash": "^4.17.21" - }, - "peerDependencies": { - "react": "^17.0.0 || ^18.0.0" - } - }, - "packages/compass-data-modeling/node_modules/@leafygreen-ui/palette": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/palette/-/palette-5.0.0.tgz", - "integrity": "sha512-RHQy165X7lKMlNU+2BkvGCNuo8fP3bS5NVOJ6thSKingoksYrz1a6SNAzuHDIkww+njf0GaKiXYT64og2Xm4Fw==", - "license": "Apache-2.0" - }, - "packages/compass-data-modeling/node_modules/@leafygreen-ui/tokens": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/tokens/-/tokens-3.2.1.tgz", - "integrity": "sha512-FJwgN9zRFa/1Lrw3teuBdTF+Fi/IAdpaNuUUEiVIissHK4Py8Dsc6HJhWKBOocBj5dEw78cRDgnqSVFvU6EjMg==", - "license": "Apache-2.0", - "dependencies": { - "@leafygreen-ui/emotion": "^5.0.0", - "@leafygreen-ui/lib": "^15.2.0", - "@leafygreen-ui/palette": "^5.0.0", - "polished": "^4.2.2" - } - }, - "packages/compass-data-modeling/node_modules/@leafygreen-ui/tokens/node_modules/@leafygreen-ui/lib": { - "version": "15.2.0", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/lib/-/lib-15.2.0.tgz", - "integrity": "sha512-wrVJGaqACcYWE/xPHHJREpRvkoy4Biwim1SUuq0hs/lXf6cEMg7MD9x2fUDJ9v6tQmLiFuwRXbJiXrvVXkz4Lg==", - "license": "Apache-2.0", - "dependencies": { - "lodash": "^4.17.21" - }, - "peerDependencies": { - "react": "^17.0.0 || ^18.0.0" - } - }, - "packages/compass-data-modeling/node_modules/@mongodb-js/diagramming": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@mongodb-js/diagramming/-/diagramming-1.3.3.tgz", - "integrity": "sha512-wE5MXYgLPDdB9MQnPuggCP8A4sHKR0goRSiFxR7yQyaUT1VSuugKv/PObF6CarOpAq9+WYyl1I1vU9ea4D5Tng==", - "license": "MIT", - "dependencies": { - "@emotion/react": "^11.14.0", - "@emotion/styled": "^11.14.0", - "@leafygreen-ui/icon": "^13.2.0", - "@leafygreen-ui/leafygreen-provider": "^5.0.0", - "@leafygreen-ui/palette": "^5.0.0", - "@leafygreen-ui/tokens": "^3.0.0", - "@leafygreen-ui/typography": "^20.1.4", - "@xyflow/react": "^12.5.1", - "d3-path": "^3.1.0", - "elkjs": "^0.10.0", - "react": "17.0.2", - "react-dom": "17.0.2" - } - }, "packages/compass-data-modeling/node_modules/@sinonjs/commons": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", @@ -57938,7 +57912,7 @@ "@mongodb-js/compass-user-data": "^0.9.0", "@mongodb-js/compass-utils": "^0.9.10", "@mongodb-js/compass-workspaces": "^0.51.0", - "@mongodb-js/diagramming": "^1.3.3", + "@mongodb-js/diagramming": "^1.3.5", "@mongodb-js/eslint-config-compass": "^1.4.6", "@mongodb-js/mocha-config-compass": "^1.7.0", "@mongodb-js/prettier-config-compass": "^1.2.8", @@ -57971,100 +57945,6 @@ "xvfb-maybe": "^0.2.1" }, "dependencies": { - "@leafygreen-ui/emotion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/emotion/-/emotion-5.0.0.tgz", - "integrity": "sha512-MOfouBCmHuFa6UObhUl03CUFqXvD2PP+nI7CLk0ny8/UKOLgAX4N+JuuSX606u+Efxk4lI2m3FZiyCrfi6oeFQ==", - "requires": { - "@emotion/css": "^11.1.3", - "@emotion/server": "^11.4.0" - } - }, - "@leafygreen-ui/hooks": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/hooks/-/hooks-9.1.1.tgz", - "integrity": "sha512-WVAu5NgFo5eALb7Z2E8v2mEaUtiGXsOrOGX8fLHSU75Xs343SGWllwxqqGnhf+bbUNlSXBAbprHAD3/Yn4QcyQ==", - "requires": { - "@leafygreen-ui/lib": "^15.2.0", - "@leafygreen-ui/tokens": "^3.1.2", - "lodash": "^4.17.21" - }, - "dependencies": { - "@leafygreen-ui/lib": { - "version": "15.2.0", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/lib/-/lib-15.2.0.tgz", - "integrity": "sha512-wrVJGaqACcYWE/xPHHJREpRvkoy4Biwim1SUuq0hs/lXf6cEMg7MD9x2fUDJ9v6tQmLiFuwRXbJiXrvVXkz4Lg==", - "requires": { - "lodash": "^4.17.21" - } - } - } - }, - "@leafygreen-ui/leafygreen-provider": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/leafygreen-provider/-/leafygreen-provider-5.0.2.tgz", - "integrity": "sha512-mLD7ziluM0ZoTlzoauu6AeA3vGVlf9JilUjmWZEcZeRfzJcIyF48PoL7Mj23AqY1k1PNcJHhlK9ALpIzpI33ug==", - "requires": { - "@leafygreen-ui/hooks": "^9.1.1", - "@leafygreen-ui/lib": "^15.2.0", - "react-transition-group": "^4.4.5" - }, - "dependencies": { - "@leafygreen-ui/lib": { - "version": "15.2.0", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/lib/-/lib-15.2.0.tgz", - "integrity": "sha512-wrVJGaqACcYWE/xPHHJREpRvkoy4Biwim1SUuq0hs/lXf6cEMg7MD9x2fUDJ9v6tQmLiFuwRXbJiXrvVXkz4Lg==", - "requires": { - "lodash": "^4.17.21" - } - } - } - }, - "@leafygreen-ui/palette": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/palette/-/palette-5.0.0.tgz", - "integrity": "sha512-RHQy165X7lKMlNU+2BkvGCNuo8fP3bS5NVOJ6thSKingoksYrz1a6SNAzuHDIkww+njf0GaKiXYT64og2Xm4Fw==" - }, - "@leafygreen-ui/tokens": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/tokens/-/tokens-3.2.1.tgz", - "integrity": "sha512-FJwgN9zRFa/1Lrw3teuBdTF+Fi/IAdpaNuUUEiVIissHK4Py8Dsc6HJhWKBOocBj5dEw78cRDgnqSVFvU6EjMg==", - "requires": { - "@leafygreen-ui/emotion": "^5.0.0", - "@leafygreen-ui/lib": "^15.2.0", - "@leafygreen-ui/palette": "^5.0.0", - "polished": "^4.2.2" - }, - "dependencies": { - "@leafygreen-ui/lib": { - "version": "15.2.0", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/lib/-/lib-15.2.0.tgz", - "integrity": "sha512-wrVJGaqACcYWE/xPHHJREpRvkoy4Biwim1SUuq0hs/lXf6cEMg7MD9x2fUDJ9v6tQmLiFuwRXbJiXrvVXkz4Lg==", - "requires": { - "lodash": "^4.17.21" - } - } - } - }, - "@mongodb-js/diagramming": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@mongodb-js/diagramming/-/diagramming-1.3.3.tgz", - "integrity": "sha512-wE5MXYgLPDdB9MQnPuggCP8A4sHKR0goRSiFxR7yQyaUT1VSuugKv/PObF6CarOpAq9+WYyl1I1vU9ea4D5Tng==", - "requires": { - "@emotion/react": "^11.14.0", - "@emotion/styled": "^11.14.0", - "@leafygreen-ui/icon": "^13.2.0", - "@leafygreen-ui/leafygreen-provider": "^5.0.0", - "@leafygreen-ui/palette": "^5.0.0", - "@leafygreen-ui/tokens": "^3.0.0", - "@leafygreen-ui/typography": "^20.1.4", - "@xyflow/react": "^12.5.1", - "d3-path": "^3.1.0", - "elkjs": "^0.10.0", - "react": "17.0.2", - "react-dom": "17.0.2" - } - }, "@sinonjs/commons": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", @@ -61001,6 +60881,104 @@ } } }, + "@mongodb-js/diagramming": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@mongodb-js/diagramming/-/diagramming-1.3.5.tgz", + "integrity": "sha512-s4mrOUj10Fpole5V5zUSgj6GqD+CYsxSQuTo3oi2gu8zGR6GHlKrYilIj3mC6mO7UHktyPeHEE8ARmF8aHUcQA==", + "requires": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", + "@leafygreen-ui/icon": "^13.2.0", + "@leafygreen-ui/leafygreen-provider": "^5.0.0", + "@leafygreen-ui/palette": "^5.0.0", + "@leafygreen-ui/tokens": "^3.0.0", + "@leafygreen-ui/typography": "^20.1.4", + "@xyflow/react": "12.5.1", + "d3-path": "^3.1.0", + "elkjs": "^0.10.0", + "react": "^17.0.2", + "react-dom": "^17.0.2" + }, + "dependencies": { + "@leafygreen-ui/emotion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/emotion/-/emotion-5.0.0.tgz", + "integrity": "sha512-MOfouBCmHuFa6UObhUl03CUFqXvD2PP+nI7CLk0ny8/UKOLgAX4N+JuuSX606u+Efxk4lI2m3FZiyCrfi6oeFQ==", + "requires": { + "@emotion/css": "^11.1.3", + "@emotion/server": "^11.4.0" + } + }, + "@leafygreen-ui/hooks": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/hooks/-/hooks-9.1.1.tgz", + "integrity": "sha512-WVAu5NgFo5eALb7Z2E8v2mEaUtiGXsOrOGX8fLHSU75Xs343SGWllwxqqGnhf+bbUNlSXBAbprHAD3/Yn4QcyQ==", + "requires": { + "@leafygreen-ui/lib": "^15.2.0", + "@leafygreen-ui/tokens": "^3.1.2", + "lodash": "^4.17.21" + } + }, + "@leafygreen-ui/leafygreen-provider": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/leafygreen-provider/-/leafygreen-provider-5.0.2.tgz", + "integrity": "sha512-mLD7ziluM0ZoTlzoauu6AeA3vGVlf9JilUjmWZEcZeRfzJcIyF48PoL7Mj23AqY1k1PNcJHhlK9ALpIzpI33ug==", + "requires": { + "@leafygreen-ui/hooks": "^9.1.1", + "@leafygreen-ui/lib": "^15.2.0", + "react-transition-group": "^4.4.5" + } + }, + "@leafygreen-ui/lib": { + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/lib/-/lib-15.2.0.tgz", + "integrity": "sha512-wrVJGaqACcYWE/xPHHJREpRvkoy4Biwim1SUuq0hs/lXf6cEMg7MD9x2fUDJ9v6tQmLiFuwRXbJiXrvVXkz4Lg==", + "requires": { + "lodash": "^4.17.21" + } + }, + "@leafygreen-ui/palette": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/palette/-/palette-5.0.0.tgz", + "integrity": "sha512-RHQy165X7lKMlNU+2BkvGCNuo8fP3bS5NVOJ6thSKingoksYrz1a6SNAzuHDIkww+njf0GaKiXYT64og2Xm4Fw==" + }, + "@leafygreen-ui/tokens": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/tokens/-/tokens-3.2.1.tgz", + "integrity": "sha512-FJwgN9zRFa/1Lrw3teuBdTF+Fi/IAdpaNuUUEiVIissHK4Py8Dsc6HJhWKBOocBj5dEw78cRDgnqSVFvU6EjMg==", + "requires": { + "@leafygreen-ui/emotion": "^5.0.0", + "@leafygreen-ui/lib": "^15.2.0", + "@leafygreen-ui/palette": "^5.0.0", + "polished": "^4.2.2" + } + }, + "@xyflow/react": { + "version": "12.5.1", + "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.5.1.tgz", + "integrity": "sha512-jMKQVqGwCz0x6pUyvxTIuCMbyehfua7CfEEWDj29zQSHigQpCy0/5d8aOmZrqK4cwur/pVHLQomT6Rm10gXfHg==", + "requires": { + "@xyflow/system": "0.0.53", + "classcat": "^5.0.3", + "zustand": "^4.4.0" + } + }, + "@xyflow/system": { + "version": "0.0.53", + "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.53.tgz", + "integrity": "sha512-QTWieiTtvNYyQAz1fxpzgtUGXNpnhfh6vvZa7dFWpWS2KOz6bEHODo/DTK3s07lDu0Bq0Db5lx/5M5mNjb9VDQ==", + "requires": { + "@types/d3-drag": "^3.0.7", + "@types/d3-selection": "^3.0.10", + "@types/d3-transition": "^3.0.8", + "@types/d3-zoom": "^3.0.8", + "d3-drag": "^3.0.0", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0" + } + } + } + }, "@mongodb-js/dl-center": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@mongodb-js/dl-center/-/dl-center-1.3.0.tgz", @@ -67038,32 +67016,6 @@ "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" }, - "@xyflow/react": { - "version": "12.8.1", - "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.8.1.tgz", - "integrity": "sha512-t5Rame4Gc/540VcOZd28yFe9Xd8lyjKUX+VTiyb1x4ykNXZH5zyDmsu+lj9je2O/jGBVb0pj1Vjcxrxyn+Xk2g==", - "requires": { - "@xyflow/system": "0.0.65", - "classcat": "^5.0.3", - "zustand": "^4.4.0" - } - }, - "@xyflow/system": { - "version": "0.0.65", - "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.65.tgz", - "integrity": "sha512-AliQPQeurQMoNlOdySnRoDQl9yDSA/1Lqi47Eo0m98lHcfrTdD9jK75H0tiGj+0qRC10SKNUXyMkT0KL0opg4g==", - "requires": { - "@types/d3-drag": "^3.0.7", - "@types/d3-interpolate": "^3.0.4", - "@types/d3-selection": "^3.0.10", - "@types/d3-transition": "^3.0.8", - "@types/d3-zoom": "^3.0.8", - "d3-drag": "^3.0.0", - "d3-interpolate": "^3.0.1", - "d3-selection": "^3.0.0", - "d3-zoom": "^3.0.0" - } - }, "@yarnpkg/lockfile": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", diff --git a/packages/compass-components/src/components/drawer-portal.spec.tsx b/packages/compass-components/src/components/drawer-portal.spec.tsx index 130f51a772a..7c4c4587daf 100644 --- a/packages/compass-components/src/components/drawer-portal.spec.tsx +++ b/packages/compass-components/src/components/drawer-portal.spec.tsx @@ -9,6 +9,8 @@ import { DrawerContentProvider, DrawerSection, DrawerAnchor, + useDrawerState, + useDrawerActions, } from './drawer-portal'; import { expect } from 'chai'; @@ -162,4 +164,68 @@ describe('DrawerSection', function () { screen.getByTestId('lg-drawer') ).to.have.attribute('aria-hidden', 'true'); }); + + it('can control drawer state via the hooks', async function () { + const ControlElement = () => { + const { isDrawerOpen } = useDrawerState(); + const { openDrawer, closeDrawer } = useDrawerActions(); + return ( +
+ + {isDrawerOpen ? 'open' : 'closed'} + + +
+ ); + }; + render( + + + + + This is an unrelated section + + + This is the controlled section + + + + ); + + // Drawer is closed by default + expect(screen.getByTestId('drawer-state')).to.have.text('closed'); + + // Open the drawer + userEvent.click(screen.getByRole('button', { name: 'Hook Open drawer' })); + await waitFor(() => { + expect(screen.getByTestId('drawer-state')).to.have.text('open'); + expect(screen.getByText('This is the controlled section')).to.be.visible; + }); + + // Close the drawer + userEvent.click(screen.getByRole('button', { name: 'Hook Close drawer' })); + await waitFor(() => { + expect(screen.getByTestId('drawer-state')).to.have.text('closed'); + expect(screen.queryByText('This is the controlled section')).not.to.exist; + }); + }); }); diff --git a/packages/compass-components/src/components/drawer-portal.tsx b/packages/compass-components/src/components/drawer-portal.tsx index 0ebc27ccc32..1254ca7fef7 100644 --- a/packages/compass-components/src/components/drawer-portal.tsx +++ b/packages/compass-components/src/components/drawer-portal.tsx @@ -32,6 +32,10 @@ type DrawerSectionProps = Omit & { order?: number; }; +type DrawerOpenStateContextValue = boolean; + +type DrawerSetOpenStateContextValue = (isOpen: boolean) => void; + type DrawerActionsContextValue = { current: { openDrawer: (id: string) => void; @@ -43,6 +47,12 @@ type DrawerActionsContextValue = { const DrawerStateContext = React.createContext([]); +const DrawerOpenStateContext = + React.createContext(false); + +const DrawerSetOpenStateContext = + React.createContext(() => {}); + const DrawerActionsContext = React.createContext({ current: { openDrawer: () => undefined, @@ -89,6 +99,8 @@ export const DrawerContentProvider: React.FunctionComponent = ({ children, }) => { const [drawerState, setDrawerState] = useState([]); + const [drawerOpenState, setDrawerOpenState] = + useState(false); const drawerActions = useRef({ openDrawer: () => undefined, closeDrawer: () => undefined, @@ -116,9 +128,13 @@ export const DrawerContentProvider: React.FunctionComponent = ({ return ( - - {children} - + + + + {children} + + + ); }; @@ -126,8 +142,12 @@ export const DrawerContentProvider: React.FunctionComponent = ({ const DrawerContextGrabber: React.FunctionComponent = ({ children }) => { const drawerToolbarContext = useDrawerToolbarContext(); const actions = useContext(DrawerActionsContext); + const openStateSetter = useContext(DrawerSetOpenStateContext); actions.current.openDrawer = drawerToolbarContext.openDrawer; actions.current.closeDrawer = drawerToolbarContext.closeDrawer; + useEffect(() => { + openStateSetter(drawerToolbarContext.isDrawerOpen); + }, [drawerToolbarContext.isDrawerOpen, openStateSetter]); return <>{children}; }; @@ -321,3 +341,14 @@ export function useDrawerActions() { }); return stableActions.current; } + +export const useDrawerState = () => { + const drawerOpenStateContext = useContext(DrawerOpenStateContext); + const drawerState = useContext(DrawerStateContext); + return { + isDrawerOpen: + drawerOpenStateContext && + // the second check is a workaround, because LG doesn't set isDrawerOpen to false when it's empty + drawerState.length > 0, + }; +}; diff --git a/packages/compass-data-modeling/package.json b/packages/compass-data-modeling/package.json index cb48e27c06d..cd39b541866 100644 --- a/packages/compass-data-modeling/package.json +++ b/packages/compass-data-modeling/package.json @@ -63,7 +63,7 @@ "@mongodb-js/compass-user-data": "^0.9.0", "@mongodb-js/compass-utils": "^0.9.10", "@mongodb-js/compass-workspaces": "^0.51.0", - "@mongodb-js/diagramming": "^1.3.3", + "@mongodb-js/diagramming": "^1.3.5", "bson": "^6.10.4", "compass-preferences-model": "^2.50.0", "html-to-image": "1.11.11", diff --git a/packages/compass-data-modeling/src/components/diagram-editor-toolbar.spec.tsx b/packages/compass-data-modeling/src/components/diagram-editor-toolbar.spec.tsx index 36c9b2f4734..03b1fa42633 100644 --- a/packages/compass-data-modeling/src/components/diagram-editor-toolbar.spec.tsx +++ b/packages/compass-data-modeling/src/components/diagram-editor-toolbar.spec.tsx @@ -17,6 +17,7 @@ function renderDiagramEditorToolbar( onRedoClick={() => {}} onExportClick={() => {}} onRelationshipDrawingToggle={() => {}} + onAddCollectionClick={() => {}} {...props} /> ); @@ -65,6 +66,16 @@ describe('DiagramEditorToolbar', function () { }); }); + context('add collection button', function () { + it('starts adding collection', function () { + const addCollectionSpy = sinon.spy(); + renderDiagramEditorToolbar({ onAddCollectionClick: addCollectionSpy }); + const addButton = screen.getByRole('button', { name: 'Add Collection' }); + userEvent.click(addButton); + expect(addCollectionSpy).to.have.been.calledOnce; + }); + }); + context('add relationship button', function () { it('renders it active if isInRelationshipDrawingMode is true', function () { renderDiagramEditorToolbar({ isInRelationshipDrawingMode: true }); diff --git a/packages/compass-data-modeling/src/components/diagram-editor-toolbar.tsx b/packages/compass-data-modeling/src/components/diagram-editor-toolbar.tsx index 00ed0fcc1ba..0a0d5cfef2f 100644 --- a/packages/compass-data-modeling/src/components/diagram-editor-toolbar.tsx +++ b/packages/compass-data-modeling/src/components/diagram-editor-toolbar.tsx @@ -15,7 +15,7 @@ import { transparentize, Tooltip, } from '@mongodb-js/compass-components'; - +import AddCollection from './icons/add-collection'; const containerStyles = css({ display: 'flex', justifyContent: 'space-between', @@ -50,6 +50,7 @@ export const DiagramEditorToolbar: React.FunctionComponent<{ onRedoClick: () => void; onExportClick: () => void; onRelationshipDrawingToggle: () => void; + onAddCollectionClick: () => void; }> = ({ step, hasUndo, @@ -58,6 +59,7 @@ export const DiagramEditorToolbar: React.FunctionComponent<{ onRedoClick, onExportClick, onRelationshipDrawingToggle, + onAddCollectionClick, isInRelationshipDrawingMode, }) => { const darkmode = useDarkMode(); @@ -70,6 +72,15 @@ export const DiagramEditorToolbar: React.FunctionComponent<{ data-testid="diagram-editor-toolbar" >
+ + + + + + + + + Drag from one collection to another to create a relationship. - - - - - -
} @@ -240,12 +256,17 @@ const CollectionDrawerContent: React.FunctionComponent< export default connect( (state: DataModelingState, ownProps: { namespace: string }) => { const model = selectCurrentModelFromState(state); + const collection = model.collections.find((collection) => { + return collection.ns === ownProps.namespace; + }); + if (!collection) { + throw new Error('Namespace not found in model: ' + ownProps.namespace); + } return { - note: - model.collections.find((collection) => { - return collection.ns === ownProps.namespace; - })?.note ?? '', - namespaces: model.collections.map((c) => c.ns), + note: collection.note, + namespace: collection.ns, + isDraftCollection: state.diagram?.draftCollection === ownProps.namespace, + collections: model.collections, relationships: model.relationships.filter((r) => { const [local, foreign] = r.relationship; return ( diff --git a/packages/compass-data-modeling/src/components/drawer/diagram-editor-side-panel.spec.tsx b/packages/compass-data-modeling/src/components/drawer/diagram-editor-side-panel.spec.tsx index 3428c9b1f0f..f8ab1e41cb5 100644 --- a/packages/compass-data-modeling/src/components/drawer/diagram-editor-side-panel.spec.tsx +++ b/packages/compass-data-modeling/src/components/drawer/diagram-editor-side-panel.spec.tsx @@ -10,6 +10,7 @@ import { import { DataModelingWorkspaceTab } from '../../index'; import DiagramEditorSidePanel from './diagram-editor-side-panel'; import { + addCollection, openDiagram, selectCollection, selectCurrentModelFromState, @@ -21,7 +22,18 @@ import type { DataModelCollection, Relationship, } from '../../services/data-model-storage'; -import { DrawerAnchor } from '@mongodb-js/compass-components'; +import { DrawerAnchor, getDrawerIds } from '@mongodb-js/compass-components'; + +const drawerTestId = getDrawerIds().root; + +const waitForDrawerToOpen = async () => { + await waitFor(() => { + expect(screen.queryByTestId(drawerTestId)).to.have.attribute( + 'aria-hidden', + 'false' + ); + }); +}; async function comboboxSelectItem( label: string, @@ -66,37 +78,9 @@ describe('DiagramEditorSidePanel', function () { it('should not render if no items are selected', function () { renderDrawer(); - expect(screen.queryByTestId('data-modeling-drawer')).to.eq(null); - }); - - it('should render and edit a collection in collection context drawer when collection is clicked', async function () { - const result = renderDrawer(); - result.plugin.store.dispatch(selectCollection('flights.airlines')); - - await waitFor(() => { - expect(screen.getByTitle('flights.airlines')).to.be.visible; - }); - - const nameInput = screen.getByLabelText('Name'); - expect(nameInput).to.be.visible; - expect(nameInput).to.have.value('airlines'); - - userEvent.click(screen.getByRole('textbox', { name: 'Notes' })); - userEvent.type( - screen.getByRole('textbox', { name: 'Notes' }), - 'Note about the collection' - ); - userEvent.tab(); - - const modifiedCollection = selectCurrentModelFromState( - result.plugin.store.getState() - ).collections.find((coll) => { - return coll.ns === 'flights.airlines'; - }); - - expect(modifiedCollection).to.have.property( - 'note', - 'Note about the collection' + expect(screen.queryByTestId(drawerTestId)).to.have.attribute( + 'aria-hidden', + 'true' ); }); @@ -106,11 +90,10 @@ describe('DiagramEditorSidePanel', function () { selectRelationship('204b1fc0-601f-4d62-bba3-38fade71e049') ); - await waitFor(() => { - expect(screen.getByTitle('countries.name → airports.Country')).to.be - .visible; - }); + await waitForDrawerToOpen(); + expect(screen.getByTitle('countries.name → airports.Country')).to.be + .visible; const localCollectionInput = screen.getByLabelText('Local collection'); expect(localCollectionInput).to.be.visible; expect(localCollectionInput).to.have.value('countries'); @@ -149,9 +132,9 @@ describe('DiagramEditorSidePanel', function () { result.plugin.store.dispatch(selectCollection('flights.airlines')); - await waitFor(() => { - expect(screen.getByLabelText('Name')).to.have.value('airlines'); - }); + await waitForDrawerToOpen(); + + expect(screen.getByLabelText('Name')).to.have.value('airlines'); result.plugin.store.dispatch( selectCollection('flights.airports_coordinates_for_schema') @@ -186,9 +169,9 @@ describe('DiagramEditorSidePanel', function () { const result = renderDrawer(); result.plugin.store.dispatch(selectCollection('flights.countries')); - await waitFor(() => { - expect(screen.getByLabelText('Name')).to.have.value('countries'); - }); + await waitForDrawerToOpen(); + + expect(screen.getByLabelText('Name')).to.have.value('countries'); // Open relationshipt editing form const relationshipItem = screen @@ -249,9 +232,9 @@ describe('DiagramEditorSidePanel', function () { const result = renderDrawer(); result.plugin.store.dispatch(selectCollection('flights.countries')); - await waitFor(() => { - expect(screen.getByLabelText('Name')).to.have.value('countries'); - }); + await waitForDrawerToOpen(); + + expect(screen.getByLabelText('Name')).to.have.value('countries'); // Find the relationhip item const relationshipItem = screen @@ -272,119 +255,184 @@ describe('DiagramEditorSidePanel', function () { }); }); - it('should delete a collection', async function () { - const result = renderDrawer(); - result.plugin.store.dispatch(selectCollection('flights.countries')); + describe('When a collection is selected', function () { + it('should render and edit a collection in collection context drawer when collection is clicked', async function () { + const result = renderDrawer(); + result.plugin.store.dispatch(selectCollection('flights.airlines')); - await waitFor(() => { - expect(screen.getByLabelText('Name')).to.have.value('countries'); - }); + await waitForDrawerToOpen(); - userEvent.click(screen.getByLabelText(/delete collection/i)); + expect(screen.getByTitle('flights.airlines')).to.be.visible; - await waitFor(() => { - expect(screen.queryByText('countries')).not.to.exist; + const nameInput = screen.getByLabelText('Name'); + expect(nameInput).to.be.visible; + expect(nameInput).to.have.value('airlines'); + + userEvent.click(screen.getByRole('textbox', { name: 'Notes' })); + userEvent.type( + screen.getByRole('textbox', { name: 'Notes' }), + 'Note about the collection' + ); + userEvent.tab(); + + const modifiedCollection = selectCurrentModelFromState( + result.plugin.store.getState() + ).collections.find((coll) => { + return coll.ns === 'flights.airlines'; + }); + + expect(modifiedCollection).to.have.property( + 'note', + 'Note about the collection' + ); }); - expect(screen.queryByLabelText('Name')).to.not.exist; - const modifiedCollection = selectCurrentModelFromState( - result.plugin.store.getState() - ).collections.find((coll) => { - return coll.ns === 'flights.countries'; + it('should delete a collection', async function () { + const result = renderDrawer(); + result.plugin.store.dispatch(selectCollection('flights.countries')); + + await waitForDrawerToOpen(); + + expect(screen.getByLabelText('Name')).to.have.value('countries'); + + userEvent.click(screen.getByLabelText(/delete collection/i)); + + await waitFor(() => { + expect(screen.queryByText('countries')).not.to.exist; + }); + expect(screen.queryByLabelText('Name')).to.not.exist; + + const modifiedCollection = selectCurrentModelFromState( + result.plugin.store.getState() + ).collections.find((coll) => { + return coll.ns === 'flights.countries'; + }); + + expect(modifiedCollection).to.be.undefined; }); - expect(modifiedCollection).to.be.undefined; - }); + it('should open and edit a collection name', async function () { + const result = renderDrawer(); + result.plugin.store.dispatch(selectCollection('flights.countries')); - it('should open and edit a collection name', async function () { - const result = renderDrawer(); - result.plugin.store.dispatch(selectCollection('flights.countries')); + await waitForDrawerToOpen(); - await waitFor(() => { expect(screen.getByLabelText('Name')).to.have.value('countries'); + + // Update the name. + userEvent.clear(screen.getByLabelText('Name')); + userEvent.type(screen.getByLabelText('Name'), 'pineapple'); + + // Blur/unfocus the input. + userEvent.click(document.body); + + // Check the name in the model. + const modifiedCollection = selectCurrentModelFromState( + result.plugin.store.getState() + ).collections.find((c: DataModelCollection) => { + return c.ns === 'flights.pineapple'; + }); + + expect(modifiedCollection).to.exist; }); - // Update the name. - userEvent.clear(screen.getByLabelText('Name')); - userEvent.type(screen.getByLabelText('Name'), 'pineapple'); + it('should handle new collection creation', async function () { + const result = renderDrawer(); + result.plugin.store.dispatch(addCollection()); - // Blur/unfocus the input. - userEvent.click(document.body); + await waitForDrawerToOpen(); - // Check the name in the model. - const modifiedCollection = selectCurrentModelFromState( - result.plugin.store.getState() - ).collections.find((c: DataModelCollection) => { - return c.ns === 'flights.pineapple'; + expect(screen.getByLabelText('Name')).to.have.value('new-collection'); + + // The name should be focused + const nameInput = screen.getByLabelText('Name'); + const activeElement = document.activeElement; + expect(activeElement).to.equal(nameInput); + + // Update the name. + userEvent.clear(nameInput); + userEvent.type(nameInput, 'pineapple'); + + // Blur/unfocus the input - now the collection should be names + userEvent.click(document.body); + + // Check the name in the model. + const newCollection = selectCurrentModelFromState( + result.plugin.store.getState() + ).collections.find((c: DataModelCollection) => { + return c.ns === 'flights.pineapple'; + }); + expect(newCollection).to.exist; + + // See the name in the input + expect(screen.getByText('flights.pineapple')).to.be.visible; }); - expect(modifiedCollection).to.exist; - }); + it('should prevent editing to an empty collection name', async function () { + const result = renderDrawer(); + result.plugin.store.dispatch(selectCollection('flights.countries')); - it('should prevent editing to an empty collection name', async function () { - const result = renderDrawer(); - result.plugin.store.dispatch(selectCollection('flights.countries')); + await waitForDrawerToOpen(); - await waitFor(() => { expect(screen.getByLabelText('Name')).to.have.value('countries'); expect(screen.getByLabelText('Name')).to.have.attribute( 'aria-invalid', 'false' ); - }); - userEvent.clear(screen.getByLabelText('Name')); + userEvent.clear(screen.getByLabelText('Name')); - await waitFor(() => { - expect(screen.getByLabelText('Name')).to.have.attribute( - 'aria-invalid', - 'true' - ); - }); + await waitFor(() => { + expect(screen.getByLabelText('Name')).to.have.attribute( + 'aria-invalid', + 'true' + ); + }); - // Blur/unfocus the input. - userEvent.click(document.body); + // Blur/unfocus the input. + userEvent.click(document.body); - const notModifiedCollection = selectCurrentModelFromState( - result.plugin.store.getState() - ).collections.find((c: DataModelCollection) => { - return c.ns === 'flights.countries'; + const notModifiedCollection = selectCurrentModelFromState( + result.plugin.store.getState() + ).collections.find((c: DataModelCollection) => { + return c.ns === 'flights.countries'; + }); + + expect(notModifiedCollection).to.exist; }); - expect(notModifiedCollection).to.exist; - }); + it('should prevent editing to a duplicate collection name', async function () { + const result = renderDrawer(); + result.plugin.store.dispatch(selectCollection('flights.countries')); - it('should prevent editing to a duplicate collection name', async function () { - const result = renderDrawer(); - result.plugin.store.dispatch(selectCollection('flights.countries')); + await waitForDrawerToOpen(); - await waitFor(() => { expect(screen.getByLabelText('Name')).to.have.value('countries'); expect(screen.getByLabelText('Name')).to.have.attribute( 'aria-invalid', 'false' ); - }); - userEvent.clear(screen.getByLabelText('Name')); - userEvent.type(screen.getByLabelText('Name'), 'airlines'); + userEvent.clear(screen.getByLabelText('Name')); + userEvent.type(screen.getByLabelText('Name'), 'airlines'); - await waitFor(() => { - expect(screen.getByLabelText('Name')).to.have.attribute( - 'aria-invalid', - 'true' - ); - }); + await waitFor(() => { + expect(screen.getByLabelText('Name')).to.have.attribute( + 'aria-invalid', + 'true' + ); + }); - // Blur/unfocus the input. - userEvent.click(document.body); + // Blur/unfocus the input. + userEvent.click(document.body); - const notModifiedCollection = selectCurrentModelFromState( - result.plugin.store.getState() - ).collections.find((c: DataModelCollection) => { - return c.ns === 'flights.countries'; - }); + const notModifiedCollection = selectCurrentModelFromState( + result.plugin.store.getState() + ).collections.find((c: DataModelCollection) => { + return c.ns === 'flights.countries'; + }); - expect(notModifiedCollection).to.exist; + expect(notModifiedCollection).to.exist; + }); }); }); diff --git a/packages/compass-data-modeling/src/components/icons/add-collection.tsx b/packages/compass-data-modeling/src/components/icons/add-collection.tsx new file mode 100644 index 00000000000..59ac417150e --- /dev/null +++ b/packages/compass-data-modeling/src/components/icons/add-collection.tsx @@ -0,0 +1,27 @@ +import React, { useMemo } from 'react'; +import { palette, useDarkMode } from '@mongodb-js/compass-components'; + +const AddCollection: React.FunctionComponent = () => { + const darkMode = useDarkMode(); + const strokeColor = useMemo( + () => (darkMode ? palette.white : palette.black), + [darkMode] + ); + + return ( + + + + ); +}; + +export default AddCollection; diff --git a/packages/compass-data-modeling/src/services/data-model-storage.ts b/packages/compass-data-modeling/src/services/data-model-storage.ts index cb2266dff78..1405e5bd6a4 100644 --- a/packages/compass-data-modeling/src/services/data-model-storage.ts +++ b/packages/compass-data-modeling/src/services/data-model-storage.ts @@ -80,6 +80,12 @@ const EditSchemaVariants = z.discriminatedUnion('type', [ ns: z.string(), note: z.string(), }), + z.object({ + type: z.literal('AddCollection'), + ns: z.string(), + position: z.tuple([z.number(), z.number()]), + initialSchema: z.custom(), + }), ]); export const EditSchema = z.intersection(EditSchemaBase, EditSchemaVariants); diff --git a/packages/compass-data-modeling/src/store/diagram.spec.ts b/packages/compass-data-modeling/src/store/diagram.spec.ts index 5f88c90c6e9..0da0c2cf71d 100644 --- a/packages/compass-data-modeling/src/store/diagram.spec.ts +++ b/packages/compass-data-modeling/src/store/diagram.spec.ts @@ -8,6 +8,8 @@ import { redoEdit, undoEdit, selectFieldsForCurrentModel, + addCollection, + renameCollection, } from './diagram'; import type { Edit, @@ -19,14 +21,14 @@ import { UUID } from 'bson'; const model: StaticModel = { collections: [ { - ns: 'collection1', + ns: 'db.collection1', indexes: [], displayPosition: [0, 0], shardKey: {}, jsonSchema: { bsonType: 'object' }, }, { - ns: 'collection2', + ns: 'db.collection2', indexes: [], displayPosition: [1, 1], shardKey: {}, @@ -77,12 +79,12 @@ describe('Data Modeling store', function () { connectionId: 'connection-id', collections: [ { - ns: 'collection1', + ns: 'db.collection1', schema: model.collections[0].jsonSchema, position: { x: 0, y: 0 }, }, { - ns: 'collection2', + ns: 'db.collection2', schema: model.collections[1].jsonSchema, position: { x: 0, y: 0 }, }, @@ -140,7 +142,7 @@ describe('Data Modeling store', function () { model: { collections: [ { - ns: 'collection2', + ns: 'db.collection2', indexes: [], displayPosition: [0, 0], shardKey: {}, @@ -223,6 +225,118 @@ describe('Data Modeling store', function () { expect(diagram.edits).to.deep.equal(loadedDiagram.edits); }); + it('should handle the collection creation flow', function () { + store.dispatch(openDiagram(loadedDiagram)); + + // start creating a new collection + store.dispatch(addCollection()); + + // the new collection is in the diagram + const diagramAtCreation = getCurrentDiagramFromState(store.getState()); + expect(diagramAtCreation.edits).to.have.length(2); + const firstAddCollectionEdit = diagramAtCreation.edits[1] as Extract< + Edit, + { type: 'AddCollection' } + >; + const firstCollectionDraftName = 'db.new-collection'; + expect(firstAddCollectionEdit.type).to.equal('AddCollection'); + expect(firstAddCollectionEdit.ns).to.equal(firstCollectionDraftName); + expect(firstAddCollectionEdit.initialSchema).to.deep.equal({ + bsonType: 'object', + properties: { + _id: { + bsonType: 'objectId', + }, + }, + required: ['_id'], + }); + + // the selection changes to the new collection + const selectedItems = store.getState().diagram?.selectedItems; + expect(selectedItems).to.deep.equal({ + type: 'collection', + id: firstCollectionDraftName, + }); + + // name the new collection + const newCollectionNs = 'db.myCollection'; + store.dispatch( + renameCollection(firstCollectionDraftName, newCollectionNs) + ); + + // now the collection is added to the edit history + const diagramAfterCreation = getCurrentDiagramFromState(store.getState()); + expect(diagramAfterCreation.edits).to.have.length(2); + expect(diagramAfterCreation.edits[0]).to.deep.equal( + loadedDiagram.edits[0] + ); + const addCollectionEdit = diagramAfterCreation.edits[1] as Extract< + Edit, + { type: 'AddCollection' } + >; + expect(addCollectionEdit.type).to.equal('AddCollection'); + expect(addCollectionEdit.ns).to.equal(newCollectionNs); + expect(addCollectionEdit.initialSchema).to.deep.equal({ + bsonType: 'object', + properties: { + _id: { + bsonType: 'objectId', + }, + }, + required: ['_id'], + }); + + // and it is selected + const selectedItemsAfterCreation = + store.getState().diagram?.selectedItems; + expect(selectedItemsAfterCreation).to.deep.equal({ + type: 'collection', + id: newCollectionNs, + }); + }); + + it('should iterate the names for new collections', function () { + store.dispatch(openDiagram(loadedDiagram)); + + // start creating a new collection + store.dispatch(addCollection()); + + // creates the first collection and makes it selected + const firstCollectionDraftName = 'db.new-collection'; + const diagram1 = getCurrentDiagramFromState(store.getState()); + expect(diagram1.edits).to.have.length(2); + const firstAddCollectionEdit = diagram1.edits[1] as Extract< + Edit, + { type: 'AddCollection' } + >; + expect(firstAddCollectionEdit.type).to.equal('AddCollection'); + expect(firstAddCollectionEdit.ns).to.equal(firstCollectionDraftName); + const selectedItems1 = store.getState().diagram?.selectedItems; + expect(selectedItems1).to.deep.equal({ + type: 'collection', + id: firstCollectionDraftName, + }); + + // start creating another new collection + store.dispatch(addCollection()); + + // creates the second collection and makes it selected + const secondCollectionDraftName = 'db.new-collection-1'; + const diagramAtCreation = getCurrentDiagramFromState(store.getState()); + expect(diagramAtCreation.edits).to.have.length(3); + const secondAddCollectionEdit = diagramAtCreation.edits[2] as Extract< + Edit, + { type: 'AddCollection' } + >; + expect(secondAddCollectionEdit.type).to.equal('AddCollection'); + expect(secondAddCollectionEdit.ns).to.equal(secondCollectionDraftName); + const selectedItems2 = store.getState().diagram?.selectedItems; + expect(selectedItems2).to.deep.equal({ + type: 'collection', + id: secondCollectionDraftName, + }); + }); + it('should apply a valid MoveCollection edit', function () { store.dispatch(openDiagram(loadedDiagram)); @@ -307,7 +421,7 @@ describe('Data Modeling store', function () { model: { collections: [ { - ns: 'collection1', + ns: 'db.collection1', indexes: [], displayPosition: [0, 0], shardKey: {}, @@ -328,7 +442,7 @@ describe('Data Modeling store', function () { const selectedFields = selectFieldsForCurrentModel(edits); expect(selectedFields).to.deep.equal({ - collection1: [['field1'], ['field2'], ['field3']], + 'db.collection1': [['field1'], ['field2'], ['field3']], }); }); @@ -341,7 +455,7 @@ describe('Data Modeling store', function () { model: { collections: [ { - ns: 'collection1', + ns: 'db.collection1', indexes: [], displayPosition: [0, 0], shardKey: {}, @@ -417,29 +531,46 @@ describe('Data Modeling store', function () { }, ]; const selectedFields = selectFieldsForCurrentModel(edits); - - expect(selectedFields).to.have.property('collection1'); - expect(selectedFields.collection1).to.deep.include(['prop1']); - expect(selectedFields.collection1).to.deep.include(['prop2']); - expect(selectedFields.collection1).to.deep.include(['prop2', 'prop2A']); - expect(selectedFields.collection1).to.deep.include([ + expect(selectedFields).to.have.property('db.collection1'); + expect(selectedFields['db.collection1']).to.deep.include(['prop1']); + expect(selectedFields['db.collection1']).to.deep.include(['prop2']); + expect(selectedFields['db.collection1']).to.deep.include([ + 'prop2', + 'prop2A', + ]); + expect(selectedFields['db.collection1']).to.deep.include([ 'prop2', 'prop2B', 'prop2B1', ]); - expect(selectedFields.collection1).to.deep.include([ + expect(selectedFields['db.collection1']).to.deep.include([ 'prop2', 'prop2B', 'prop2B2', ]); - expect(selectedFields.collection1).to.deep.include(['prop3']); - expect(selectedFields.collection1).to.deep.include(['prop3', 'prop3A']); - expect(selectedFields.collection1).to.deep.include(['prop4']); - expect(selectedFields.collection1).to.deep.include(['prop4', 'prop4A']); - expect(selectedFields.collection1).to.deep.include(['prop4', 'prop4B']); - expect(selectedFields.collection1).to.deep.include(['prop5']); - expect(selectedFields.collection1).to.deep.include(['prop5', 'prop5A']); - expect(selectedFields.collection1).to.deep.include(['prop5', 'prop5B']); + expect(selectedFields['db.collection1']).to.deep.include(['prop3']); + expect(selectedFields['db.collection1']).to.deep.include([ + 'prop3', + 'prop3A', + ]); + expect(selectedFields['db.collection1']).to.deep.include(['prop4']); + expect(selectedFields['db.collection1']).to.deep.include([ + 'prop4', + 'prop4A', + ]); + expect(selectedFields['db.collection1']).to.deep.include([ + 'prop4', + 'prop4B', + ]); + expect(selectedFields['db.collection1']).to.deep.include(['prop5']); + expect(selectedFields['db.collection1']).to.deep.include([ + 'prop5', + 'prop5A', + ]); + expect(selectedFields['db.collection1']).to.deep.include([ + 'prop5', + 'prop5B', + ]); }); }); }); diff --git a/packages/compass-data-modeling/src/store/diagram.ts b/packages/compass-data-modeling/src/store/diagram.ts index c5ac862d958..ddef3e11782 100644 --- a/packages/compass-data-modeling/src/store/diagram.ts +++ b/packages/compass-data-modeling/src/store/diagram.ts @@ -1,7 +1,11 @@ import type { Reducer } from 'redux'; import { UUID } from 'bson'; import { isAction } from './util'; -import type { EditAction, Relationship } from '../services/data-model-storage'; +import type { + DataModelCollection, + EditAction, + Relationship, +} from '../services/data-model-storage'; import { validateEdit, type Edit, @@ -21,12 +25,18 @@ import { getDiagramName, } from '../services/open-and-download-diagram'; import type { MongoDBJSONSchema } from 'mongodb-schema'; +import { getCoordinatesForNewNode } from '@mongodb-js/diagramming'; +import { collectionToDiagramNode } from '../utils/nodes-and-edges'; +import toNS from 'mongodb-ns'; function isNonEmptyArray(arr: T[]): arr is [T, ...T[]] { return Array.isArray(arr) && arr.length > 0; } -export type SelectedItems = { type: 'collection' | 'relationship'; id: string }; +export type SelectedItems = { + type: 'collection' | 'relationship'; + id: string; +}; export type DiagramState = | (Omit & { @@ -37,6 +47,7 @@ export type DiagramState = }; editErrors?: string[]; selectedItems: SelectedItems | null; + draftCollection?: string; }) | null; // null when no diagram is currently open @@ -179,6 +190,27 @@ export const diagramReducer: Reducer = ( updatedAt: new Date().toISOString(), }; } + if ( + isAction(action, DiagramActionTypes.APPLY_EDIT) && + state.draftCollection && + action.edit.type === 'RenameCollection' + ) { + return { + ...state, + edits: getEditsAfterDraftCollectionNamed( + state.edits, + state.draftCollection, + action.edit.toNS + ), + editErrors: undefined, + updatedAt: new Date().toISOString(), + selectedItems: { + type: 'collection', + id: action.edit.toNS, + }, + draftCollection: undefined, + }; + } if (isAction(action, DiagramActionTypes.APPLY_EDIT)) { return { ...state, @@ -193,6 +225,8 @@ export const diagramReducer: Reducer = ( state.selectedItems, action.edit ), + draftCollection: + action.edit.type === 'AddCollection' ? action.edit.ns : undefined, }; } if (isAction(action, DiagramActionTypes.APPLY_EDIT_FAILED)) { @@ -255,6 +289,43 @@ export const diagramReducer: Reducer = ( return state; }; +/** + * When the collection is created, it gets a draft name + * If the user renames it, we update the addCollection edit + * instead of appending a renameCollection edit, for cleaner history. + * @param edits + * @param draftNamespace + * @param newNamespace + * @returns + */ +const getEditsAfterDraftCollectionNamed = ( + edits: NonNullable['edits'], + draftNamespace: string, + newNamespace: string +) => { + if (draftNamespace === newNamespace) { + return edits; + } + + const { current } = edits; + const originalEditIndex = current.findIndex( + (edit) => edit.type === 'AddCollection' && edit.ns === draftNamespace + ); + const newEdit = { + ...current[originalEditIndex], + ns: newNamespace, + }; + return { + prev: edits.prev, + current: [ + ...current.slice(0, originalEditIndex), + newEdit, + ...current.slice(originalEditIndex + 1), + ] as [Edit, ...Edit[]], + next: [], + }; +}; + /** * When an edit impacts the selected item we sometimes need to update * the selection to reflect that, for instance when renaming a @@ -264,12 +335,11 @@ const updateSelectedItemsFromAppliedEdit = ( currentSelection: SelectedItems | null, edit: Edit ): SelectedItems | null => { - if (!currentSelection) { - return currentSelection; - } - switch (edit.type) { case 'RenameCollection': { + if (!currentSelection) { + return currentSelection; + } if ( currentSelection?.type === 'collection' && currentSelection.id === edit.fromNS @@ -281,6 +351,12 @@ const updateSelectedItemsFromAppliedEdit = ( } break; } + case 'AddCollection': { + return { + type: 'collection', + id: edit.ns, + }; + } } return currentSelection; @@ -538,6 +614,80 @@ export function updateCollectionNote( return applyEdit({ type: 'UpdateCollectionNote', ns, note }); } +function getPositionForNewCollection( + existingCollections: DataModelCollection[], + newCollection: Omit +): [number, number] { + const existingNodes = existingCollections.map((collection) => + collectionToDiagramNode(collection) + ); + const newNode = collectionToDiagramNode({ + ns: newCollection.ns, + jsonSchema: newCollection.jsonSchema, + displayPosition: [0, 0], + }); + const xyposition = getCoordinatesForNewNode(existingNodes, newNode); + return [xyposition.x, xyposition.y]; +} + +function getNameForNewCollection( + existingCollections: DataModelCollection[] +): string { + const database = toNS(existingCollections[0]?.ns).database; // TODO: again, what if there just isn't anything + const baseName = `${database}.new-collection`; + let counter = 1; + let newName = baseName; + + while (existingCollections.some((collection) => collection.ns === newName)) { + newName = `${baseName}-${counter}`; + counter++; + } + + return newName; +} + +export function addCollection( + ns?: string, + position?: [number, number] +): DataModelingThunkAction< + boolean, + ApplyEditAction | ApplyEditFailedAction | CollectionSelectedAction +> { + return (dispatch, getState) => { + const existingCollections = selectCurrentModelFromState( + getState() + ).collections; + if (!ns) ns = getNameForNewCollection(existingCollections); + if (!position) { + position = getPositionForNewCollection(existingCollections, { + ns, + jsonSchema: {} as MongoDBJSONSchema, + indexes: [], + }); + } + + const edit: Omit< + Extract, + 'id' | 'timestamp' + > = { + type: 'AddCollection', + ns, + initialSchema: { + bsonType: 'object', + properties: { + _id: { + bsonType: 'objectId', + }, + }, + required: ['_id'], + }, + position, + }; + dispatch(applyEdit(edit)); + return true; + }; +} + function _applyEdit(edit: Edit, model?: StaticModel): StaticModel { if (edit.type === 'SetModel') { return edit.model; @@ -546,6 +696,18 @@ function _applyEdit(edit: Edit, model?: StaticModel): StaticModel { throw new Error('Editing a model that has not been initialized'); } switch (edit.type) { + case 'AddCollection': { + const newCollection: DataModelCollection = { + ns: edit.ns, + jsonSchema: edit.initialSchema, + displayPosition: edit.position, + indexes: [], + }; + return { + ...model, + collections: [...model.collections, newCollection], + }; + } case 'AddRelationship': { return { ...model, diff --git a/packages/compass-e2e-tests/helpers/compass-web-sandbox.ts b/packages/compass-e2e-tests/helpers/compass-web-sandbox.ts index 5ceacb17191..caccb9f9608 100644 --- a/packages/compass-e2e-tests/helpers/compass-web-sandbox.ts +++ b/packages/compass-e2e-tests/helpers/compass-web-sandbox.ts @@ -19,7 +19,7 @@ const debug = Debug('compass-e2e-tests:compass-web-sandbox'); * with webdriver will get the values */ process.env.OPEN_BROWSER = 'false'; // tell webpack dev server not to open the default browser -process.env.DISABLE_DEVSERVER_OVERLAY = 'false'; +process.env.DISABLE_DEVSERVER_OVERLAY = 'true'; process.env.APP_ENV = 'webdriverio'; const wait = (ms: number) => { diff --git a/packages/compass-e2e-tests/helpers/compass.ts b/packages/compass-e2e-tests/helpers/compass.ts index 8aa1f32765c..508810a0f8f 100644 --- a/packages/compass-e2e-tests/helpers/compass.ts +++ b/packages/compass-e2e-tests/helpers/compass.ts @@ -645,6 +645,7 @@ async function startCompassElectron( const maybeWrappedBinary = (await opts.wrapBinary?.(binary)) ?? binary; process.env.APP_ENV = 'webdriverio'; + process.env.DISABLE_DEVSERVER_OVERLAY = 'true'; // For webdriverio env we are changing appName so that keychain records do not // overlap with anything else. But leave it alone when testing auto-update. if (!process.env.HADRON_AUTO_UPDATE_ENDPOINT_OVERRIDE) { diff --git a/packages/compass-e2e-tests/helpers/selectors.ts b/packages/compass-e2e-tests/helpers/selectors.ts index ec196677a62..536565c3f18 100644 --- a/packages/compass-e2e-tests/helpers/selectors.ts +++ b/packages/compass-e2e-tests/helpers/selectors.ts @@ -1452,9 +1452,9 @@ export const DataModelZoomOutButton = `${DataModelEditor} [aria-label="Minus Ico export const DataModelZoomInButton = `${DataModelEditor} [aria-label="Plus Icon"]`; export const DataModelPreview = `${DataModelEditor} [data-testid="model-preview"]`; export const DataModelPreviewCollection = (collectionId: string) => - `${DataModelPreview} [aria-roleDescription="node"][data-id="${collectionId}"]`; + `${DataModelPreview} [data-id="${collectionId}"]`; // TODO(COMPASS-9719): add once we upgrade reactflow again in diagramming: [aria-roleDescription="node"] export const DataModelPreviewRelationship = (relationshipId: string) => - `${DataModelPreview} [aria-roleDescription="edge"][data-id="${relationshipId}"]`; + `${DataModelPreview} [data-id="${relationshipId}"]`; // TODO(COMPASS-9719): add once we upgrade reactflow again in diagramming: [aria-roleDescription="edge"] export const DataModelApplyEditor = `${DataModelEditor} [data-testid="apply-editor"]`; export const DataModelEditorApplyButton = `${DataModelApplyEditor} [data-testid="apply-button"]`; export const DataModelUndoButton = 'button[aria-label="Undo"]'; @@ -1480,7 +1480,8 @@ export const DataModelsListItem = (diagramName?: string) => { export const DataModelsListItemActions = (diagramName: string) => `${DataModelsListItem(diagramName)} [aria-label="Show actions"]`; export const DataModelsListItemDeleteButton = `[data-action="delete"]`; -export const DataModelAddRelationshipBtn = 'aria/Add relationship'; +export const DataModelAddRelationshipBtn = 'aria/Add Relationship'; +export const DataModelAddCollectionBtn = 'aria/Add Collection'; export const DataModelNameInputLabel = '//label[text()="Name"]'; export const DataModelNameInput = 'input[data-testid="data-model-collection-drawer-name-input"]'; diff --git a/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts b/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts index 2733131a6ff..5fef51b79f7 100644 --- a/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts +++ b/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts @@ -547,7 +547,7 @@ describe('Data Modeling tab', function () { // Click the add relationship button const drawer = browser.$(Selectors.SideDrawer); - const addRelationshipBtn = browser.$( + const addRelationshipBtn = drawer.$( Selectors.DataModelAddRelationshipBtn ); await addRelationshipBtn.waitForClickable(); @@ -696,7 +696,7 @@ describe('Data Modeling tab', function () { // Rename the collection (it submits on unfocus). await browser.setValueVisible( browser.$(Selectors.DataModelNameInput), - 'testCollection-renamedOne' + 'renamedOne' ); await drawer.click(); // Unfocus the input. @@ -705,16 +705,14 @@ describe('Data Modeling tab', function () { const collectionName = await browser.getInputByLabel( browser.$(Selectors.SideDrawer).$(Selectors.DataModelNameInputLabel) ); - return ( - (await collectionName.getValue()) === 'testCollection-renamedOne' - ); + return (await collectionName.getValue()) === 'renamedOne'; }); // Select the second collection and verify that the new name is in the diagram. await selectCollectionOnTheDiagram(browser, 'test.testCollection-two'); const nodes = await getDiagramNodes(browser, 2); expect(nodes).to.have.lengthOf(2); - expect(nodes[0].id).to.equal('test.testCollection-renamedOne'); + expect(nodes[0].id).to.equal('test.renamedOne'); expect(nodes[1].id).to.equal('test.testCollection-two'); // Remove the collection. @@ -731,7 +729,47 @@ describe('Data Modeling tab', function () { // Verify that the collection is removed from the list and the diagram. const nodesPostDelete = await getDiagramNodes(browser, 1); expect(nodesPostDelete).to.have.lengthOf(1); - expect(nodesPostDelete[0].id).to.equal('test.testCollection-renamedOne'); + expect(nodesPostDelete[0].id).to.equal('test.renamedOne'); + }); + + it('adding a new collection from the toolbar', async function () { + const dataModelName = 'Test Edit Collection'; + await setupDiagram(browser, { + diagramName: dataModelName, + connectionName: DEFAULT_CONNECTION_NAME_1, + databaseName: 'test', + }); + + const dataModelEditor = browser.$(Selectors.DataModelEditor); + await dataModelEditor.waitForDisplayed(); + + // Click on the add collection button. + await browser.clickVisible(Selectors.DataModelAddCollectionBtn); + + // Verify that the new collection is added to the diagram. + const nodes = await getDiagramNodes(browser, 3); + expect(nodes[2].id).to.equal('test.new-collection'); + + // Verify that the drawer is opened. + const drawer = browser.$(Selectors.SideDrawer); + await drawer.waitForDisplayed(); + + // Name the collection (it submits on unfocus). + const collectionName = 'testCollection-newOne'; + await browser.setValueVisible( + browser.$(Selectors.DataModelNameInput), + collectionName + ); + await drawer.click(); // Unfocus the input. + + // Verify that the new collection is named in the diagram. + const nodesAfterNaming = await getDiagramNodes(browser, 3); + expect(nodesAfterNaming[2].id).to.equal(`test.${collectionName}`); + + // Undo once - verify that the collection is removed + // This is to ensure that the initial edit of the collection name wasn't a separate edit + await browser.clickVisible(Selectors.DataModelUndoButton); + await getDiagramNodes(browser, 2); }); }); });