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);
});
});
});