From 1342fae8db26a1c3d489703fdd0f32313259b45b Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Mon, 23 Sep 2024 13:26:17 +0200 Subject: [PATCH 01/37] setup plugin --- package-lock.json | 226 +++++++++++++++++- packages/compass-global-writes/.depcheckrc | 11 + packages/compass-global-writes/.eslintignore | 2 + packages/compass-global-writes/.eslintrc.js | 8 + packages/compass-global-writes/.mocharc.js | 1 + .../compass-global-writes/.prettierignore | 3 + .../compass-global-writes/.prettierrc.json | 1 + packages/compass-global-writes/package.json | 79 ++++++ .../src/components/index.spec.tsx | 13 + .../src/components/index.tsx | 34 +++ packages/compass-global-writes/src/index.ts | 31 +++ .../src/modules/index.ts | 32 +++ .../src/modules/namespace.ts | 3 + .../src/plugin-title.tsx | 5 + packages/compass-global-writes/src/store.ts | 39 +++ .../compass-global-writes/tsconfig-lint.json | 5 + packages/compass-global-writes/tsconfig.json | 8 + packages/compass-web/src/entrypoint.tsx | 2 + packages/compass-workspaces/src/types.ts | 3 +- .../compass/src/app/components/workspace.tsx | 2 + 20 files changed, 503 insertions(+), 5 deletions(-) create mode 100644 packages/compass-global-writes/.depcheckrc create mode 100644 packages/compass-global-writes/.eslintignore create mode 100644 packages/compass-global-writes/.eslintrc.js create mode 100644 packages/compass-global-writes/.mocharc.js create mode 100644 packages/compass-global-writes/.prettierignore create mode 100644 packages/compass-global-writes/.prettierrc.json create mode 100644 packages/compass-global-writes/package.json create mode 100644 packages/compass-global-writes/src/components/index.spec.tsx create mode 100644 packages/compass-global-writes/src/components/index.tsx create mode 100644 packages/compass-global-writes/src/index.ts create mode 100644 packages/compass-global-writes/src/modules/index.ts create mode 100644 packages/compass-global-writes/src/modules/namespace.ts create mode 100644 packages/compass-global-writes/src/plugin-title.tsx create mode 100644 packages/compass-global-writes/src/store.ts create mode 100644 packages/compass-global-writes/tsconfig-lint.json create mode 100644 packages/compass-global-writes/tsconfig.json diff --git a/package-lock.json b/package-lock.json index fc87f160cf6..28cb8ce3bdc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7818,6 +7818,10 @@ "resolved": "packages/compass-generative-ai", "link": true }, + "node_modules/@mongodb-js/compass-global-writes": { + "resolved": "packages/compass-global-writes", + "link": true + }, "node_modules/@mongodb-js/compass-import-export": { "resolved": "packages/compass-import-export", "link": true @@ -37181,7 +37185,6 @@ "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", - "dev": true, "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", @@ -37195,7 +37198,6 @@ "version": "0.20.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", - "dev": true, "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" @@ -45261,6 +45263,118 @@ "url": "https://opencollective.com/sinon" } }, + "packages/compass-global-writes": { + "version": "1.0.0", + "license": "SSPL", + "dependencies": { + "react": "^17.0.2", + "react-dom": "^17.0.2" + }, + "devDependencies": { + "@mongodb-js/eslint-config-compass": "^1.1.7", + "@mongodb-js/mocha-config-compass": "^1.4.2", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/testing-library-compass": "^1.0.1", + "@mongodb-js/tsconfig-compass": "^1.0.5", + "@types/chai": "^4.2.21", + "@types/chai-dom": "^0.0.10", + "@types/mocha": "^9.0.0", + "@types/react": "^17.0.5", + "@types/react-dom": "^17.0.10", + "@types/sinon-chai": "^3.2.5", + "chai": "^4.3.6", + "depcheck": "^1.4.1", + "eslint": "^7.25.0", + "hadron-app-registry": "^9.2.6", + "mocha": "^10.2.0", + "nyc": "^15.1.0", + "prettier": "^2.7.1", + "sinon": "^17.0.1", + "typescript": "^5.0.4", + "xvfb-maybe": "^0.2.1" + } + }, + "packages/compass-global-writes/node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "packages/compass-global-writes/node_modules/@sinonjs/fake-timers": { + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.3.1.tgz", + "integrity": "sha512-EVJO7nW5M/F5Tur0Rf2z/QoMo+1Ia963RiMtapiQrEWvY0iBUvADo8Beegwjpnle5BHkyHuoxSTW3jF43H1XRA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, + "packages/compass-global-writes/node_modules/@sinonjs/samsam": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.2.tgz", + "integrity": "sha512-v46t/fwnhejRSFTGqbpn9u+LQ9xJDse10gNnPgAcxgdoCDMXj/G2asWAC/8Qs+BAZDicX+MNZouXT1A7c83kVw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.1", + "lodash.get": "^4.4.2", + "type-detect": "^4.1.0" + } + }, + "packages/compass-global-writes/node_modules/@sinonjs/samsam/node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "packages/compass-global-writes/node_modules/just-extend": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", + "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", + "dev": true + }, + "packages/compass-global-writes/node_modules/nise": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.9.tgz", + "integrity": "sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/text-encoding": "^0.7.2", + "just-extend": "^6.2.0", + "path-to-regexp": "^6.2.1" + } + }, + "packages/compass-global-writes/node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true + }, + "packages/compass-global-writes/node_modules/sinon": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", + "integrity": "sha512-wmwE19Lie0MLT+ZYNpDymasPHUKTaZHUH/pKEubRXIzySv9Atnlw+BUMGCzWgV7b7wO+Hw6f1TEOr0IUnmU8/g==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/samsam": "^8.0.0", + "diff": "^5.1.0", + "nise": "^5.1.5", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, "packages/compass-home": { "name": "@mongodb-js/compass-home", "version": "7.0.1", @@ -56894,6 +57008,112 @@ } } }, + "@mongodb-js/compass-global-writes": { + "version": "file:packages/compass-global-writes", + "requires": { + "@mongodb-js/eslint-config-compass": "^1.1.7", + "@mongodb-js/mocha-config-compass": "^1.4.2", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/testing-library-compass": "^1.0.1", + "@mongodb-js/tsconfig-compass": "^1.0.5", + "@types/chai": "^4.2.21", + "@types/chai-dom": "^0.0.10", + "@types/mocha": "^9.0.0", + "@types/react": "^17.0.5", + "@types/react-dom": "^17.0.10", + "@types/sinon-chai": "^3.2.5", + "chai": "^4.3.6", + "depcheck": "^1.4.1", + "eslint": "^7.25.0", + "hadron-app-registry": "^9.2.6", + "mocha": "^10.2.0", + "nyc": "^15.1.0", + "prettier": "^2.7.1", + "react": "^17.0.2", + "react-dom": "^17.0.2", + "sinon": "^17.0.1", + "typescript": "^5.0.4", + "xvfb-maybe": "^0.2.1" + }, + "dependencies": { + "@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.3.1.tgz", + "integrity": "sha512-EVJO7nW5M/F5Tur0Rf2z/QoMo+1Ia963RiMtapiQrEWvY0iBUvADo8Beegwjpnle5BHkyHuoxSTW3jF43H1XRA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^3.0.1" + } + }, + "@sinonjs/samsam": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.2.tgz", + "integrity": "sha512-v46t/fwnhejRSFTGqbpn9u+LQ9xJDse10gNnPgAcxgdoCDMXj/G2asWAC/8Qs+BAZDicX+MNZouXT1A7c83kVw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^3.0.1", + "lodash.get": "^4.4.2", + "type-detect": "^4.1.0" + }, + "dependencies": { + "type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "dev": true + } + } + }, + "just-extend": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", + "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", + "dev": true + }, + "nise": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.9.tgz", + "integrity": "sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==", + "dev": true, + "requires": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/text-encoding": "^0.7.2", + "just-extend": "^6.2.0", + "path-to-regexp": "^6.2.1" + } + }, + "path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true + }, + "sinon": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", + "integrity": "sha512-wmwE19Lie0MLT+ZYNpDymasPHUKTaZHUH/pKEubRXIzySv9Atnlw+BUMGCzWgV7b7wO+Hw6f1TEOr0IUnmU8/g==", + "dev": true, + "requires": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/samsam": "^8.0.0", + "diff": "^5.1.0", + "nise": "^5.1.5", + "supports-color": "^7.2.0" + } + } + } + }, "@mongodb-js/compass-import-export": { "version": "file:packages/compass-import-export", "requires": { @@ -84338,7 +84558,6 @@ "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", - "dev": true, "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", @@ -84349,7 +84568,6 @@ "version": "0.20.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", - "dev": true, "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" diff --git a/packages/compass-global-writes/.depcheckrc b/packages/compass-global-writes/.depcheckrc new file mode 100644 index 00000000000..ae7c8273e41 --- /dev/null +++ b/packages/compass-global-writes/.depcheckrc @@ -0,0 +1,11 @@ +ignores: + - '@mongodb-js/prettier-config-compass' + - '@mongodb-js/tsconfig-compass' + - '@types/chai' + - '@types/sinon-chai' + - 'sinon' + - '@types/chai-dom' + - '@types/react' + - '@types/react-dom' +ignore-patterns: + - 'dist' diff --git a/packages/compass-global-writes/.eslintignore b/packages/compass-global-writes/.eslintignore new file mode 100644 index 00000000000..85a8a75e68c --- /dev/null +++ b/packages/compass-global-writes/.eslintignore @@ -0,0 +1,2 @@ +.nyc-output +dist diff --git a/packages/compass-global-writes/.eslintrc.js b/packages/compass-global-writes/.eslintrc.js new file mode 100644 index 00000000000..f06f8fc6013 --- /dev/null +++ b/packages/compass-global-writes/.eslintrc.js @@ -0,0 +1,8 @@ +module.exports = { + root: true, + extends: ['@mongodb-js/eslint-config-compass/plugin'], + parserOptions: { + tsconfigRootDir: __dirname, + project: ['./tsconfig-lint.json'], + }, +}; diff --git a/packages/compass-global-writes/.mocharc.js b/packages/compass-global-writes/.mocharc.js new file mode 100644 index 00000000000..a7e53abc444 --- /dev/null +++ b/packages/compass-global-writes/.mocharc.js @@ -0,0 +1 @@ +module.exports = require('@mongodb-js/mocha-config-compass/compass-plugin'); diff --git a/packages/compass-global-writes/.prettierignore b/packages/compass-global-writes/.prettierignore new file mode 100644 index 00000000000..4d28df6603a --- /dev/null +++ b/packages/compass-global-writes/.prettierignore @@ -0,0 +1,3 @@ +.nyc_output +dist +coverage diff --git a/packages/compass-global-writes/.prettierrc.json b/packages/compass-global-writes/.prettierrc.json new file mode 100644 index 00000000000..18853d1532e --- /dev/null +++ b/packages/compass-global-writes/.prettierrc.json @@ -0,0 +1 @@ +"@mongodb-js/prettier-config-compass" diff --git a/packages/compass-global-writes/package.json b/packages/compass-global-writes/package.json new file mode 100644 index 00000000000..3e53e98bd31 --- /dev/null +++ b/packages/compass-global-writes/package.json @@ -0,0 +1,79 @@ +{ + "name": "@mongodb-js/compass-global-writes", + "description": "Compass Global Sharding management", + "author": { + "name": "MongoDB Inc", + "email": "compass@mongodb.com" + }, + "private": true, + "bugs": { + "url": "https://jira.mongodb.org/projects/COMPASS/issues", + "email": "compass@mongodb.com" + }, + "homepage": "https://github.com/mongodb-js/compass", + "version": "1.0.0", + "repository": { + "type": "git", + "url": "https://github.com/mongodb-js/compass.git" + }, + "files": [ + "dist" + ], + "license": "SSPL", + "main": "dist/index.js", + "compass:main": "src/index.ts", + "exports": { + "import": "./dist/.esm-wrapper.mjs", + "require": "./dist/index.js" + }, + "compass:exports": { + ".": "./src/index.ts" + }, + "types": "./dist/index.d.ts", + "scripts": { + "bootstrap": "npm run compile", + "compile": "tsc -p tsconfig.json", + "typecheck": "tsc -p tsconfig-lint.json --noEmit", + "eslint": "eslint", + "prettier": "prettier", + "lint": "npm run eslint . && npm run prettier -- --check .", + "depcheck": "compass-scripts check-peer-deps && depcheck", + "check": "npm run typecheck && npm run lint && npm run depcheck", + "check-ci": "npm run check", + "test": "mocha", + "test-electron": "xvfb-maybe electron-mocha --no-sandbox", + "test-cov": "nyc --compact=false --produce-source-map=false -x \"**/*.spec.*\" --reporter=lcov --reporter=text --reporter=html npm run test", + "test-watch": "npm run test -- --watch", + "test-ci": "npm run test-cov", + "test-ci-electron": "npm run test-electron", + "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." + }, + "dependencies": { + "react": "^17.0.2", + "react-dom": "^17.0.2" + }, + "devDependencies": { + "@mongodb-js/eslint-config-compass": "^1.1.7", + "@mongodb-js/mocha-config-compass": "^1.4.2", + "@mongodb-js/prettier-config-compass": "^1.0.2", + "@mongodb-js/testing-library-compass": "^1.0.1", + "@mongodb-js/tsconfig-compass": "^1.0.5", + "@types/chai": "^4.2.21", + "@types/chai-dom": "^0.0.10", + "@types/mocha": "^9.0.0", + "@types/react": "^17.0.5", + "@types/react-dom": "^17.0.10", + "@types/sinon-chai": "^3.2.5", + "chai": "^4.3.6", + "depcheck": "^1.4.1", + "eslint": "^7.25.0", + "hadron-app-registry": "^9.2.6", + "mocha": "^10.2.0", + "nyc": "^15.1.0", + "prettier": "^2.7.1", + "sinon": "^17.0.1", + "typescript": "^5.0.4", + "xvfb-maybe": "^0.2.1" + }, + "is_compass_plugin": true +} diff --git a/packages/compass-global-writes/src/components/index.spec.tsx b/packages/compass-global-writes/src/components/index.spec.tsx new file mode 100644 index 00000000000..92aa3b00d9e --- /dev/null +++ b/packages/compass-global-writes/src/components/index.spec.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { expect } from 'chai'; +import { render, screen } from '@mongodb-js/testing-library-compass'; +import { CompassGlobalWritesPlugin } from '../index'; + +describe('Compass GlobalWrites Plugin', function () { + const Plugin = CompassGlobalWritesPlugin.provider.withMockServices({}); + it('renders a Plugin', function () { + render(); + expect(screen.findByText('This feature is currently in development.')).to + .exist; + }); +}); diff --git a/packages/compass-global-writes/src/components/index.tsx b/packages/compass-global-writes/src/components/index.tsx new file mode 100644 index 00000000000..cf5d50f8f1a --- /dev/null +++ b/packages/compass-global-writes/src/components/index.tsx @@ -0,0 +1,34 @@ +import { + css, + spacing, + WorkspaceContainer, + Body, +} from '@mongodb-js/compass-components'; +import React from 'react'; + +const containerStyles = css({ + paddingLeft: spacing[3], + paddingRight: spacing[3], + display: 'flex', + width: '100%', + height: '100%', +}); + +const centeredContent = css({ + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + height: '100%', +}); + +export function GlobalWrites() { + return ( +
+ + + This feature is currently in development. + + +
+ ); +} diff --git a/packages/compass-global-writes/src/index.ts b/packages/compass-global-writes/src/index.ts new file mode 100644 index 00000000000..78b4801439e --- /dev/null +++ b/packages/compass-global-writes/src/index.ts @@ -0,0 +1,31 @@ +import React from 'react'; +import { registerHadronPlugin } from 'hadron-app-registry'; + +import { GlobalWrites } from './components'; +import { GlobalWritesTabTitle } from './plugin-title'; +import { activateGlobalWritesPlugin } from './store'; +import { createLoggerLocator } from '@mongodb-js/compass-logging/provider'; +import { telemetryLocator } from '@mongodb-js/compass-telemetry/provider'; +import { connectionInfoRefLocator } from '@mongodb-js/compass-connections/provider'; + +const CompassGlobalWritesHadronPlugin = registerHadronPlugin( + { + name: 'CompassGlobalWrites', + component: function GlobalWritesProvider({ children }) { + return React.createElement(React.Fragment, null, children); + }, + activate: activateGlobalWritesPlugin, + }, + { + logger: createLoggerLocator('COMPASS-GLOBAL-WRITES-UI'), + track: telemetryLocator, + connectionInfoRef: connectionInfoRefLocator, + } +); + +export const CompassGlobalWritesPlugin = { + name: 'GlobalWrites' as const, + provider: CompassGlobalWritesHadronPlugin, + content: GlobalWrites, + header: GlobalWritesTabTitle, +}; diff --git a/packages/compass-global-writes/src/modules/index.ts b/packages/compass-global-writes/src/modules/index.ts new file mode 100644 index 00000000000..ffd62e43ae7 --- /dev/null +++ b/packages/compass-global-writes/src/modules/index.ts @@ -0,0 +1,32 @@ +import { combineReducers } from 'redux'; +import type { Action, AnyAction } from 'redux'; +import type { ThunkAction, ThunkDispatch } from 'redux-thunk'; +import type { Logger } from '@mongodb-js/compass-logging'; +import type { TrackFunction } from '@mongodb-js/compass-telemetry'; +import type { ConnectionInfoRef } from '@mongodb-js/compass-connections/provider'; + +import namespace from './namespace'; + +const reducer = combineReducers({ + namespace, +}); + +export type RootState = ReturnType; + +export type GlobalWritesExtraArgs = { + logger: Logger; + track: TrackFunction; + connectionInfoRef: ConnectionInfoRef; +}; + +export type GlobalWritesThunkDispatch = + ThunkDispatch; + +export type GlobalWritesThunkAction = ThunkAction< + R, + RootState, + GlobalWritesExtraArgs, + A +>; + +export default reducer; diff --git a/packages/compass-global-writes/src/modules/namespace.ts b/packages/compass-global-writes/src/modules/namespace.ts new file mode 100644 index 00000000000..4ec4c8f1d05 --- /dev/null +++ b/packages/compass-global-writes/src/modules/namespace.ts @@ -0,0 +1,3 @@ +export default function reducer(state = ''): string { + return state; +} diff --git a/packages/compass-global-writes/src/plugin-title.tsx b/packages/compass-global-writes/src/plugin-title.tsx new file mode 100644 index 00000000000..a733e03dee5 --- /dev/null +++ b/packages/compass-global-writes/src/plugin-title.tsx @@ -0,0 +1,5 @@ +import React from 'react'; + +export function GlobalWritesTabTitle() { + return
Global Writes
; +} diff --git a/packages/compass-global-writes/src/store.ts b/packages/compass-global-writes/src/store.ts new file mode 100644 index 00000000000..a0a3ecaad7b --- /dev/null +++ b/packages/compass-global-writes/src/store.ts @@ -0,0 +1,39 @@ +import { createStore, applyMiddleware } from 'redux'; +import type { GlobalWritesExtraArgs } from './modules'; +import reducer from './modules'; +import thunk from 'redux-thunk'; +import type { ActivateHelpers } from 'hadron-app-registry'; +import type { Logger } from '@mongodb-js/compass-logging'; +import type { TrackFunction } from '@mongodb-js/compass-telemetry'; +import type { ConnectionInfoRef } from '@mongodb-js/compass-connections/provider'; +import type { CollectionTabPluginMetadata } from '@mongodb-js/compass-collection'; + +type GlobalWritesPluginOptions = CollectionTabPluginMetadata; + +type GlobalWritesPluginServices = { + connectionInfoRef: ConnectionInfoRef; + logger: Logger; + track: TrackFunction; +}; + +export function activateGlobalWritesPlugin( + options: GlobalWritesPluginOptions, + { connectionInfoRef, logger, track }: GlobalWritesPluginServices, + { cleanup }: ActivateHelpers +) { + const store = createStore( + reducer, + { + namespace: options.namespace, + }, + applyMiddleware( + thunk.withExtraArgument({ + logger, + track, + connectionInfoRef, + }) + ) + ); + + return { store, deactivate: () => cleanup() }; +} diff --git a/packages/compass-global-writes/tsconfig-lint.json b/packages/compass-global-writes/tsconfig-lint.json new file mode 100644 index 00000000000..6bdef84f322 --- /dev/null +++ b/packages/compass-global-writes/tsconfig-lint.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.json", + "include": ["**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/compass-global-writes/tsconfig.json b/packages/compass-global-writes/tsconfig.json new file mode 100644 index 00000000000..79bc84584ce --- /dev/null +++ b/packages/compass-global-writes/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@mongodb-js/tsconfig-compass/tsconfig.react.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": ["src/**/*"], + "exclude": ["./src/**/*.spec.*"] +} diff --git a/packages/compass-web/src/entrypoint.tsx b/packages/compass-web/src/entrypoint.tsx index 333b354213e..cdf1fb96537 100644 --- a/packages/compass-web/src/entrypoint.tsx +++ b/packages/compass-web/src/entrypoint.tsx @@ -29,6 +29,7 @@ import { import { CompassSchemaPlugin } from '@mongodb-js/compass-schema'; import { CompassIndexesPlugin } from '@mongodb-js/compass-indexes'; import { CompassSchemaValidationPlugin } from '@mongodb-js/compass-schema-validation'; +import { CompassGlobalWritesPlugin } from '@mongodb-js/compass-global-writes'; import ExplainPlanCollectionTabModal from '@mongodb-js/compass-explain-plan'; import ExportToLanguageCollectionTabModal from '@mongodb-js/compass-export-to-language'; import { @@ -172,6 +173,7 @@ function CompassWorkspace({ CompassSchemaPlugin, CompassIndexesPlugin, CompassSchemaValidationPlugin, + CompassGlobalWritesPlugin, ]} modals={[ ExplainPlanCollectionTabModal, diff --git a/packages/compass-workspaces/src/types.ts b/packages/compass-workspaces/src/types.ts index c489c713b28..9a2fca9f88b 100644 --- a/packages/compass-workspaces/src/types.ts +++ b/packages/compass-workspaces/src/types.ts @@ -3,7 +3,8 @@ export type CollectionSubtab = | 'Aggregations' | 'Schema' | 'Indexes' - | 'Validation'; + | 'Validation' + | 'GlobalWrites'; export type WelcomeWorkspace = { type: 'Welcome'; diff --git a/packages/compass/src/app/components/workspace.tsx b/packages/compass/src/app/components/workspace.tsx index 4e30ae5998f..7804ee984c8 100644 --- a/packages/compass/src/app/components/workspace.tsx +++ b/packages/compass/src/app/components/workspace.tsx @@ -26,6 +26,7 @@ import { CompassAggregationsPlugin } from '@mongodb-js/compass-aggregations'; import { CompassSchemaPlugin } from '@mongodb-js/compass-schema'; import { CompassIndexesPlugin } from '@mongodb-js/compass-indexes'; import { CompassSchemaValidationPlugin } from '@mongodb-js/compass-schema-validation'; +import { CompassGlobalWritesPlugin } from '@mongodb-js/compass-global-writes'; import { CreateViewPlugin } from '@mongodb-js/compass-aggregations'; import { CreateNamespacePlugin, @@ -101,6 +102,7 @@ export default function Workspace({ CompassSchemaPlugin, CompassIndexesPlugin, CompassSchemaValidationPlugin, + CompassGlobalWritesPlugin, ]} modals={[ ExplainPlanCollectionTabModal, From 5c92d42b642aebe756fe5fa9f356e10e000b600c Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Mon, 23 Sep 2024 14:50:22 +0200 Subject: [PATCH 02/37] service and clean up --- package-lock.json | 48 +++++++++++++++++-- packages/compass-global-writes/package.json | 11 ++++- .../src/components/index.spec.tsx | 7 ++- packages/compass-global-writes/src/index.ts | 4 +- .../src/modules/index.ts | 2 + .../services/atlas-global-writes-service.ts | 5 ++ .../src/{ => store}/store.ts | 18 +++++-- 7 files changed, 81 insertions(+), 14 deletions(-) create mode 100644 packages/compass-global-writes/src/services/atlas-global-writes-service.ts rename packages/compass-global-writes/src/{ => store}/store.ts (67%) diff --git a/package-lock.json b/package-lock.json index 28cb8ce3bdc..27e3ad6e094 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37185,6 +37185,7 @@ "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", + "dev": true, "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", @@ -37198,6 +37199,7 @@ "version": "0.20.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", + "dev": true, "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" @@ -45264,11 +45266,20 @@ } }, "packages/compass-global-writes": { + "name": "@mongodb-js/compass-global-writes", "version": "1.0.0", "license": "SSPL", "dependencies": { + "@mongodb-js/atlas-service": "^0.28.3", + "@mongodb-js/compass-collection": "^4.41.0", + "@mongodb-js/compass-components": "^1.29.4", + "@mongodb-js/compass-connections": "^1.42.0", + "@mongodb-js/compass-logging": "^1.4.7", + "@mongodb-js/compass-telemetry": "^1.1.7", + "hadron-app-registry": "^9.2.6", "react": "^17.0.2", - "react-dom": "^17.0.2" + "redux": "^5.0.1", + "redux-thunk": "^3.1.0" }, "devDependencies": { "@mongodb-js/eslint-config-compass": "^1.1.7", @@ -45285,7 +45296,6 @@ "chai": "^4.3.6", "depcheck": "^1.4.1", "eslint": "^7.25.0", - "hadron-app-registry": "^9.2.6", "mocha": "^10.2.0", "nyc": "^15.1.0", "prettier": "^2.7.1", @@ -45357,6 +45367,19 @@ "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", "dev": true }, + "packages/compass-global-writes/node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" + }, + "packages/compass-global-writes/node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "peerDependencies": { + "redux": "^5.0.0" + } + }, "packages/compass-global-writes/node_modules/sinon": { "version": "17.0.1", "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", @@ -57011,6 +57034,12 @@ "@mongodb-js/compass-global-writes": { "version": "file:packages/compass-global-writes", "requires": { + "@mongodb-js/atlas-service": "^0.28.3", + "@mongodb-js/compass-collection": "^4.41.0", + "@mongodb-js/compass-components": "^1.29.4", + "@mongodb-js/compass-connections": "^1.42.0", + "@mongodb-js/compass-logging": "^1.4.7", + "@mongodb-js/compass-telemetry": "^1.1.7", "@mongodb-js/eslint-config-compass": "^1.1.7", "@mongodb-js/mocha-config-compass": "^1.4.2", "@mongodb-js/prettier-config-compass": "^1.0.2", @@ -57030,7 +57059,8 @@ "nyc": "^15.1.0", "prettier": "^2.7.1", "react": "^17.0.2", - "react-dom": "^17.0.2", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", "sinon": "^17.0.1", "typescript": "^5.0.4", "xvfb-maybe": "^0.2.1" @@ -57098,6 +57128,16 @@ "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", "dev": true }, + "redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" + }, + "redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==" + }, "sinon": { "version": "17.0.1", "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", @@ -84558,6 +84598,7 @@ "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", + "dev": true, "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", @@ -84568,6 +84609,7 @@ "version": "0.20.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", + "dev": true, "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" diff --git a/packages/compass-global-writes/package.json b/packages/compass-global-writes/package.json index 3e53e98bd31..cae449fb59b 100644 --- a/packages/compass-global-writes/package.json +++ b/packages/compass-global-writes/package.json @@ -49,8 +49,16 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "dependencies": { + "@mongodb-js/atlas-service": "^0.28.3", + "@mongodb-js/compass-collection": "^4.41.0", + "@mongodb-js/compass-components": "^1.29.4", + "@mongodb-js/compass-connections": "^1.42.0", + "@mongodb-js/compass-logging": "^1.4.7", + "@mongodb-js/compass-telemetry": "^1.1.7", + "hadron-app-registry": "^9.2.6", "react": "^17.0.2", - "react-dom": "^17.0.2" + "redux": "^5.0.1", + "redux-thunk": "^3.1.0" }, "devDependencies": { "@mongodb-js/eslint-config-compass": "^1.1.7", @@ -67,7 +75,6 @@ "chai": "^4.3.6", "depcheck": "^1.4.1", "eslint": "^7.25.0", - "hadron-app-registry": "^9.2.6", "mocha": "^10.2.0", "nyc": "^15.1.0", "prettier": "^2.7.1", diff --git a/packages/compass-global-writes/src/components/index.spec.tsx b/packages/compass-global-writes/src/components/index.spec.tsx index 92aa3b00d9e..eeb039f28f5 100644 --- a/packages/compass-global-writes/src/components/index.spec.tsx +++ b/packages/compass-global-writes/src/components/index.spec.tsx @@ -1,13 +1,12 @@ import React from 'react'; import { expect } from 'chai'; import { render, screen } from '@mongodb-js/testing-library-compass'; -import { CompassGlobalWritesPlugin } from '../index'; +import { GlobalWrites } from './index'; describe('Compass GlobalWrites Plugin', function () { - const Plugin = CompassGlobalWritesPlugin.provider.withMockServices({}); it('renders a Plugin', function () { - render(); - expect(screen.findByText('This feature is currently in development.')).to + render(); + expect(screen.getByText('This feature is currently in development.')).to .exist; }); }); diff --git a/packages/compass-global-writes/src/index.ts b/packages/compass-global-writes/src/index.ts index 78b4801439e..827f952157c 100644 --- a/packages/compass-global-writes/src/index.ts +++ b/packages/compass-global-writes/src/index.ts @@ -3,10 +3,11 @@ import { registerHadronPlugin } from 'hadron-app-registry'; import { GlobalWrites } from './components'; import { GlobalWritesTabTitle } from './plugin-title'; -import { activateGlobalWritesPlugin } from './store'; +import { activateGlobalWritesPlugin } from './store/store'; import { createLoggerLocator } from '@mongodb-js/compass-logging/provider'; import { telemetryLocator } from '@mongodb-js/compass-telemetry/provider'; import { connectionInfoRefLocator } from '@mongodb-js/compass-connections/provider'; +import { atlasServiceLocator } from '@mongodb-js/atlas-service/provider'; const CompassGlobalWritesHadronPlugin = registerHadronPlugin( { @@ -20,6 +21,7 @@ const CompassGlobalWritesHadronPlugin = registerHadronPlugin( logger: createLoggerLocator('COMPASS-GLOBAL-WRITES-UI'), track: telemetryLocator, connectionInfoRef: connectionInfoRefLocator, + atlasService: atlasServiceLocator, } ); diff --git a/packages/compass-global-writes/src/modules/index.ts b/packages/compass-global-writes/src/modules/index.ts index ffd62e43ae7..b94deb25399 100644 --- a/packages/compass-global-writes/src/modules/index.ts +++ b/packages/compass-global-writes/src/modules/index.ts @@ -5,6 +5,7 @@ import type { Logger } from '@mongodb-js/compass-logging'; import type { TrackFunction } from '@mongodb-js/compass-telemetry'; import type { ConnectionInfoRef } from '@mongodb-js/compass-connections/provider'; +import type { AtlasGlobalWritesService } from '../services/atlas-global-writes-service'; import namespace from './namespace'; const reducer = combineReducers({ @@ -17,6 +18,7 @@ export type GlobalWritesExtraArgs = { logger: Logger; track: TrackFunction; connectionInfoRef: ConnectionInfoRef; + atlasGlobalWritesService: AtlasGlobalWritesService; }; export type GlobalWritesThunkDispatch
= diff --git a/packages/compass-global-writes/src/services/atlas-global-writes-service.ts b/packages/compass-global-writes/src/services/atlas-global-writes-service.ts new file mode 100644 index 00000000000..44c7fc583de --- /dev/null +++ b/packages/compass-global-writes/src/services/atlas-global-writes-service.ts @@ -0,0 +1,5 @@ +import type { AtlasService } from '@mongodb-js/atlas-service/provider'; + +export class AtlasGlobalWritesService { + constructor(private atlasService: AtlasService) {} +} diff --git a/packages/compass-global-writes/src/store.ts b/packages/compass-global-writes/src/store/store.ts similarity index 67% rename from packages/compass-global-writes/src/store.ts rename to packages/compass-global-writes/src/store/store.ts index a0a3ecaad7b..b8912cba06f 100644 --- a/packages/compass-global-writes/src/store.ts +++ b/packages/compass-global-writes/src/store/store.ts @@ -1,12 +1,14 @@ import { createStore, applyMiddleware } from 'redux'; -import type { GlobalWritesExtraArgs } from './modules'; -import reducer from './modules'; import thunk from 'redux-thunk'; import type { ActivateHelpers } from 'hadron-app-registry'; import type { Logger } from '@mongodb-js/compass-logging'; import type { TrackFunction } from '@mongodb-js/compass-telemetry'; import type { ConnectionInfoRef } from '@mongodb-js/compass-connections/provider'; import type { CollectionTabPluginMetadata } from '@mongodb-js/compass-collection'; +import type { AtlasService } from '@mongodb-js/atlas-service/provider'; + +import reducer from '../modules'; +import { AtlasGlobalWritesService } from '../services/atlas-global-writes-service'; type GlobalWritesPluginOptions = CollectionTabPluginMetadata; @@ -14,23 +16,31 @@ type GlobalWritesPluginServices = { connectionInfoRef: ConnectionInfoRef; logger: Logger; track: TrackFunction; + atlasService: AtlasService; }; export function activateGlobalWritesPlugin( options: GlobalWritesPluginOptions, - { connectionInfoRef, logger, track }: GlobalWritesPluginServices, + { + connectionInfoRef, + logger, + track, + atlasService, + }: GlobalWritesPluginServices, { cleanup }: ActivateHelpers ) { + const atlasGlobalWritesService = new AtlasGlobalWritesService(atlasService); const store = createStore( reducer, { namespace: options.namespace, }, applyMiddleware( - thunk.withExtraArgument({ + thunk.withExtraArgument({ logger, track, connectionInfoRef, + atlasGlobalWritesService, }) ) ); From 7b1a78498617326dbdc738b0d3eb4540500e8fac Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Mon, 23 Sep 2024 17:22:48 +0200 Subject: [PATCH 03/37] show tab only if global writes is supported --- package-lock.json | 26 +++++-------------- .../src/components/collection-tab.tsx | 23 ++++++++++++---- packages/compass-connections/src/provider.ts | 1 + packages/compass-global-writes/package.json | 5 ++-- 4 files changed, 29 insertions(+), 26 deletions(-) diff --git a/package-lock.json b/package-lock.json index 27e3ad6e094..224c142e95a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45278,8 +45278,9 @@ "@mongodb-js/compass-telemetry": "^1.1.7", "hadron-app-registry": "^9.2.6", "react": "^17.0.2", - "redux": "^5.0.1", - "redux-thunk": "^3.1.0" + "react-redux": "^8.1.3", + "redux": "^4.2.1", + "redux-thunk": "^2.4.2" }, "devDependencies": { "@mongodb-js/eslint-config-compass": "^1.1.7", @@ -45372,14 +45373,6 @@ "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" }, - "packages/compass-global-writes/node_modules/redux-thunk": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", - "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", - "peerDependencies": { - "redux": "^5.0.0" - } - }, "packages/compass-global-writes/node_modules/sinon": { "version": "17.0.1", "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", @@ -57059,8 +57052,9 @@ "nyc": "^15.1.0", "prettier": "^2.7.1", "react": "^17.0.2", - "redux": "^5.0.1", - "redux-thunk": "^3.1.0", + "react-redux": "^8.1.3", + "redux": "^4.2.1", + "redux-thunk": "^2.4.2", "sinon": "^17.0.1", "typescript": "^5.0.4", "xvfb-maybe": "^0.2.1" @@ -57129,15 +57123,9 @@ "dev": true }, "redux": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "version": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" }, - "redux-thunk": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", - "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==" - }, "sinon": { "version": "17.0.1", "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", diff --git a/packages/compass-collection/src/components/collection-tab.tsx b/packages/compass-collection/src/components/collection-tab.tsx index 4d9df0da573..f4da240c81b 100644 --- a/packages/compass-collection/src/components/collection-tab.tsx +++ b/packages/compass-collection/src/components/collection-tab.tsx @@ -13,7 +13,10 @@ import type { CollectionTabOptions } from '../stores/collection-tab'; import type { CollectionMetadata } from 'mongodb-collection-model'; import type { CollectionSubtab } from '@mongodb-js/compass-workspaces'; import { useTelemetry } from '@mongodb-js/compass-telemetry/provider'; -import { useConnectionInfoRef } from '@mongodb-js/compass-connections/provider'; +import { + useConnectionInfoRef, + useConnectionSupports, +} from '@mongodb-js/compass-connections/provider'; type CollectionSubtabTrackingId = Lowercase extends infer U ? U extends string @@ -114,8 +117,19 @@ function WithErrorBoundary({ function useCollectionTabs(props: CollectionMetadata) { const pluginTabs = useCollectionSubTabs(); - return pluginTabs.map( - ({ name, content: Content, provider: Provider, header: Header }) => { + const connectionInfoRef = useConnectionInfoRef(); + const isGlobalWritesSupported = useConnectionSupports( + connectionInfoRef.current.id, + 'globalWrites' + ); + return pluginTabs + .filter((x) => { + if (x.name === 'GlobalWrites' && !isGlobalWritesSupported) { + return false; + } + return true; + }) + .map(({ name, content: Content, provider: Provider, header: Header }) => { // `pluginTabs` never change in runtime so it's safe to call the hook here // eslint-disable-next-line react-hooks/rules-of-hooks Provider.useActivate(props); @@ -136,8 +150,7 @@ function useCollectionTabs(props: CollectionMetadata) { ), }; - } - ); + }); } const CollectionTabWithMetadata: React.FunctionComponent< diff --git a/packages/compass-connections/src/provider.ts b/packages/compass-connections/src/provider.ts index dee73bd3e64..96c74721ddf 100644 --- a/packages/compass-connections/src/provider.ts +++ b/packages/compass-connections/src/provider.ts @@ -98,6 +98,7 @@ export { } from './stores/store-context'; export { useConnectionsStore as useConnections }; +export { useConnectionSupports } from './hooks/use-connection-supports'; const ConnectionStatus = { /** diff --git a/packages/compass-global-writes/package.json b/packages/compass-global-writes/package.json index cae449fb59b..307509fd210 100644 --- a/packages/compass-global-writes/package.json +++ b/packages/compass-global-writes/package.json @@ -57,8 +57,9 @@ "@mongodb-js/compass-telemetry": "^1.1.7", "hadron-app-registry": "^9.2.6", "react": "^17.0.2", - "redux": "^5.0.1", - "redux-thunk": "^3.1.0" + "react-redux": "^8.1.3", + "redux": "^4.2.1", + "redux-thunk": "^2.4.2" }, "devDependencies": { "@mongodb-js/eslint-config-compass": "^1.1.7", From 17fa0dd17349211e823d67bbff01b321b485ef14 Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Mon, 23 Sep 2024 19:54:59 +0200 Subject: [PATCH 04/37] clean up --- packages/compass-global-writes/src/index.ts | 2 +- .../src/modules/namespace.ts | 3 -- .../src/store/{store.ts => index.ts} | 18 ++++----- .../{modules/index.ts => store/reducer.ts} | 38 ++++++++++++++----- 4 files changed, 39 insertions(+), 22 deletions(-) delete mode 100644 packages/compass-global-writes/src/modules/namespace.ts rename packages/compass-global-writes/src/store/{store.ts => index.ts} (82%) rename packages/compass-global-writes/src/{modules/index.ts => store/reducer.ts} (55%) diff --git a/packages/compass-global-writes/src/index.ts b/packages/compass-global-writes/src/index.ts index 827f952157c..754e14a2086 100644 --- a/packages/compass-global-writes/src/index.ts +++ b/packages/compass-global-writes/src/index.ts @@ -3,7 +3,7 @@ import { registerHadronPlugin } from 'hadron-app-registry'; import { GlobalWrites } from './components'; import { GlobalWritesTabTitle } from './plugin-title'; -import { activateGlobalWritesPlugin } from './store/store'; +import { activateGlobalWritesPlugin } from './store'; import { createLoggerLocator } from '@mongodb-js/compass-logging/provider'; import { telemetryLocator } from '@mongodb-js/compass-telemetry/provider'; import { connectionInfoRefLocator } from '@mongodb-js/compass-connections/provider'; diff --git a/packages/compass-global-writes/src/modules/namespace.ts b/packages/compass-global-writes/src/modules/namespace.ts deleted file mode 100644 index 4ec4c8f1d05..00000000000 --- a/packages/compass-global-writes/src/modules/namespace.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default function reducer(state = ''): string { - return state; -} diff --git a/packages/compass-global-writes/src/store/store.ts b/packages/compass-global-writes/src/store/index.ts similarity index 82% rename from packages/compass-global-writes/src/store/store.ts rename to packages/compass-global-writes/src/store/index.ts index b8912cba06f..5807b9cc877 100644 --- a/packages/compass-global-writes/src/store/store.ts +++ b/packages/compass-global-writes/src/store/index.ts @@ -7,7 +7,7 @@ import type { ConnectionInfoRef } from '@mongodb-js/compass-connections/provider import type { CollectionTabPluginMetadata } from '@mongodb-js/compass-collection'; import type { AtlasService } from '@mongodb-js/atlas-service/provider'; -import reducer from '../modules'; +import reducer, { ShardingStatuses } from './reducer'; import { AtlasGlobalWritesService } from '../services/atlas-global-writes-service'; type GlobalWritesPluginOptions = CollectionTabPluginMetadata; @@ -34,15 +34,15 @@ export function activateGlobalWritesPlugin( reducer, { namespace: options.namespace, + isNamespaceSharded: false, + status: ShardingStatuses.NOT_AVAILABLE, }, - applyMiddleware( - thunk.withExtraArgument({ - logger, - track, - connectionInfoRef, - atlasGlobalWritesService, - }) - ) + applyMiddleware(thunk.withExtraArgument({ + logger, + track, + connectionInfoRef, + atlasGlobalWritesService, + })) ); return { store, deactivate: () => cleanup() }; diff --git a/packages/compass-global-writes/src/modules/index.ts b/packages/compass-global-writes/src/store/reducer.ts similarity index 55% rename from packages/compass-global-writes/src/modules/index.ts rename to packages/compass-global-writes/src/store/reducer.ts index b94deb25399..28c566afe41 100644 --- a/packages/compass-global-writes/src/modules/index.ts +++ b/packages/compass-global-writes/src/store/reducer.ts @@ -1,18 +1,38 @@ -import { combineReducers } from 'redux'; -import type { Action, AnyAction } from 'redux'; +import type { Action, Reducer } from 'redux'; import type { ThunkAction, ThunkDispatch } from 'redux-thunk'; import type { Logger } from '@mongodb-js/compass-logging'; import type { TrackFunction } from '@mongodb-js/compass-telemetry'; import type { ConnectionInfoRef } from '@mongodb-js/compass-connections/provider'; import type { AtlasGlobalWritesService } from '../services/atlas-global-writes-service'; -import namespace from './namespace'; -const reducer = combineReducers({ - namespace, -}); +export enum ShardingStatuses { + /** + * No information yet. + */ + NOT_AVAILABLE = 'NOT_AVAILABLE', +} -export type RootState = ReturnType; + +export type RootState = { + namespace: string; + isNamespaceSharded: boolean; + status: keyof typeof ShardingStatuses; +}; + + +const initialState: RootState = { + namespace: '', + isNamespaceSharded: false, + status: ShardingStatuses.NOT_AVAILABLE, +}; + + +const reducer: Reducer = ( + state = initialState, +) => { + return state; +} export type GlobalWritesExtraArgs = { logger: Logger; @@ -21,7 +41,7 @@ export type GlobalWritesExtraArgs = { atlasGlobalWritesService: AtlasGlobalWritesService; }; -export type GlobalWritesThunkDispatch = +export type GlobalWritesThunkDispatch = ThunkDispatch; export type GlobalWritesThunkAction = ThunkAction< @@ -31,4 +51,4 @@ export type GlobalWritesThunkAction = ThunkAction< A >; -export default reducer; +export default reducer; \ No newline at end of file From 16c52893f609abfa93b9d23be578bf73439dace5 Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Mon, 23 Sep 2024 19:55:12 +0200 Subject: [PATCH 05/37] fix ts --- packages/compass-telemetry/src/telemetry-events.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/compass-telemetry/src/telemetry-events.ts b/packages/compass-telemetry/src/telemetry-events.ts index 88381525972..08797c9c8ec 100644 --- a/packages/compass-telemetry/src/telemetry-events.ts +++ b/packages/compass-telemetry/src/telemetry-events.ts @@ -2434,6 +2434,7 @@ type ScreenEvent = ConnectionScoped<{ | 'databases' | 'documents' | 'indexes' + | 'globalwrites' | 'my_queries' | 'performance' | 'schema' From f64ea1de058aae9c7115333e6535079f53829440 Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Mon, 23 Sep 2024 20:02:14 +0200 Subject: [PATCH 06/37] clean up --- .../compass-global-writes/src/store/index.ts | 40 +++++++++++++------ .../src/store/reducer.ts | 32 +-------------- 2 files changed, 30 insertions(+), 42 deletions(-) diff --git a/packages/compass-global-writes/src/store/index.ts b/packages/compass-global-writes/src/store/index.ts index 5807b9cc877..e482b67d8af 100644 --- a/packages/compass-global-writes/src/store/index.ts +++ b/packages/compass-global-writes/src/store/index.ts @@ -1,4 +1,4 @@ -import { createStore, applyMiddleware } from 'redux'; +import { createStore, applyMiddleware, type Action } from 'redux'; import thunk from 'redux-thunk'; import type { ActivateHelpers } from 'hadron-app-registry'; import type { Logger } from '@mongodb-js/compass-logging'; @@ -6,16 +6,30 @@ import type { TrackFunction } from '@mongodb-js/compass-telemetry'; import type { ConnectionInfoRef } from '@mongodb-js/compass-connections/provider'; import type { CollectionTabPluginMetadata } from '@mongodb-js/compass-collection'; import type { AtlasService } from '@mongodb-js/atlas-service/provider'; +import type { ThunkAction } from 'redux-thunk'; -import reducer, { ShardingStatuses } from './reducer'; +import reducer, { ShardingStatuses, type RootState } from './reducer'; import { AtlasGlobalWritesService } from '../services/atlas-global-writes-service'; -type GlobalWritesPluginOptions = CollectionTabPluginMetadata; - -type GlobalWritesPluginServices = { - connectionInfoRef: ConnectionInfoRef; +type GlobalWritesExtraArgs = { logger: Logger; track: TrackFunction; + connectionInfoRef: ConnectionInfoRef; + atlasGlobalWritesService: AtlasGlobalWritesService; +}; + +export type GlobalWritesThunkAction = ThunkAction< + R, + RootState, + GlobalWritesExtraArgs, + A +>; + +type GlobalWritesPluginOptions = CollectionTabPluginMetadata; +type GlobalWritesPluginServices = Pick< + GlobalWritesExtraArgs, + 'logger' | 'track' | 'connectionInfoRef' +> & { atlasService: AtlasService; }; @@ -37,12 +51,14 @@ export function activateGlobalWritesPlugin( isNamespaceSharded: false, status: ShardingStatuses.NOT_AVAILABLE, }, - applyMiddleware(thunk.withExtraArgument({ - logger, - track, - connectionInfoRef, - atlasGlobalWritesService, - })) + applyMiddleware( + thunk.withExtraArgument({ + logger, + track, + connectionInfoRef, + atlasGlobalWritesService, + }) + ) ); return { store, deactivate: () => cleanup() }; diff --git a/packages/compass-global-writes/src/store/reducer.ts b/packages/compass-global-writes/src/store/reducer.ts index 28c566afe41..c26e7d2a18b 100644 --- a/packages/compass-global-writes/src/store/reducer.ts +++ b/packages/compass-global-writes/src/store/reducer.ts @@ -1,10 +1,4 @@ import type { Action, Reducer } from 'redux'; -import type { ThunkAction, ThunkDispatch } from 'redux-thunk'; -import type { Logger } from '@mongodb-js/compass-logging'; -import type { TrackFunction } from '@mongodb-js/compass-telemetry'; -import type { ConnectionInfoRef } from '@mongodb-js/compass-connections/provider'; - -import type { AtlasGlobalWritesService } from '../services/atlas-global-writes-service'; export enum ShardingStatuses { /** @@ -13,42 +7,20 @@ export enum ShardingStatuses { NOT_AVAILABLE = 'NOT_AVAILABLE', } - export type RootState = { namespace: string; isNamespaceSharded: boolean; status: keyof typeof ShardingStatuses; }; - const initialState: RootState = { namespace: '', isNamespaceSharded: false, status: ShardingStatuses.NOT_AVAILABLE, }; - -const reducer: Reducer = ( - state = initialState, -) => { +const reducer: Reducer = (state = initialState) => { return state; -} - -export type GlobalWritesExtraArgs = { - logger: Logger; - track: TrackFunction; - connectionInfoRef: ConnectionInfoRef; - atlasGlobalWritesService: AtlasGlobalWritesService; }; -export type GlobalWritesThunkDispatch = - ThunkDispatch; - -export type GlobalWritesThunkAction = ThunkAction< - R, - RootState, - GlobalWritesExtraArgs, - A ->; - -export default reducer; \ No newline at end of file +export default reducer; From 3b74cf62605a34425e58b3b6162638c3504b04c2 Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Mon, 23 Sep 2024 20:49:12 +0200 Subject: [PATCH 07/37] depcheck --- packages/compass-global-writes/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/compass-global-writes/package.json b/packages/compass-global-writes/package.json index 307509fd210..917f8d4791a 100644 --- a/packages/compass-global-writes/package.json +++ b/packages/compass-global-writes/package.json @@ -57,7 +57,6 @@ "@mongodb-js/compass-telemetry": "^1.1.7", "hadron-app-registry": "^9.2.6", "react": "^17.0.2", - "react-redux": "^8.1.3", "redux": "^4.2.1", "redux-thunk": "^2.4.2" }, From 2c4f6ff74fd65b6494df8419b29c3a360d48a506 Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Mon, 23 Sep 2024 20:55:39 +0200 Subject: [PATCH 08/37] correct version of redux in lock file --- package-lock.json | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index 224c142e95a..8628fb58edc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45266,7 +45266,6 @@ } }, "packages/compass-global-writes": { - "name": "@mongodb-js/compass-global-writes", "version": "1.0.0", "license": "SSPL", "dependencies": { @@ -45278,7 +45277,6 @@ "@mongodb-js/compass-telemetry": "^1.1.7", "hadron-app-registry": "^9.2.6", "react": "^17.0.2", - "react-redux": "^8.1.3", "redux": "^4.2.1", "redux-thunk": "^2.4.2" }, @@ -45368,11 +45366,6 @@ "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", "dev": true }, - "packages/compass-global-writes/node_modules/redux": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", - "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" - }, "packages/compass-global-writes/node_modules/sinon": { "version": "17.0.1", "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", @@ -57052,7 +57045,6 @@ "nyc": "^15.1.0", "prettier": "^2.7.1", "react": "^17.0.2", - "react-redux": "^8.1.3", "redux": "^4.2.1", "redux-thunk": "^2.4.2", "sinon": "^17.0.1", @@ -57122,10 +57114,6 @@ "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", "dev": true }, - "redux": { - "version": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", - "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" - }, "sinon": { "version": "17.0.1", "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", From 15fb5c0d5f13fa0d96a9b67d9f82a4840470ce91 Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Mon, 23 Sep 2024 21:01:19 +0200 Subject: [PATCH 09/37] depcheck again --- package-lock.json | 5 +++++ packages/compass-web/package.json | 1 + packages/compass/package.json | 1 + 3 files changed, 7 insertions(+) diff --git a/package-lock.json b/package-lock.json index 8628fb58edc..bc4acf39ccd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43789,6 +43789,7 @@ "@mongodb-js/compass-field-store": "^9.17.0", "@mongodb-js/compass-find-in-page": "^4.30.4", "@mongodb-js/compass-generative-ai": "^0.22.3", + "@mongodb-js/compass-global-writes": "^1.0.0", "@mongodb-js/compass-import-export": "^7.41.0", "@mongodb-js/compass-indexes": "^5.41.0", "@mongodb-js/compass-intercom": "^0.12.3", @@ -45266,6 +45267,7 @@ } }, "packages/compass-global-writes": { + "name": "@mongodb-js/compass-global-writes", "version": "1.0.0", "license": "SSPL", "dependencies": { @@ -47087,6 +47089,7 @@ "@mongodb-js/compass-export-to-language": "^9.18.0", "@mongodb-js/compass-field-store": "^9.17.0", "@mongodb-js/compass-generative-ai": "^0.22.3", + "@mongodb-js/compass-global-writes": "^1.0.0", "@mongodb-js/compass-indexes": "^5.41.0", "@mongodb-js/compass-logging": "^1.4.7", "@mongodb-js/compass-query-bar": "^8.43.0", @@ -58787,6 +58790,7 @@ "@mongodb-js/compass-export-to-language": "^9.18.0", "@mongodb-js/compass-field-store": "^9.17.0", "@mongodb-js/compass-generative-ai": "^0.22.3", + "@mongodb-js/compass-global-writes": "^1.0.0", "@mongodb-js/compass-indexes": "^5.41.0", "@mongodb-js/compass-logging": "^1.4.7", "@mongodb-js/compass-query-bar": "^8.43.0", @@ -80097,6 +80101,7 @@ "@mongodb-js/compass-field-store": "^9.17.0", "@mongodb-js/compass-find-in-page": "^4.30.4", "@mongodb-js/compass-generative-ai": "^0.22.3", + "@mongodb-js/compass-global-writes": "^1.0.0", "@mongodb-js/compass-import-export": "^7.41.0", "@mongodb-js/compass-indexes": "^5.41.0", "@mongodb-js/compass-intercom": "^0.12.3", diff --git a/packages/compass-web/package.json b/packages/compass-web/package.json index 1e3a95da45a..d52c9e31d25 100644 --- a/packages/compass-web/package.json +++ b/packages/compass-web/package.json @@ -74,6 +74,7 @@ "@mongodb-js/compass-export-to-language": "^9.18.0", "@mongodb-js/compass-field-store": "^9.17.0", "@mongodb-js/compass-generative-ai": "^0.22.3", + "@mongodb-js/compass-global-writes": "^1.0.0", "@mongodb-js/compass-indexes": "^5.41.0", "@mongodb-js/compass-logging": "^1.4.7", "@mongodb-js/compass-query-bar": "^8.43.0", diff --git a/packages/compass/package.json b/packages/compass/package.json index 327751e90d6..f3894466020 100644 --- a/packages/compass/package.json +++ b/packages/compass/package.json @@ -205,6 +205,7 @@ "@mongodb-js/compass-field-store": "^9.17.0", "@mongodb-js/compass-find-in-page": "^4.30.4", "@mongodb-js/compass-generative-ai": "^0.22.3", + "@mongodb-js/compass-global-writes": "^1.0.0", "@mongodb-js/compass-import-export": "^7.41.0", "@mongodb-js/compass-indexes": "^5.41.0", "@mongodb-js/compass-intercom": "^0.12.3", From 22ff79f94d4d6d280d285c9df04e113b95165c61 Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Wed, 25 Sep 2024 09:36:05 +0200 Subject: [PATCH 10/37] feedback --- packages/compass-global-writes/src/store/index.ts | 2 +- packages/compass-global-writes/src/store/reducer.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/compass-global-writes/src/store/index.ts b/packages/compass-global-writes/src/store/index.ts index e482b67d8af..1ddb8a33263 100644 --- a/packages/compass-global-writes/src/store/index.ts +++ b/packages/compass-global-writes/src/store/index.ts @@ -49,7 +49,7 @@ export function activateGlobalWritesPlugin( { namespace: options.namespace, isNamespaceSharded: false, - status: ShardingStatuses.NOT_AVAILABLE, + status: ShardingStatuses.NOT_READY, }, applyMiddleware( thunk.withExtraArgument({ diff --git a/packages/compass-global-writes/src/store/reducer.ts b/packages/compass-global-writes/src/store/reducer.ts index c26e7d2a18b..5555f3ac410 100644 --- a/packages/compass-global-writes/src/store/reducer.ts +++ b/packages/compass-global-writes/src/store/reducer.ts @@ -2,9 +2,9 @@ import type { Action, Reducer } from 'redux'; export enum ShardingStatuses { /** - * No information yet. + * Initial status, no information available yet. */ - NOT_AVAILABLE = 'NOT_AVAILABLE', + NOT_READY = 'NOT_READY', } export type RootState = { @@ -16,7 +16,7 @@ export type RootState = { const initialState: RootState = { namespace: '', isNamespaceSharded: false, - status: ShardingStatuses.NOT_AVAILABLE, + status: ShardingStatuses.NOT_READY, }; const reducer: Reducer = (state = initialState) => { From b1f5d5670e3f2b571cfabdbf0e6b9325eddd15eb Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Wed, 25 Sep 2024 13:37:39 +0200 Subject: [PATCH 11/37] warning --- .../src/components/states/unsharded.tsx | 9 ++++ .../src/plugin-title.tsx | 19 ++++++-- .../services/atlas-global-writes-service.ts | 20 ++++++++ .../compass-global-writes/src/store/index.ts | 8 +++- .../src/store/reducer.ts | 48 +++++++++++++++++++ 5 files changed, 100 insertions(+), 4 deletions(-) create mode 100644 packages/compass-global-writes/src/components/states/unsharded.tsx diff --git a/packages/compass-global-writes/src/components/states/unsharded.tsx b/packages/compass-global-writes/src/components/states/unsharded.tsx new file mode 100644 index 00000000000..c70efdb4acc --- /dev/null +++ b/packages/compass-global-writes/src/components/states/unsharded.tsx @@ -0,0 +1,9 @@ +import React from 'react'; + +export function UnshardedState() { + return ( +
+

Unsharded

+
+ ); +} diff --git a/packages/compass-global-writes/src/plugin-title.tsx b/packages/compass-global-writes/src/plugin-title.tsx index a733e03dee5..75f50db3366 100644 --- a/packages/compass-global-writes/src/plugin-title.tsx +++ b/packages/compass-global-writes/src/plugin-title.tsx @@ -1,5 +1,18 @@ +import { connect } from 'react-redux'; import React from 'react'; +import { type RootState, ShardingStatuses } from './store/reducer'; +import { Icon } from '@mongodb-js/compass-components'; -export function GlobalWritesTabTitle() { - return
Global Writes
; -} +const PluginTitle = ({ showWarning }: { showWarning: boolean }) => { + return ( +
+ Global Writes {showWarning && } +
+ ); +}; + +export const GlobalWritesTabTitle = connect( + ({ isNamespaceSharded, status }: RootState) => ({ + showWarning: !isNamespaceSharded && status !== ShardingStatuses.NOT_READY, + }) +)(PluginTitle); diff --git a/packages/compass-global-writes/src/services/atlas-global-writes-service.ts b/packages/compass-global-writes/src/services/atlas-global-writes-service.ts index 44c7fc583de..4cd3d3ac444 100644 --- a/packages/compass-global-writes/src/services/atlas-global-writes-service.ts +++ b/packages/compass-global-writes/src/services/atlas-global-writes-service.ts @@ -2,4 +2,24 @@ import type { AtlasService } from '@mongodb-js/atlas-service/provider'; export class AtlasGlobalWritesService { constructor(private atlasService: AtlasService) {} + + async isNamespaceManaged( + namespace: string, + { + clusterName, + orgId, + }: { + orgId: string; + clusterName: string; + } + ) { + const uri = this.atlasService.cloudEndpoint( + `nds/clusters/${orgId}/${clusterName}` + ); + const response = await this.atlasService.authenticatedFetch(uri); + const cluster = await response.json(); + console.log(cluster, namespace); + + return false; + } } diff --git a/packages/compass-global-writes/src/store/index.ts b/packages/compass-global-writes/src/store/index.ts index 1ddb8a33263..22b5e93f09a 100644 --- a/packages/compass-global-writes/src/store/index.ts +++ b/packages/compass-global-writes/src/store/index.ts @@ -8,7 +8,11 @@ import type { CollectionTabPluginMetadata } from '@mongodb-js/compass-collection import type { AtlasService } from '@mongodb-js/atlas-service/provider'; import type { ThunkAction } from 'redux-thunk'; -import reducer, { ShardingStatuses, type RootState } from './reducer'; +import reducer, { + ShardingStatuses, + updateIsNamespaceManaged, + type RootState, +} from './reducer'; import { AtlasGlobalWritesService } from '../services/atlas-global-writes-service'; type GlobalWritesExtraArgs = { @@ -61,5 +65,7 @@ export function activateGlobalWritesPlugin( ) ); + void store.dispatch(updateIsNamespaceManaged()); + return { store, deactivate: () => cleanup() }; } diff --git a/packages/compass-global-writes/src/store/reducer.ts b/packages/compass-global-writes/src/store/reducer.ts index 5555f3ac410..03f3b21f11b 100644 --- a/packages/compass-global-writes/src/store/reducer.ts +++ b/packages/compass-global-writes/src/store/reducer.ts @@ -1,10 +1,32 @@ import type { Action, Reducer } from 'redux'; +import type { GlobalWritesThunkAction } from '.'; + +export function isAction
( + action: Action, + type: A['type'] +): action is A { + return action.type === type; +} + +enum GlobalWritesActionTypes { + SetIsManagedNamespace = 'global-writes/SetIsManagedNamespace', +} + +type SetIsManagedNamespaceAction = { + type: GlobalWritesActionTypes.SetIsManagedNamespace; + isNamespaceManaged: boolean; +}; export enum ShardingStatuses { /** * Initial status, no information available yet. */ NOT_READY = 'NOT_READY', + + /** + * Namespace is not geo-sharded. + */ + UNSHARDED = 'UNSHARDED', } export type RootState = { @@ -23,4 +45,30 @@ const reducer: Reducer = (state = initialState) => { return state; }; +export const updateIsNamespaceManaged = + (): GlobalWritesThunkAction, SetIsManagedNamespaceAction> => + async ( + dispatch, + getState, + { atlasGlobalWritesService, connectionInfoRef } + ) => { + if (!connectionInfoRef.current.atlasMetadata) { + return; + } + + const { clusterName, orgId } = connectionInfoRef.current.atlasMetadata; + const { namespace } = getState(); + + const isNamespaceManaged = + await atlasGlobalWritesService.isNamespaceManaged(namespace, { + orgId, + clusterName, + }); + + dispatch({ + type: GlobalWritesActionTypes.SetIsManagedNamespace, + isNamespaceManaged, + }); + }; + export default reducer; From ca0afc76cb7d937d1972f07d76dbab178b42820f Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Wed, 25 Sep 2024 14:41:43 +0200 Subject: [PATCH 12/37] show warning --- .../src/plugin-title.tsx | 33 ++++++++++++-- .../services/atlas-global-writes-service.ts | 43 ++++++++++++++++--- .../compass-global-writes/src/store/index.ts | 4 +- .../src/store/reducer.ts | 39 +++++++++++++---- 4 files changed, 101 insertions(+), 18 deletions(-) diff --git a/packages/compass-global-writes/src/plugin-title.tsx b/packages/compass-global-writes/src/plugin-title.tsx index 75f50db3366..4c76fd8d84a 100644 --- a/packages/compass-global-writes/src/plugin-title.tsx +++ b/packages/compass-global-writes/src/plugin-title.tsx @@ -1,12 +1,39 @@ import { connect } from 'react-redux'; import React from 'react'; import { type RootState, ShardingStatuses } from './store/reducer'; -import { Icon } from '@mongodb-js/compass-components'; +import { + css, + Icon, + palette, + spacing, + useDarkMode, +} from '@mongodb-js/compass-components'; + +const containerStyles = css({ + display: 'flex', + gap: spacing[200], + alignItems: 'center', +}); + +const iconStylesLight = css({ + color: palette.yellow.dark2, +}); + +const iconStylesDark = css({ + color: palette.yellow.base, +}); const PluginTitle = ({ showWarning }: { showWarning: boolean }) => { + const darkMode = useDarkMode(); return ( -
- Global Writes {showWarning && } +
+ Global Writes{' '} + {showWarning && ( + + )}
); }; diff --git a/packages/compass-global-writes/src/services/atlas-global-writes-service.ts b/packages/compass-global-writes/src/services/atlas-global-writes-service.ts index 4cd3d3ac444..d75e03bd05a 100644 --- a/packages/compass-global-writes/src/services/atlas-global-writes-service.ts +++ b/packages/compass-global-writes/src/services/atlas-global-writes-service.ts @@ -1,4 +1,30 @@ import type { AtlasService } from '@mongodb-js/atlas-service/provider'; +import toNS from 'mongodb-ns'; + +export type ManagedNamespace = { + db: string; + collection: string; + customShardKey: string; + isCustomShardKeyHashed: boolean; + isShardKeyUnique: boolean; + numInitialChunks?: number; + presplitHashedZones: boolean; +}; + +type ClusterDetailsApiResponse = { + geoSharding: { + customZoneMapping: unknown; + managedNamespaces: ManagedNamespace[]; + }; +}; + +function assertDataIsClusterDetailsApiResponse( + data: any +): asserts data is ClusterDetailsApiResponse { + if (!Array.isArray(data?.geoSharding?.managedNamespaces)) { + throw new Error('Invalid cluster details API response'); + } +} export class AtlasGlobalWritesService { constructor(private atlasService: AtlasService) {} @@ -7,19 +33,26 @@ export class AtlasGlobalWritesService { namespace: string, { clusterName, - orgId, + projectId, }: { - orgId: string; + projectId: string; clusterName: string; } ) { const uri = this.atlasService.cloudEndpoint( - `nds/clusters/${orgId}/${clusterName}` + `nds/clusters/${projectId}/${clusterName}` ); const response = await this.atlasService.authenticatedFetch(uri); const cluster = await response.json(); - console.log(cluster, namespace); - return false; + assertDataIsClusterDetailsApiResponse(cluster); + + const { database, collection } = toNS(namespace); + return cluster.geoSharding.managedNamespaces.some((managedNamespace) => { + return ( + managedNamespace.db === database && + managedNamespace.collection === collection + ); + }); } } diff --git a/packages/compass-global-writes/src/store/index.ts b/packages/compass-global-writes/src/store/index.ts index 22b5e93f09a..a27c204293b 100644 --- a/packages/compass-global-writes/src/store/index.ts +++ b/packages/compass-global-writes/src/store/index.ts @@ -10,7 +10,7 @@ import type { ThunkAction } from 'redux-thunk'; import reducer, { ShardingStatuses, - updateIsNamespaceManaged, + fetchClusterShardingData, type RootState, } from './reducer'; import { AtlasGlobalWritesService } from '../services/atlas-global-writes-service'; @@ -65,7 +65,7 @@ export function activateGlobalWritesPlugin( ) ); - void store.dispatch(updateIsNamespaceManaged()); + void store.dispatch(fetchClusterShardingData()); return { store, deactivate: () => cleanup() }; } diff --git a/packages/compass-global-writes/src/store/reducer.ts b/packages/compass-global-writes/src/store/reducer.ts index 03f3b21f11b..498780fe93e 100644 --- a/packages/compass-global-writes/src/store/reducer.ts +++ b/packages/compass-global-writes/src/store/reducer.ts @@ -41,11 +41,25 @@ const initialState: RootState = { status: ShardingStatuses.NOT_READY, }; -const reducer: Reducer = (state = initialState) => { +const reducer: Reducer = (state = initialState, action) => { + if ( + isAction( + action, + GlobalWritesActionTypes.SetIsManagedNamespace + ) + ) { + return { + ...state, + isNamespaceSharded: action.isNamespaceManaged, + status: !action.isNamespaceManaged + ? ShardingStatuses.UNSHARDED + : state.status, + }; + } return state; }; -export const updateIsNamespaceManaged = +export const fetchClusterShardingData = (): GlobalWritesThunkAction, SetIsManagedNamespaceAction> => async ( dispatch, @@ -56,19 +70,28 @@ export const updateIsNamespaceManaged = return; } - const { clusterName, orgId } = connectionInfoRef.current.atlasMetadata; const { namespace } = getState(); + const { clusterName, projectId } = connectionInfoRef.current.atlasMetadata; + // Call the API to check if the namespace is managed. If the namespace is managed, + // we would want to fetch more data that is needed to figure out the state and + // accordingly show the UI to the user. const isNamespaceManaged = await atlasGlobalWritesService.isNamespaceManaged(namespace, { - orgId, + projectId, clusterName, }); - dispatch({ - type: GlobalWritesActionTypes.SetIsManagedNamespace, - isNamespaceManaged, - }); + if (!isNamespaceManaged) { + dispatch({ + type: GlobalWritesActionTypes.SetIsManagedNamespace, + isNamespaceManaged: false, + }); + return; + } + + // Now fetch the sharding key and possible process error. + return; }; export default reducer; From 1cef989dc493e43fef8d6cca2cc440a7a03b907e Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Wed, 25 Sep 2024 15:52:47 +0200 Subject: [PATCH 13/37] title tooltip --- .../src/plugin-title.tsx | 41 +++++++++++++++++-- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/packages/compass-global-writes/src/plugin-title.tsx b/packages/compass-global-writes/src/plugin-title.tsx index 4c76fd8d84a..831b50f5c74 100644 --- a/packages/compass-global-writes/src/plugin-title.tsx +++ b/packages/compass-global-writes/src/plugin-title.tsx @@ -2,10 +2,13 @@ import { connect } from 'react-redux'; import React from 'react'; import { type RootState, ShardingStatuses } from './store/reducer'; import { + Body, css, + cx, Icon, palette, spacing, + Tooltip, useDarkMode, } from '@mongodb-js/compass-components'; @@ -15,6 +18,10 @@ const containerStyles = css({ alignItems: 'center', }); +const warningIconStyles = css({ + display: 'flex', +}); + const iconStylesLight = css({ color: palette.yellow.dark2, }); @@ -29,10 +36,36 @@ const PluginTitle = ({ showWarning }: { showWarning: boolean }) => {
Global Writes{' '} {showWarning && ( - + { + // LG does not bubble up the click event to the parent component, + // so we add noop onClick and let it bubble up. + }} + > + + + } + > + + Collections in Atlas Global Clusters with Atlas-managed sharding + must be configured with a compound shard key made up of both a + ‘location’ field and an identifier field that you provide. Please + configure sharding here. + + )}
); From e6788d6f3dd1ec452d26cf6af9ef9d0f2ec86631 Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Wed, 25 Sep 2024 21:34:54 +0200 Subject: [PATCH 14/37] unsharded view and tests --- .../src/components/index.spec.tsx | 15 +- .../src/components/index.tsx | 41 ++- .../src/components/states/unsharded.tsx | 322 ++++++++++++++++- .../src/components/states/usharded.spec.tsx | 193 ++++++++++ packages/compass-global-writes/src/index.ts | 2 +- .../src/plugin-title.tsx | 4 +- .../compass-global-writes/src/store/index.ts | 19 +- .../tests/cluster-api-response.json | 337 ++++++++++++++++++ .../tests/create-store.tsx | 80 +++++ 9 files changed, 990 insertions(+), 23 deletions(-) create mode 100644 packages/compass-global-writes/src/components/states/usharded.spec.tsx create mode 100644 packages/compass-global-writes/tests/cluster-api-response.json create mode 100644 packages/compass-global-writes/tests/create-store.tsx diff --git a/packages/compass-global-writes/src/components/index.spec.tsx b/packages/compass-global-writes/src/components/index.spec.tsx index eeb039f28f5..c60d5680849 100644 --- a/packages/compass-global-writes/src/components/index.spec.tsx +++ b/packages/compass-global-writes/src/components/index.spec.tsx @@ -1,12 +1,17 @@ import React from 'react'; import { expect } from 'chai'; -import { render, screen } from '@mongodb-js/testing-library-compass'; +import { screen } from '@mongodb-js/testing-library-compass'; import { GlobalWrites } from './index'; +import { renderWithStore } from './../../tests/create-store'; describe('Compass GlobalWrites Plugin', function () { - it('renders a Plugin', function () { - render(); - expect(screen.getByText('This feature is currently in development.')).to - .exist; + it('renders plugin in NOT_READY state', function () { + renderWithStore(); + expect(screen.getByText('Loading ...')).to.exist; + }); + + it('renders plugin in UNSHARDED state', function () { + renderWithStore(); + expect(screen.getByTestId('shard-collection-button')).to.exist; }); }); diff --git a/packages/compass-global-writes/src/components/index.tsx b/packages/compass-global-writes/src/components/index.tsx index cf5d50f8f1a..5144f2a82a6 100644 --- a/packages/compass-global-writes/src/components/index.tsx +++ b/packages/compass-global-writes/src/components/index.tsx @@ -1,10 +1,15 @@ +import React from 'react'; +import { connect } from 'react-redux'; import { css, spacing, WorkspaceContainer, Body, + SpinLoaderWithLabel, } from '@mongodb-js/compass-components'; -import React from 'react'; +import type { RootState, ShardingStatus } from '../store/reducer'; +import { ShardingStatuses } from '../store/reducer'; +import UnshardedState from './states/unsharded'; const containerStyles = css({ paddingLeft: spacing[3], @@ -14,6 +19,10 @@ const containerStyles = css({ height: '100%', }); +const workspaceContentStyles = css({ + paddingTop: spacing[400], +}); + const centeredContent = css({ display: 'flex', justifyContent: 'center', @@ -21,14 +30,34 @@ const centeredContent = css({ height: '100%', }); -export function GlobalWrites() { - return ( -
- +type GlobalWritesProps = { + shardingStatus: ShardingStatus; +}; + +function getStateViewBasedOnShardingStatus(shardingStatus: ShardingStatus) { + switch (shardingStatus) { + case ShardingStatuses.NOT_READY: + return ( - This feature is currently in development. + + ); + case ShardingStatuses.UNSHARDED: + return ; + default: + return null; + } +} + +export function GlobalWrites({ shardingStatus }: GlobalWritesProps) { + return ( +
+ + {getStateViewBasedOnShardingStatus(shardingStatus)}
); } +export default connect((state: RootState) => ({ + shardingStatus: state.status, +}))(GlobalWrites); diff --git a/packages/compass-global-writes/src/components/states/unsharded.tsx b/packages/compass-global-writes/src/components/states/unsharded.tsx index c70efdb4acc..ac15848ba0d 100644 --- a/packages/compass-global-writes/src/components/states/unsharded.tsx +++ b/packages/compass-global-writes/src/components/states/unsharded.tsx @@ -1,9 +1,323 @@ -import React from 'react'; +import React, { useCallback, useState } from 'react'; +import { + Banner, + BannerVariant, + Body, + css, + Label, + Link, + spacing, + Subtitle, + InlineInfoLink, + TextInput, + Accordion, + RadioGroup, + Radio, + ComboboxWithCustomOption, + ComboboxOption, + Checkbox, + Button, + cx, +} from '@mongodb-js/compass-components'; +import { useAutocompleteFields } from '@mongodb-js/compass-field-store'; +import { connect } from 'react-redux'; +import type { CreateShardKeyData, RootState } from '../../store/reducer'; +import { createShardKey } from '../../store/reducer'; -export function UnshardedState() { +const nbsp = '\u00a0'; + +const containerStyles = css({ + display: 'flex', + flexDirection: 'column', + gap: spacing[400], +}); + +const contentStyles = css({ + display: 'flex', + flexDirection: 'column', + gap: spacing[200], +}); + +const listStyles = css({ + listStyle: 'disc', + paddingLeft: 'auto', + marginTop: 0, +}); + +const shardKeyFormFieldsStyles = css({ + display: 'flex', + flexDirection: 'row', + gap: spacing[400], +}); + +const secondShardKeyStyles = css({ + width: '300px', +}); + +const hasedIndexOptionsStyles = css({ + marginLeft: spacing[600] + spacing[100], // This aligns it with the radio button text + marginTop: spacing[400], +}); + +const chunksInputStyles = css({ + display: 'flex', + alignItems: 'center', + gap: spacing[100], +}); + +function CreateShardKeyDescription() { return ( -
-

Unsharded

+
+ Configure compound shard key + + To properly configure Global Writes, your collections must be sharded + using a compound shard key made up of a 'location' field and a + second field of your choosing. + + + + All documents in your collection should contain both the + 'location' field and your chosen second field. + + +
    +
  • + + The second field should represent a well-distributed and immutable + value to ensure that data is equally distributed across shards in a + particular zone.{nbsp} + + Note that the value of this field cannot be an array. + + {nbsp} + For more information, read our documentation on{' '} + + selecting a shard key + + . + +
  • +
+ + + Once you shard your collection, it cannot be unsharded. +
); } + +type ShardingAdvancedOption = 'default' | 'unique-index' | 'hashed-index'; +type UnshardedStateProps = { + namespace: string; + onCreateShardKey: (data: CreateShardKeyData) => void; +}; + +function CreateShardKeyForm({ + namespace, + onCreateShardKey, +}: Pick) { + const [isAdvancedOptionsOpen, setIsAdvancedOptionsOpen] = useState(false); + const [selectedOption, setSelectedOption] = + useState('default'); + const fields = useAutocompleteFields(namespace); + + const [secondShardKey, setSecondShardKey] = useState(null); + const [numInitialChunks, setNumInitialChunks] = useState< + string | undefined + >(); + const [isPreSplitData, setIsPreSplitData] = useState(false); + + const onSubmit = useCallback(() => { + if (!secondShardKey) { + return; + } + const isCustomShardKeyHashed = selectedOption === 'hashed-index'; + const presplitHashedZones = isCustomShardKeyHashed && isPreSplitData; + + const data: CreateShardKeyData = { + customShardKey: secondShardKey, + isShardKeyUnique: selectedOption === 'unique-index', + isCustomShardKeyHashed, + presplitHashedZones, + numInitialChunks: + presplitHashedZones && numInitialChunks + ? Number(numInitialChunks) + : null, + }; + + onCreateShardKey(data); + }, [ + isPreSplitData, + numInitialChunks, + secondShardKey, + selectedOption, + onCreateShardKey, + ]); + + return ( +
+
+
+ + +
+
+ + ({ value }))} + className={secondShardKeyStyles} + value={secondShardKey} + renderOption={(option, index, isCustom) => { + return ( + + ); + }} + /> +
+
+ + ) => { + setSelectedOption(event.target.value as ShardingAdvancedOption); + }} + > + + Default + + + Enforce a uniqueness constraint on the shard key of this Global + Collection.{' '} + + Learn more + + + } + > + Use unique index as the shard key + + + Improve even distribution of the sharded data by hashing the + second field of the shard key.{' '} + + Learn more + + + } + > + Use hashed index as the shard key + + + {selectedOption === 'hashed-index' && ( +
+ setIsPreSplitData(!isPreSplitData)} + label="Pre-split data for even distribution." + checked={isPreSplitData} + /> +
+ setNumInitialChunks(event.target.value)} + /> + chunks per shard. +
+
+ )} +
+
+ +
+
+ ); +} + +export function UnshardedState(props: UnshardedStateProps) { + return ( +
+ + + To use Global Writes, this collection must be configured with a + compound shard key made up of both a 'location' field and an + identifier field that you should provide. + + {nbsp}See the instructions below for details. + + + +
+ ); +} + +export default connect( + (state: RootState) => ({ + namespace: state.namespace, + }), + { + onCreateShardKey: createShardKey, + } +)(UnshardedState); diff --git a/packages/compass-global-writes/src/components/states/usharded.spec.tsx b/packages/compass-global-writes/src/components/states/usharded.spec.tsx new file mode 100644 index 00000000000..9bbb4d45fe5 --- /dev/null +++ b/packages/compass-global-writes/src/components/states/usharded.spec.tsx @@ -0,0 +1,193 @@ +import React from 'react'; +import { expect } from 'chai'; +import { screen, userEvent } from '@mongodb-js/testing-library-compass'; +import { UnshardedState } from './unsharded'; +import { renderWithStore } from '../../../tests/create-store'; +import sinon from 'sinon'; + +function renderWithProps( + props?: Partial> +) { + return renderWithStore( + {}} + {...props} + /> + ); +} + +describe('UnshardedState', function () { + it('renders the warning banner', function () { + renderWithProps(); + expect(screen.getByRole('alert')).to.exist; + }); + + it('renders the text to the user', function () { + renderWithProps(); + expect(screen.getByTestId('unsharded-text-description')).to.exist; + }); + + context('shard collection form', function () { + let onCreateShardKeySpy: sinon.SinonSpy; + beforeEach(function () { + onCreateShardKeySpy = sinon.spy(); + renderWithProps({ onCreateShardKey: onCreateShardKeySpy }); + }); + + it('renders location form field as disabled', function () { + expect(screen.getByLabelText('First shard key field')).to.have.attribute( + 'aria-disabled', + 'true' + ); + }); + + it('does not allow user to submit when no second shard key is selected', function () { + expect(screen.getByTestId('shard-collection-button')).to.have.attribute( + 'aria-disabled', + 'true' + ); + + userEvent.click(screen.getByTestId('shard-collection-button')); + expect(onCreateShardKeySpy.called).to.be.false; + }); + + it('allows user to input second shard key and submit it', function () { + const input = screen.getByLabelText('Second shard key field'); + + expect(input).to.exist; + userEvent.type(input, 'name'); + expect(input).to.have.value('name'); + + // Hide the combo box + userEvent.keyboard('{Escape}'); + + userEvent.click(screen.getByTestId('shard-collection-button')); + + expect(onCreateShardKeySpy.calledOnce).to.be.true; + expect(onCreateShardKeySpy.firstCall.args[0]).to.deep.equal({ + customShardKey: 'name', + isShardKeyUnique: false, + isCustomShardKeyHashed: false, + presplitHashedZones: false, + numInitialChunks: null, + }); + }); + + it('renders advanced options and radio buttons for: default, unique-index and hashed index', function () { + const accordian = screen.getByText('Advanced Shard Key Configuration'); + expect(accordian).to.exist; + + userEvent.click(accordian); + + const defaultRadio = screen.getByLabelText('Default'); + const uniqueIndexRadio = screen.getByLabelText( + 'Use unique index as the shard key' + ); + const hashedIndexRadio = screen.getByLabelText( + 'Use hashed index as the shard key' + ); + + expect(defaultRadio).to.exist; + expect(uniqueIndexRadio).to.exist; + expect(hashedIndexRadio).to.exist; + }); + + it('allows user to select unique index as shard key', function () { + const accordian = screen.getByText('Advanced Shard Key Configuration'); + userEvent.click(accordian); + + const uniqueIndexRadio = screen.getByLabelText( + 'Use unique index as the shard key' + ); + userEvent.click(uniqueIndexRadio); + + expect(uniqueIndexRadio).to.have.attribute('aria-checked', 'true'); + + // Enter second shard key and validate submit + const input = screen.getByLabelText('Second shard key field'); + userEvent.type(input, 'name'); + userEvent.keyboard('{Escape}'); + + userEvent.click(screen.getByTestId('shard-collection-button')); + + expect(onCreateShardKeySpy.calledOnce).to.be.true; + expect(onCreateShardKeySpy.firstCall.args[0]).to.deep.equal({ + customShardKey: 'name', + isShardKeyUnique: true, + isCustomShardKeyHashed: false, + presplitHashedZones: false, + numInitialChunks: null, + }); + }); + + it('allows user to select hashed index as shard key with split-chunks option', function () { + const accordian = screen.getByText('Advanced Shard Key Configuration'); + userEvent.click(accordian); + + const hashedIndexRadio = screen.getByLabelText( + 'Use hashed index as the shard key' + ); + userEvent.click(hashedIndexRadio); + + expect(hashedIndexRadio).to.have.attribute('aria-checked', 'true'); + + // Enter second shard key and validate submit + const input = screen.getByLabelText('Second shard key field'); + userEvent.type(input, 'name'); + userEvent.keyboard('{Escape}'); + + // Check pre-split data + userEvent.click(screen.getByTestId('presplit-data-checkbox'), undefined, { + skipPointerEventsCheck: true, + }); + + userEvent.click(screen.getByTestId('shard-collection-button')); + + expect(onCreateShardKeySpy.calledOnce).to.be.true; + expect(onCreateShardKeySpy.firstCall.args[0]).to.deep.equal({ + customShardKey: 'name', + isShardKeyUnique: false, + isCustomShardKeyHashed: true, + presplitHashedZones: true, + numInitialChunks: null, + }); + }); + + it('allows user to select hashed index as shard key with all its options', function () { + const accordian = screen.getByText('Advanced Shard Key Configuration'); + userEvent.click(accordian); + + const hashedIndexRadio = screen.getByLabelText( + 'Use hashed index as the shard key' + ); + userEvent.click(hashedIndexRadio); + + expect(hashedIndexRadio).to.have.attribute('aria-checked', 'true'); + + // Enter second shard key and validate submit + const input = screen.getByLabelText('Second shard key field'); + userEvent.type(input, 'name'); + userEvent.keyboard('{Escape}'); + + // Check pre-split data + userEvent.click(screen.getByTestId('presplit-data-checkbox'), undefined, { + skipPointerEventsCheck: true, + }); + + // Enter number of chunks + userEvent.type(screen.getByTestId('chunks-per-shard-input'), '10'); + + userEvent.click(screen.getByTestId('shard-collection-button')); + + expect(onCreateShardKeySpy.calledOnce).to.be.true; + expect(onCreateShardKeySpy.firstCall.args[0]).to.deep.equal({ + customShardKey: 'name', + isShardKeyUnique: false, + isCustomShardKeyHashed: true, + presplitHashedZones: true, + numInitialChunks: 10, + }); + }); + }); +}); diff --git a/packages/compass-global-writes/src/index.ts b/packages/compass-global-writes/src/index.ts index 754e14a2086..7a000ca91ab 100644 --- a/packages/compass-global-writes/src/index.ts +++ b/packages/compass-global-writes/src/index.ts @@ -1,7 +1,7 @@ import React from 'react'; import { registerHadronPlugin } from 'hadron-app-registry'; -import { GlobalWrites } from './components'; +import GlobalWrites from './components'; import { GlobalWritesTabTitle } from './plugin-title'; import { activateGlobalWritesPlugin } from './store'; import { createLoggerLocator } from '@mongodb-js/compass-logging/provider'; diff --git a/packages/compass-global-writes/src/plugin-title.tsx b/packages/compass-global-writes/src/plugin-title.tsx index 831b50f5c74..e7fd917d6d2 100644 --- a/packages/compass-global-writes/src/plugin-title.tsx +++ b/packages/compass-global-writes/src/plugin-title.tsx @@ -62,8 +62,8 @@ const PluginTitle = ({ showWarning }: { showWarning: boolean }) => { Collections in Atlas Global Clusters with Atlas-managed sharding must be configured with a compound shard key made up of both a - ‘location’ field and an identifier field that you provide. Please - configure sharding here. + 'location' field and an identifier field that you provide. + Please configure sharding here. )} diff --git a/packages/compass-global-writes/src/store/index.ts b/packages/compass-global-writes/src/store/index.ts index a27c204293b..334034a52a6 100644 --- a/packages/compass-global-writes/src/store/index.ts +++ b/packages/compass-global-writes/src/store/index.ts @@ -1,4 +1,4 @@ -import { createStore, applyMiddleware, type Action } from 'redux'; +import { createStore, applyMiddleware, type Action, type Store } from 'redux'; import thunk from 'redux-thunk'; import type { ActivateHelpers } from 'hadron-app-registry'; import type { Logger } from '@mongodb-js/compass-logging'; @@ -6,7 +6,7 @@ import type { TrackFunction } from '@mongodb-js/compass-telemetry'; import type { ConnectionInfoRef } from '@mongodb-js/compass-connections/provider'; import type { CollectionTabPluginMetadata } from '@mongodb-js/compass-collection'; import type { AtlasService } from '@mongodb-js/atlas-service/provider'; -import type { ThunkAction } from 'redux-thunk'; +import type { ThunkAction, ThunkDispatch } from 'redux-thunk'; import reducer, { ShardingStatuses, @@ -28,15 +28,24 @@ export type GlobalWritesThunkAction = ThunkAction< GlobalWritesExtraArgs, A >; +export type GlobalWritesThunkDispatch
= + ThunkDispatch; -type GlobalWritesPluginOptions = CollectionTabPluginMetadata; -type GlobalWritesPluginServices = Pick< +export type GlobalWritesPluginOptions = Pick< + CollectionTabPluginMetadata, + 'namespace' +>; +export type GlobalWritesPluginServices = Pick< GlobalWritesExtraArgs, 'logger' | 'track' | 'connectionInfoRef' > & { atlasService: AtlasService; }; +export type GlobalWritesStore = Store & { + dispatch: GlobalWritesThunkDispatch; +}; + export function activateGlobalWritesPlugin( options: GlobalWritesPluginOptions, { @@ -48,7 +57,7 @@ export function activateGlobalWritesPlugin( { cleanup }: ActivateHelpers ) { const atlasGlobalWritesService = new AtlasGlobalWritesService(atlasService); - const store = createStore( + const store: GlobalWritesStore = createStore( reducer, { namespace: options.namespace, diff --git a/packages/compass-global-writes/tests/cluster-api-response.json b/packages/compass-global-writes/tests/cluster-api-response.json new file mode 100644 index 00000000000..0d6a161bf1e --- /dev/null +++ b/packages/compass-global-writes/tests/cluster-api-response.json @@ -0,0 +1,337 @@ +{ + "@provider": "AWS", + "acceptDataRisksAndForceReplicaSetReconfig": null, + "backupEnabled": false, + "biConnector": { + "enabled": false, + "privateHostname": null, + "privateLinkHostnamesMap": {}, + "publicHostname": null, + "readPreference": "secondary" + }, + "clusterTags": [], + "clusterType": "GEOSHARDED", + "configServerManagementMode": "ATLAS_MANAGED", + "configServerType": "DEDICATED", + "continuousDeliveryFCV": null, + "createDate": "2024-08-29T17:05:18Z", + "dataProcessingRegion": null, + "deleteAfterDate": "2024-09-20T05:05:18Z", + "deploymentClusterName": "atlas-rnqv3e", + "deploymentItemName": "atlas-rnqv3e", + "diskBackupEnabled": true, + "diskSizeGB": 10.0, + "diskWarmingMode": "FULLY_WARMED", + "employeeAccessGrant": null, + "encryptionAtRestProvider": "NONE", + "endpointToLoadBalancedSRVConnectionURI": {}, + "fixedMongoDBFCV": null, + "fixedMongoDBFCVExpiration": null, + "forceReplicaSetReconfig": false, + "geoSharding": { + "customZoneMapping": {}, + "managedNamespaces": [ + { + "collection": "listingsAndReviews", + "customShardKey": "address.country", + "db": "sample_airbnb", + "isCustomShardKeyHashed": false, + "isShardKeyUnique": false, + "numInitialChunks": null, + "presplitHashedZones": false + } + ], + "selfManagedSharding": false + }, + "groupId": "66bb81dafe547055785904a3", + "hostnameSchemeForAgents": "INTERNAL", + "hostnameSubdomainLevel": "MONGODB", + "internalClusterRole": "NONE", + "isCrossCloudCluster": false, + "isFastProvisioned": false, + "isMongoDBVersionFixed": false, + "isMonitoringPaused": false, + "isNvme": false, + "isPaused": false, + "isUnderCompaction": false, + "labels": [], + "lastUpdateDate": "2024-09-13T12:14:58Z", + "maxIncomingConns": 3000, + "mongoDBMajorVersion": "8.0", + "mongoDBUriHosts": [ + "cluster1-shard-00-00.zwcs5.mongodb-dev.net:27016", + "cluster1-shard-00-01.zwcs5.mongodb-dev.net:27016", + "cluster1-shard-00-02.zwcs5.mongodb-dev.net:27016" + ], + "mongoDBUriHostsLastUpdateDate": "2024-08-29T17:26:41Z", + "mongoDBVersion": "8.0.0-rc20", + "name": "Cluster1", + "needsMongoDBConfigPublishAfter": "2024-09-13T13:40:29Z", + "needsSampleDataLoadAfter": null, + "osPolicyVersion": "169", + "pitEnabled": true, + "privateLinkMongoDBUriHosts": {}, + "privateLinkSrvAddresses": {}, + "privateMongoDBUriHosts": [ + "cluster1-shard-00-00-pri.zwcs5.mongodb-dev.net:27016", + "cluster1-shard-00-01-pri.zwcs5.mongodb-dev.net:27016", + "cluster1-shard-00-02-pri.zwcs5.mongodb-dev.net:27016" + ], + "privateSrvAddress": "cluster1-pri.zwcs5.mongodb-dev.net", + "redactClientLogData": false, + "replicaSetScalingStrategy": null, + "replicationSpecList": [ + { + "id": "66d0aa1b85ebe5234e75b411", + "numShards": 1, + "regionConfigs": [ + { + "analyticsAutoScaling": { + "autoIndex": { + "enabled": false + }, + "compute": { + "enabled": true, + "maxInstanceSize": "M40", + "minInstanceSize": "M30", + "scaleDownEnabled": true + }, + "diskGB": { + "enabled": true + } + }, + "analyticsSpecs": { + "diskIOPS": 3000, + "diskThroughput": 125, + "encryptEBSVolume": true, + "instanceSize": "M30", + "nodeCount": 0, + "preferredCpuArchitecture": "arm64", + "volumeType": "gp3" + }, + "autoScaling": { + "autoIndex": { + "enabled": false + }, + "compute": { + "enabled": true, + "maxInstanceSize": "M40", + "minInstanceSize": "M30", + "scaleDownEnabled": true + }, + "diskGB": { + "enabled": true + } + }, + "cloudProvider": "AWS", + "electableSpecs": { + "diskIOPS": 3000, + "diskThroughput": 125, + "encryptEBSVolume": true, + "instanceSize": "M30", + "nodeCount": 3, + "preferredCpuArchitecture": "arm64", + "volumeType": "gp3" + }, + "priority": 7, + "readOnlySpecs": { + "diskIOPS": 3000, + "diskThroughput": 125, + "encryptEBSVolume": true, + "instanceSize": "M30", + "nodeCount": 0, + "preferredCpuArchitecture": "arm64", + "volumeType": "gp3" + }, + "regionName": "US_EAST_1", + "regionView": { + "complianceLevel": "COMMERCIAL", + "continent": "North America", + "isRecommended": true, + "key": "US_EAST_1", + "latitude": 39.0437567, + "location": "N. Virginia", + "longitude": -77.4874416, + "name": "us-east-1", + "provider": "AWS" + } + } + ], + "zoneId": "66d0aa4e49898e6af5e43159", + "zoneName": "Americas" + }, + { + "id": "66d0aa1b85ebe5234e75b414", + "numShards": 1, + "regionConfigs": [ + { + "analyticsAutoScaling": { + "autoIndex": { + "enabled": false + }, + "compute": { + "enabled": true, + "maxInstanceSize": "M40", + "minInstanceSize": "M30", + "scaleDownEnabled": true + }, + "diskGB": { + "enabled": true + } + }, + "analyticsSpecs": { + "diskIOPS": 3000, + "diskThroughput": 125, + "encryptEBSVolume": true, + "instanceSize": "M30", + "nodeCount": 0, + "preferredCpuArchitecture": "arm64", + "volumeType": "gp3" + }, + "autoScaling": { + "autoIndex": { + "enabled": false + }, + "compute": { + "enabled": true, + "maxInstanceSize": "M40", + "minInstanceSize": "M30", + "scaleDownEnabled": true + }, + "diskGB": { + "enabled": true + } + }, + "cloudProvider": "AWS", + "electableSpecs": { + "diskIOPS": 3000, + "diskThroughput": 125, + "encryptEBSVolume": true, + "instanceSize": "M30", + "nodeCount": 3, + "preferredCpuArchitecture": "arm64", + "volumeType": "gp3" + }, + "priority": 7, + "readOnlySpecs": { + "diskIOPS": 3000, + "diskThroughput": 125, + "encryptEBSVolume": true, + "instanceSize": "M30", + "nodeCount": 0, + "preferredCpuArchitecture": "arm64", + "volumeType": "gp3" + }, + "regionName": "EU_CENTRAL_1", + "regionView": { + "complianceLevel": "COMMERCIAL", + "continent": "Europe", + "isRecommended": true, + "key": "EU_CENTRAL_1", + "latitude": 50.110924, + "location": "Frankfurt", + "longitude": 8.682127, + "name": "eu-central-1", + "provider": "AWS" + } + } + ], + "zoneId": "66d0aa4e49898e6af5e4315a", + "zoneName": "EMEA" + }, + { + "id": "66d0aa1b85ebe5234e75b417", + "numShards": 1, + "regionConfigs": [ + { + "analyticsAutoScaling": { + "autoIndex": { + "enabled": false + }, + "compute": { + "enabled": true, + "maxInstanceSize": "M40", + "minInstanceSize": "M30", + "scaleDownEnabled": true + }, + "diskGB": { + "enabled": true + } + }, + "analyticsSpecs": { + "diskIOPS": 3000, + "diskThroughput": 125, + "encryptEBSVolume": true, + "instanceSize": "M30", + "nodeCount": 0, + "preferredCpuArchitecture": "arm64", + "volumeType": "gp3" + }, + "autoScaling": { + "autoIndex": { + "enabled": false + }, + "compute": { + "enabled": true, + "maxInstanceSize": "M40", + "minInstanceSize": "M30", + "scaleDownEnabled": true + }, + "diskGB": { + "enabled": true + } + }, + "cloudProvider": "AWS", + "electableSpecs": { + "diskIOPS": 3000, + "diskThroughput": 125, + "encryptEBSVolume": true, + "instanceSize": "M30", + "nodeCount": 3, + "preferredCpuArchitecture": "arm64", + "volumeType": "gp3" + }, + "priority": 7, + "readOnlySpecs": { + "diskIOPS": 3000, + "diskThroughput": 125, + "encryptEBSVolume": true, + "instanceSize": "M30", + "nodeCount": 0, + "preferredCpuArchitecture": "arm64", + "volumeType": "gp3" + }, + "regionName": "AP_SOUTHEAST_1", + "regionView": { + "complianceLevel": "COMMERCIAL", + "continent": "Asia", + "isRecommended": true, + "key": "AP_SOUTHEAST_1", + "latitude": 1.29027, + "location": "Singapore", + "longitude": 103.851959, + "name": "ap-southeast-1", + "provider": "AWS" + } + } + ], + "zoneId": "66d0aa4e49898e6af5e4315b", + "zoneName": "APAC" + } + ], + "resurrectOptions": null, + "resurrectRequested": null, + "retainBackupsForDeleting": null, + "rootCertType": "ISRGROOTX1", + "sampleDatasetToLoad": "sampledata", + "searchDeploymentRequest": null, + "serverlessBackupOptions": null, + "srvAddress": "cluster1.zwcs5.mongodb-dev.net", + "state": "UPDATING", + "tenantAccessRevokedForPause": false, + "tenantUpgrading": false, + "terminationProtectionEnabled": false, + "uniqueId": "66d0aa4e49898e6af5e43174", + "userNotifiedAboutPauseDate": null, + "versionReleaseSystem": "LTS" +} diff --git a/packages/compass-global-writes/tests/create-store.tsx b/packages/compass-global-writes/tests/create-store.tsx new file mode 100644 index 00000000000..91ec891f24f --- /dev/null +++ b/packages/compass-global-writes/tests/create-store.tsx @@ -0,0 +1,80 @@ +import React from 'react'; +import type { + GlobalWritesPluginOptions, + GlobalWritesPluginServices, +} from '../src/store'; +import { activateGlobalWritesPlugin } from '../src/store'; +import { createActivateHelpers } from 'hadron-app-registry'; +import { createNoopLogger } from '@mongodb-js/compass-logging/provider'; +import { createNoopTrack } from '@mongodb-js/compass-telemetry/provider'; +import type { ConnectionInfoRef } from '@mongodb-js/compass-connections/provider'; +import type { AtlasService } from '@mongodb-js/atlas-service/provider'; +import { Provider } from 'react-redux'; +import { render } from '@mongodb-js/testing-library-compass'; + +import clusterApiResponse from './cluster-api-response.json'; + +const atlasService = { + cloudEndpoint: (p: string) => { + return `https://example.com/${p}`; + }, + authenticatedFetch: (url: RequestInfo | URL) => { + // + if (url.toString().endsWith('nds/clusters/Project0/Cluster0')) { + return Promise.resolve({ + status: 200, + // eslint-disable-next-line @typescript-eslint/require-await + json: async () => clusterApiResponse, + } as Response); + } + return Promise.resolve({ + status: 200, + // eslint-disable-next-line @typescript-eslint/require-await + json: async () => ({}), + } as Response); + }, +} as unknown as AtlasService; + +export const setupStore = ( + options: Partial = {}, + services: Partial = {} +) => { + const connectionInfoRef = { + current: { + id: 'TEST', + atlasMetadata: { + clusterName: 'Cluster0', + clusterType: 'GEOSHARDED', + projectId: 'Project0', + }, + }, + } as ConnectionInfoRef; + + return activateGlobalWritesPlugin( + { + namespace: 'airbnb.listings', + ...options, + }, + { + logger: createNoopLogger('TEST'), + track: createNoopTrack(), + connectionInfoRef, + ...services, + atlasService: { + ...atlasService, + ...services.atlasService, + } as AtlasService, + }, + createActivateHelpers() + ).store; +}; + +export const renderWithStore = ( + component: JSX.Element, + services: Partial = {}, + options: Partial = {} +) => { + const store = setupStore(options, services); + render({component}); + return store; +}; From e4ab4a412817c9e8ccc2817666010236615a5177 Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Wed, 25 Sep 2024 23:08:34 +0200 Subject: [PATCH 15/37] sharding in progress --- .../src/components/index.tsx | 3 + .../src/components/states/sharding.tsx | 29 ++++ .../src/components/states/unsharded.tsx | 8 +- .../src/components/states/usharded.spec.tsx | 1 + .../services/atlas-global-writes-service.ts | 101 ++++++++++---- .../src/store/reducer.ts | 130 +++++++++++++++++- 6 files changed, 244 insertions(+), 28 deletions(-) create mode 100644 packages/compass-global-writes/src/components/states/sharding.tsx diff --git a/packages/compass-global-writes/src/components/index.tsx b/packages/compass-global-writes/src/components/index.tsx index 5144f2a82a6..01505d64513 100644 --- a/packages/compass-global-writes/src/components/index.tsx +++ b/packages/compass-global-writes/src/components/index.tsx @@ -10,6 +10,7 @@ import { import type { RootState, ShardingStatus } from '../store/reducer'; import { ShardingStatuses } from '../store/reducer'; import UnshardedState from './states/unsharded'; +import ShardingState from './states/sharding'; const containerStyles = css({ paddingLeft: spacing[3], @@ -44,6 +45,8 @@ function getStateViewBasedOnShardingStatus(shardingStatus: ShardingStatus) { ); case ShardingStatuses.UNSHARDED: return ; + case ShardingStatuses.SHARDING: + return ; default: return null; } diff --git a/packages/compass-global-writes/src/components/states/sharding.tsx b/packages/compass-global-writes/src/components/states/sharding.tsx new file mode 100644 index 00000000000..27408a1286a --- /dev/null +++ b/packages/compass-global-writes/src/components/states/sharding.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { + Banner, + BannerVariant, + css, + spacing, +} from '@mongodb-js/compass-components'; +import { connect } from 'react-redux'; + +const nbsp = '\u00a0'; + +const containerStyles = css({ + display: 'flex', + flexDirection: 'column', + gap: spacing[400], +}); + +export function ShardingState() { + return ( +
+ + Sharding your collection ... + {nbsp}this should not take too long. + +
+ ); +} + +export default connect()(ShardingState); diff --git a/packages/compass-global-writes/src/components/states/unsharded.tsx b/packages/compass-global-writes/src/components/states/unsharded.tsx index ac15848ba0d..0ffa62e2ed9 100644 --- a/packages/compass-global-writes/src/components/states/unsharded.tsx +++ b/packages/compass-global-writes/src/components/states/unsharded.tsx @@ -112,13 +112,15 @@ function CreateShardKeyDescription() { type ShardingAdvancedOption = 'default' | 'unique-index' | 'hashed-index'; type UnshardedStateProps = { namespace: string; + isLoading: boolean; onCreateShardKey: (data: CreateShardKeyData) => void; }; function CreateShardKeyForm({ namespace, + isLoading, onCreateShardKey, -}: Pick) { +}: Pick) { const [isAdvancedOptionsOpen, setIsAdvancedOptionsOpen] = useState(false); const [selectedOption, setSelectedOption] = useState('default'); @@ -286,8 +288,9 @@ function CreateShardKeyForm({ @@ -316,6 +319,7 @@ export function UnshardedState(props: UnshardedStateProps) { export default connect( (state: RootState) => ({ namespace: state.namespace, + isLoading: state.createShardkey.isLoading, }), { onCreateShardKey: createShardKey, diff --git a/packages/compass-global-writes/src/components/states/usharded.spec.tsx b/packages/compass-global-writes/src/components/states/usharded.spec.tsx index 9bbb4d45fe5..2d4f5f971bc 100644 --- a/packages/compass-global-writes/src/components/states/usharded.spec.tsx +++ b/packages/compass-global-writes/src/components/states/usharded.spec.tsx @@ -10,6 +10,7 @@ function renderWithProps( ) { return renderWithStore( {}} {...props} diff --git a/packages/compass-global-writes/src/services/atlas-global-writes-service.ts b/packages/compass-global-writes/src/services/atlas-global-writes-service.ts index d75e03bd05a..3067c51e584 100644 --- a/packages/compass-global-writes/src/services/atlas-global-writes-service.ts +++ b/packages/compass-global-writes/src/services/atlas-global-writes-service.ts @@ -1,58 +1,109 @@ -import type { AtlasService } from '@mongodb-js/atlas-service/provider'; import toNS from 'mongodb-ns'; +import type { AtlasService } from '@mongodb-js/atlas-service/provider'; +import type { CreateShardKeyData } from '../store/reducer'; -export type ManagedNamespace = { +type ZoneMapping = unknown; +type ManagedNamespace = { db: string; collection: string; customShardKey: string; isCustomShardKeyHashed: boolean; isShardKeyUnique: boolean; - numInitialChunks?: number; + numInitialChunks: number | null; presplitHashedZones: boolean; }; +type GeoShardingData = { + customZoneMapping: Record; + managedNamespaces: ManagedNamespace[]; + selfManagedSharding: boolean; +}; + type ClusterDetailsApiResponse = { - geoSharding: { - customZoneMapping: unknown; - managedNamespaces: ManagedNamespace[]; - }; + geoSharding: GeoShardingData; +}; + +type AtlasCluterInfo = { + projectId: string; + clusterName: string; }; function assertDataIsClusterDetailsApiResponse( data: any ): asserts data is ClusterDetailsApiResponse { if (!Array.isArray(data?.geoSharding?.managedNamespaces)) { - throw new Error('Invalid cluster details API response'); + throw new Error( + 'Invalid cluster details API response geoSharding.managedNamespaces' + ); + } + if (typeof data?.geoSharding?.customZoneMapping !== 'object') { + throw new Error( + 'Invalid cluster details API response geoSharding.customZoneMapping' + ); } } export class AtlasGlobalWritesService { constructor(private atlasService: AtlasService) {} - async isNamespaceManaged( - namespace: string, - { - clusterName, - projectId, - }: { - projectId: string; - clusterName: string; - } - ) { + private async fetchClusterDetails({ + clusterName, + projectId, + }: AtlasCluterInfo): Promise { const uri = this.atlasService.cloudEndpoint( `nds/clusters/${projectId}/${clusterName}` ); const response = await this.atlasService.authenticatedFetch(uri); - const cluster = await response.json(); + const clusterDetails = await response.json(); + assertDataIsClusterDetailsApiResponse(clusterDetails); + return clusterDetails; + } - assertDataIsClusterDetailsApiResponse(cluster); + async isNamespaceManaged( + namespace: string, + atlasClusterInfo: AtlasCluterInfo + ) { + const clusterData = await this.fetchClusterDetails(atlasClusterInfo); + const { database, collection } = toNS(namespace); + return clusterData.geoSharding.managedNamespaces.some( + (managedNamespace) => { + return ( + managedNamespace.db === database && + managedNamespace.collection === collection + ); + } + ); + } + + async createShardKey( + namespace: string, + keyData: CreateShardKeyData, + atlasClusterInfo: AtlasCluterInfo + ) { + const clusterData = await this.fetchClusterDetails(atlasClusterInfo); const { database, collection } = toNS(namespace); - return cluster.geoSharding.managedNamespaces.some((managedNamespace) => { - return ( - managedNamespace.db === database && - managedNamespace.collection === collection - ); + + const requestData: GeoShardingData = { + ...clusterData.geoSharding, + managedNamespaces: [ + ...clusterData.geoSharding.managedNamespaces, + { + db: database, + collection: collection, + ...keyData, + }, + ], + }; + + const uri = this.atlasService.cloudEndpoint( + `nds/clusters/${atlasClusterInfo.projectId}/${atlasClusterInfo.clusterName}/geoSharding` + ); + + const response = await this.atlasService.authenticatedFetch(uri, { + method: 'PATCH', + body: JSON.stringify(requestData), }); + assertDataIsClusterDetailsApiResponse(await response.json()); } } diff --git a/packages/compass-global-writes/src/store/reducer.ts b/packages/compass-global-writes/src/store/reducer.ts index 498780fe93e..1f5e9bfc4d8 100644 --- a/packages/compass-global-writes/src/store/reducer.ts +++ b/packages/compass-global-writes/src/store/reducer.ts @@ -8,8 +8,19 @@ export function isAction
( return action.type === type; } +export type CreateShardKeyData = { + customShardKey: string; + isShardKeyUnique: boolean; + isCustomShardKeyHashed: boolean; + presplitHashedZones: boolean; + numInitialChunks: number | null; +}; + enum GlobalWritesActionTypes { SetIsManagedNamespace = 'global-writes/SetIsManagedNamespace', + ShardingInProgressStarted = 'global-writes/ShardingInProgressStarted', + ShardingInProgressFinished = 'global-writes/ShardingInProgressFinished', + ShardingInProgressErrored = 'global-writes/ShardingInProgressErrored', } type SetIsManagedNamespaceAction = { @@ -17,6 +28,19 @@ type SetIsManagedNamespaceAction = { isNamespaceManaged: boolean; }; +type ShardingInProgressStartedAction = { + type: GlobalWritesActionTypes.ShardingInProgressStarted; +}; + +type ShardingInProgressFinishedAction = { + type: GlobalWritesActionTypes.ShardingInProgressFinished; +}; + +type ShardingInProgressErroredAction = { + type: GlobalWritesActionTypes.ShardingInProgressErrored; + error: Error; +}; + export enum ShardingStatuses { /** * Initial status, no information available yet. @@ -27,18 +51,33 @@ export enum ShardingStatuses { * Namespace is not geo-sharded. */ UNSHARDED = 'UNSHARDED', + + /** + * Namespace is being sharded. + */ + SHARDING = 'SHARDING', } +export type ShardingStatus = keyof typeof ShardingStatuses; + export type RootState = { namespace: string; isNamespaceSharded: boolean; - status: keyof typeof ShardingStatuses; + status: ShardingStatus; + createShardkey: { + isLoading: boolean; + error: Error | null; + }; }; const initialState: RootState = { namespace: '', isNamespaceSharded: false, status: ShardingStatuses.NOT_READY, + createShardkey: { + isLoading: false, + error: null, + }, }; const reducer: Reducer = (state = initialState, action) => { @@ -56,6 +95,54 @@ const reducer: Reducer = (state = initialState, action) => { : state.status, }; } + + if ( + isAction( + action, + GlobalWritesActionTypes.ShardingInProgressStarted + ) + ) { + return { + ...state, + createShardkey: { + isLoading: true, + error: null, + }, + }; + } + + if ( + isAction( + action, + GlobalWritesActionTypes.ShardingInProgressFinished + ) + ) { + return { + ...state, + isNamespaceSharded: true, + status: ShardingStatuses.SHARDING, + createShardkey: { + isLoading: false, + error: null, + }, + }; + } + + if ( + isAction( + action, + GlobalWritesActionTypes.ShardingInProgressErrored + ) + ) { + return { + ...state, + createShardkey: { + isLoading: false, + error: action.error, + }, + }; + } + return state; }; @@ -94,4 +181,45 @@ export const fetchClusterShardingData = return; }; +export const createShardKey = + ( + data: CreateShardKeyData + ): GlobalWritesThunkAction< + Promise, + | ShardingInProgressStartedAction + | ShardingInProgressFinishedAction + | ShardingInProgressErroredAction + > => + async ( + dispatch, + getState, + { connectionInfoRef, atlasGlobalWritesService } + ) => { + if (!connectionInfoRef.current.atlasMetadata) { + return; + } + + const { namespace } = getState(); + const { clusterName, projectId } = connectionInfoRef.current.atlasMetadata; + + dispatch({ + type: GlobalWritesActionTypes.ShardingInProgressStarted, + }); + + try { + await atlasGlobalWritesService.createShardKey(namespace, data, { + projectId, + clusterName, + }); + dispatch({ + type: GlobalWritesActionTypes.ShardingInProgressFinished, + }); + } catch (error) { + dispatch({ + type: GlobalWritesActionTypes.ShardingInProgressErrored, + error: error as Error, + }); + } + }; + export default reducer; From d6790e7355088fcc05b05eb4cca8993e0a6f4b6a Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Thu, 26 Sep 2024 00:26:58 +0200 Subject: [PATCH 16/37] small clean up --- .../src/components/index.spec.tsx | 5 + .../src/components/states/sharding.spec.tsx | 18 + .../services/atlas-global-writes-service.ts | 2 +- .../compass-global-writes/src/store/index.ts | 4 + .../src/store/reducer.ts | 22 +- .../tests/cluster-api-response.json | 322 +----------------- .../tests/create-store.tsx | 1 - 7 files changed, 40 insertions(+), 334 deletions(-) create mode 100644 packages/compass-global-writes/src/components/states/sharding.spec.tsx diff --git a/packages/compass-global-writes/src/components/index.spec.tsx b/packages/compass-global-writes/src/components/index.spec.tsx index c60d5680849..c6a0b18ef43 100644 --- a/packages/compass-global-writes/src/components/index.spec.tsx +++ b/packages/compass-global-writes/src/components/index.spec.tsx @@ -14,4 +14,9 @@ describe('Compass GlobalWrites Plugin', function () { renderWithStore(); expect(screen.getByTestId('shard-collection-button')).to.exist; }); + + it('renders plugin in SHARDING state', function () { + renderWithStore(); + expect(screen.getByText(/sharding your collection/i)).to.exist; + }); }); diff --git a/packages/compass-global-writes/src/components/states/sharding.spec.tsx b/packages/compass-global-writes/src/components/states/sharding.spec.tsx new file mode 100644 index 00000000000..beb6e372928 --- /dev/null +++ b/packages/compass-global-writes/src/components/states/sharding.spec.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import { expect } from 'chai'; +import { screen } from '@mongodb-js/testing-library-compass'; +import { ShardingState } from './sharding'; +import { renderWithStore } from '../../../tests/create-store'; + +function renderWithProps( + props?: Partial> +) { + return renderWithStore(); +} + +describe('Sharding', function () { + it('renders the info banner', function () { + renderWithProps(); + expect(screen.getByRole('alert')).to.exist; + }); +}); diff --git a/packages/compass-global-writes/src/services/atlas-global-writes-service.ts b/packages/compass-global-writes/src/services/atlas-global-writes-service.ts index 3067c51e584..6ff8fe8f301 100644 --- a/packages/compass-global-writes/src/services/atlas-global-writes-service.ts +++ b/packages/compass-global-writes/src/services/atlas-global-writes-service.ts @@ -3,7 +3,7 @@ import type { AtlasService } from '@mongodb-js/atlas-service/provider'; import type { CreateShardKeyData } from '../store/reducer'; type ZoneMapping = unknown; -type ManagedNamespace = { +export type ManagedNamespace = { db: string; collection: string; customShardKey: string; diff --git a/packages/compass-global-writes/src/store/index.ts b/packages/compass-global-writes/src/store/index.ts index 334034a52a6..4409bf562d6 100644 --- a/packages/compass-global-writes/src/store/index.ts +++ b/packages/compass-global-writes/src/store/index.ts @@ -63,6 +63,10 @@ export function activateGlobalWritesPlugin( namespace: options.namespace, isNamespaceSharded: false, status: ShardingStatuses.NOT_READY, + createShardkey: { + error: null, + isLoading: false, + }, }, applyMiddleware( thunk.withExtraArgument({ diff --git a/packages/compass-global-writes/src/store/reducer.ts b/packages/compass-global-writes/src/store/reducer.ts index 1f5e9bfc4d8..ac7f349c0eb 100644 --- a/packages/compass-global-writes/src/store/reducer.ts +++ b/packages/compass-global-writes/src/store/reducer.ts @@ -1,5 +1,6 @@ import type { Action, Reducer } from 'redux'; import type { GlobalWritesThunkAction } from '.'; +import type { ManagedNamespace } from '../services/atlas-global-writes-service'; export function isAction( action: Action, @@ -8,13 +9,14 @@ export function isAction( return action.type === type; } -export type CreateShardKeyData = { - customShardKey: string; - isShardKeyUnique: boolean; - isCustomShardKeyHashed: boolean; - presplitHashedZones: boolean; - numInitialChunks: number | null; -}; +export type CreateShardKeyData = Pick< + ManagedNamespace, + | 'customShardKey' + | 'isCustomShardKeyHashed' + | 'isShardKeyUnique' + | 'numInitialChunks' + | 'presplitHashedZones' +>; enum GlobalWritesActionTypes { SetIsManagedNamespace = 'global-writes/SetIsManagedNamespace', @@ -172,13 +174,11 @@ export const fetchClusterShardingData = if (!isNamespaceManaged) { dispatch({ type: GlobalWritesActionTypes.SetIsManagedNamespace, - isNamespaceManaged: false, + isNamespaceManaged, }); return; } - - // Now fetch the sharding key and possible process error. - return; + // TODO (COMPASS-8277): Now fetch the sharding key and possible process error. }; export const createShardKey = diff --git a/packages/compass-global-writes/tests/cluster-api-response.json b/packages/compass-global-writes/tests/cluster-api-response.json index 0d6a161bf1e..1f8c12ab7ed 100644 --- a/packages/compass-global-writes/tests/cluster-api-response.json +++ b/packages/compass-global-writes/tests/cluster-api-response.json @@ -1,33 +1,4 @@ { - "@provider": "AWS", - "acceptDataRisksAndForceReplicaSetReconfig": null, - "backupEnabled": false, - "biConnector": { - "enabled": false, - "privateHostname": null, - "privateLinkHostnamesMap": {}, - "publicHostname": null, - "readPreference": "secondary" - }, - "clusterTags": [], - "clusterType": "GEOSHARDED", - "configServerManagementMode": "ATLAS_MANAGED", - "configServerType": "DEDICATED", - "continuousDeliveryFCV": null, - "createDate": "2024-08-29T17:05:18Z", - "dataProcessingRegion": null, - "deleteAfterDate": "2024-09-20T05:05:18Z", - "deploymentClusterName": "atlas-rnqv3e", - "deploymentItemName": "atlas-rnqv3e", - "diskBackupEnabled": true, - "diskSizeGB": 10.0, - "diskWarmingMode": "FULLY_WARMED", - "employeeAccessGrant": null, - "encryptionAtRestProvider": "NONE", - "endpointToLoadBalancedSRVConnectionURI": {}, - "fixedMongoDBFCV": null, - "fixedMongoDBFCVExpiration": null, - "forceReplicaSetReconfig": false, "geoSharding": { "customZoneMapping": {}, "managedNamespaces": [ @@ -42,296 +13,5 @@ } ], "selfManagedSharding": false - }, - "groupId": "66bb81dafe547055785904a3", - "hostnameSchemeForAgents": "INTERNAL", - "hostnameSubdomainLevel": "MONGODB", - "internalClusterRole": "NONE", - "isCrossCloudCluster": false, - "isFastProvisioned": false, - "isMongoDBVersionFixed": false, - "isMonitoringPaused": false, - "isNvme": false, - "isPaused": false, - "isUnderCompaction": false, - "labels": [], - "lastUpdateDate": "2024-09-13T12:14:58Z", - "maxIncomingConns": 3000, - "mongoDBMajorVersion": "8.0", - "mongoDBUriHosts": [ - "cluster1-shard-00-00.zwcs5.mongodb-dev.net:27016", - "cluster1-shard-00-01.zwcs5.mongodb-dev.net:27016", - "cluster1-shard-00-02.zwcs5.mongodb-dev.net:27016" - ], - "mongoDBUriHostsLastUpdateDate": "2024-08-29T17:26:41Z", - "mongoDBVersion": "8.0.0-rc20", - "name": "Cluster1", - "needsMongoDBConfigPublishAfter": "2024-09-13T13:40:29Z", - "needsSampleDataLoadAfter": null, - "osPolicyVersion": "169", - "pitEnabled": true, - "privateLinkMongoDBUriHosts": {}, - "privateLinkSrvAddresses": {}, - "privateMongoDBUriHosts": [ - "cluster1-shard-00-00-pri.zwcs5.mongodb-dev.net:27016", - "cluster1-shard-00-01-pri.zwcs5.mongodb-dev.net:27016", - "cluster1-shard-00-02-pri.zwcs5.mongodb-dev.net:27016" - ], - "privateSrvAddress": "cluster1-pri.zwcs5.mongodb-dev.net", - "redactClientLogData": false, - "replicaSetScalingStrategy": null, - "replicationSpecList": [ - { - "id": "66d0aa1b85ebe5234e75b411", - "numShards": 1, - "regionConfigs": [ - { - "analyticsAutoScaling": { - "autoIndex": { - "enabled": false - }, - "compute": { - "enabled": true, - "maxInstanceSize": "M40", - "minInstanceSize": "M30", - "scaleDownEnabled": true - }, - "diskGB": { - "enabled": true - } - }, - "analyticsSpecs": { - "diskIOPS": 3000, - "diskThroughput": 125, - "encryptEBSVolume": true, - "instanceSize": "M30", - "nodeCount": 0, - "preferredCpuArchitecture": "arm64", - "volumeType": "gp3" - }, - "autoScaling": { - "autoIndex": { - "enabled": false - }, - "compute": { - "enabled": true, - "maxInstanceSize": "M40", - "minInstanceSize": "M30", - "scaleDownEnabled": true - }, - "diskGB": { - "enabled": true - } - }, - "cloudProvider": "AWS", - "electableSpecs": { - "diskIOPS": 3000, - "diskThroughput": 125, - "encryptEBSVolume": true, - "instanceSize": "M30", - "nodeCount": 3, - "preferredCpuArchitecture": "arm64", - "volumeType": "gp3" - }, - "priority": 7, - "readOnlySpecs": { - "diskIOPS": 3000, - "diskThroughput": 125, - "encryptEBSVolume": true, - "instanceSize": "M30", - "nodeCount": 0, - "preferredCpuArchitecture": "arm64", - "volumeType": "gp3" - }, - "regionName": "US_EAST_1", - "regionView": { - "complianceLevel": "COMMERCIAL", - "continent": "North America", - "isRecommended": true, - "key": "US_EAST_1", - "latitude": 39.0437567, - "location": "N. Virginia", - "longitude": -77.4874416, - "name": "us-east-1", - "provider": "AWS" - } - } - ], - "zoneId": "66d0aa4e49898e6af5e43159", - "zoneName": "Americas" - }, - { - "id": "66d0aa1b85ebe5234e75b414", - "numShards": 1, - "regionConfigs": [ - { - "analyticsAutoScaling": { - "autoIndex": { - "enabled": false - }, - "compute": { - "enabled": true, - "maxInstanceSize": "M40", - "minInstanceSize": "M30", - "scaleDownEnabled": true - }, - "diskGB": { - "enabled": true - } - }, - "analyticsSpecs": { - "diskIOPS": 3000, - "diskThroughput": 125, - "encryptEBSVolume": true, - "instanceSize": "M30", - "nodeCount": 0, - "preferredCpuArchitecture": "arm64", - "volumeType": "gp3" - }, - "autoScaling": { - "autoIndex": { - "enabled": false - }, - "compute": { - "enabled": true, - "maxInstanceSize": "M40", - "minInstanceSize": "M30", - "scaleDownEnabled": true - }, - "diskGB": { - "enabled": true - } - }, - "cloudProvider": "AWS", - "electableSpecs": { - "diskIOPS": 3000, - "diskThroughput": 125, - "encryptEBSVolume": true, - "instanceSize": "M30", - "nodeCount": 3, - "preferredCpuArchitecture": "arm64", - "volumeType": "gp3" - }, - "priority": 7, - "readOnlySpecs": { - "diskIOPS": 3000, - "diskThroughput": 125, - "encryptEBSVolume": true, - "instanceSize": "M30", - "nodeCount": 0, - "preferredCpuArchitecture": "arm64", - "volumeType": "gp3" - }, - "regionName": "EU_CENTRAL_1", - "regionView": { - "complianceLevel": "COMMERCIAL", - "continent": "Europe", - "isRecommended": true, - "key": "EU_CENTRAL_1", - "latitude": 50.110924, - "location": "Frankfurt", - "longitude": 8.682127, - "name": "eu-central-1", - "provider": "AWS" - } - } - ], - "zoneId": "66d0aa4e49898e6af5e4315a", - "zoneName": "EMEA" - }, - { - "id": "66d0aa1b85ebe5234e75b417", - "numShards": 1, - "regionConfigs": [ - { - "analyticsAutoScaling": { - "autoIndex": { - "enabled": false - }, - "compute": { - "enabled": true, - "maxInstanceSize": "M40", - "minInstanceSize": "M30", - "scaleDownEnabled": true - }, - "diskGB": { - "enabled": true - } - }, - "analyticsSpecs": { - "diskIOPS": 3000, - "diskThroughput": 125, - "encryptEBSVolume": true, - "instanceSize": "M30", - "nodeCount": 0, - "preferredCpuArchitecture": "arm64", - "volumeType": "gp3" - }, - "autoScaling": { - "autoIndex": { - "enabled": false - }, - "compute": { - "enabled": true, - "maxInstanceSize": "M40", - "minInstanceSize": "M30", - "scaleDownEnabled": true - }, - "diskGB": { - "enabled": true - } - }, - "cloudProvider": "AWS", - "electableSpecs": { - "diskIOPS": 3000, - "diskThroughput": 125, - "encryptEBSVolume": true, - "instanceSize": "M30", - "nodeCount": 3, - "preferredCpuArchitecture": "arm64", - "volumeType": "gp3" - }, - "priority": 7, - "readOnlySpecs": { - "diskIOPS": 3000, - "diskThroughput": 125, - "encryptEBSVolume": true, - "instanceSize": "M30", - "nodeCount": 0, - "preferredCpuArchitecture": "arm64", - "volumeType": "gp3" - }, - "regionName": "AP_SOUTHEAST_1", - "regionView": { - "complianceLevel": "COMMERCIAL", - "continent": "Asia", - "isRecommended": true, - "key": "AP_SOUTHEAST_1", - "latitude": 1.29027, - "location": "Singapore", - "longitude": 103.851959, - "name": "ap-southeast-1", - "provider": "AWS" - } - } - ], - "zoneId": "66d0aa4e49898e6af5e4315b", - "zoneName": "APAC" - } - ], - "resurrectOptions": null, - "resurrectRequested": null, - "retainBackupsForDeleting": null, - "rootCertType": "ISRGROOTX1", - "sampleDatasetToLoad": "sampledata", - "searchDeploymentRequest": null, - "serverlessBackupOptions": null, - "srvAddress": "cluster1.zwcs5.mongodb-dev.net", - "state": "UPDATING", - "tenantAccessRevokedForPause": false, - "tenantUpgrading": false, - "terminationProtectionEnabled": false, - "uniqueId": "66d0aa4e49898e6af5e43174", - "userNotifiedAboutPauseDate": null, - "versionReleaseSystem": "LTS" + } } diff --git a/packages/compass-global-writes/tests/create-store.tsx b/packages/compass-global-writes/tests/create-store.tsx index 91ec891f24f..0aad67f78dc 100644 --- a/packages/compass-global-writes/tests/create-store.tsx +++ b/packages/compass-global-writes/tests/create-store.tsx @@ -19,7 +19,6 @@ const atlasService = { return `https://example.com/${p}`; }, authenticatedFetch: (url: RequestInfo | URL) => { - // if (url.toString().endsWith('nds/clusters/Project0/Cluster0')) { return Promise.resolve({ status: 200, From 6e2c8935ead1f159f4fa271f92024354f4e62bfa Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Thu, 26 Sep 2024 00:28:21 +0200 Subject: [PATCH 17/37] correct nums --- packages/compass-global-writes/src/components/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/compass-global-writes/src/components/index.tsx b/packages/compass-global-writes/src/components/index.tsx index 01505d64513..2688e6a2284 100644 --- a/packages/compass-global-writes/src/components/index.tsx +++ b/packages/compass-global-writes/src/components/index.tsx @@ -13,8 +13,8 @@ import UnshardedState from './states/unsharded'; import ShardingState from './states/sharding'; const containerStyles = css({ - paddingLeft: spacing[3], - paddingRight: spacing[3], + paddingLeft: spacing[400], + paddingRight: spacing[400], display: 'flex', width: '100%', height: '100%', From 99a5529c7d0a15b065e01831cd03597bc658b7f2 Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Thu, 26 Sep 2024 12:50:30 +0200 Subject: [PATCH 18/37] error toasts --- .../src/components/states/sharding.tsx | 13 ++++ .../src/components/states/unsharded.tsx | 58 ++++++++------- .../src/store/index.spec.ts | 21 ++++++ .../src/store/reducer.ts | 74 +++++++++++++------ 4 files changed, 117 insertions(+), 49 deletions(-) create mode 100644 packages/compass-global-writes/src/store/index.spec.ts diff --git a/packages/compass-global-writes/src/components/states/sharding.tsx b/packages/compass-global-writes/src/components/states/sharding.tsx index 27408a1286a..f6c659e7ad0 100644 --- a/packages/compass-global-writes/src/components/states/sharding.tsx +++ b/packages/compass-global-writes/src/components/states/sharding.tsx @@ -2,7 +2,9 @@ import React from 'react'; import { Banner, BannerVariant, + Body, css, + Link, spacing, } from '@mongodb-js/compass-components'; import { connect } from 'react-redux'; @@ -22,6 +24,17 @@ export function ShardingState() { Sharding your collection ... {nbsp}this should not take too long. + + Once your collection is sharded, this tab will show instructions on + document 'location' field formatting, and provide some common + command examples. + + + You can read more about Global Writes in our documentation. +
); } diff --git a/packages/compass-global-writes/src/components/states/unsharded.tsx b/packages/compass-global-writes/src/components/states/unsharded.tsx index 0ffa62e2ed9..196cc4329b9 100644 --- a/packages/compass-global-writes/src/components/states/unsharded.tsx +++ b/packages/compass-global-writes/src/components/states/unsharded.tsx @@ -17,6 +17,7 @@ import { ComboboxOption, Checkbox, Button, + SpinLoader, cx, } from '@mongodb-js/compass-components'; import { useAutocompleteFields } from '@mongodb-js/compass-field-store'; @@ -110,7 +111,7 @@ function CreateShardKeyDescription() { } type ShardingAdvancedOption = 'default' | 'unique-index' | 'hashed-index'; -type UnshardedStateProps = { +type CreateShardKeyFormProps = { namespace: string; isLoading: boolean; onCreateShardKey: (data: CreateShardKeyData) => void; @@ -120,9 +121,9 @@ function CreateShardKeyForm({ namespace, isLoading, onCreateShardKey, -}: Pick) { +}: CreateShardKeyFormProps) { const [isAdvancedOptionsOpen, setIsAdvancedOptionsOpen] = useState(false); - const [selectedOption, setSelectedOption] = + const [selectedAdvancedOption, setSelectedAdvancedOption] = useState('default'); const fields = useAutocompleteFields(namespace); @@ -136,12 +137,12 @@ function CreateShardKeyForm({ if (!secondShardKey) { return; } - const isCustomShardKeyHashed = selectedOption === 'hashed-index'; + const isCustomShardKeyHashed = selectedAdvancedOption === 'hashed-index'; const presplitHashedZones = isCustomShardKeyHashed && isPreSplitData; const data: CreateShardKeyData = { customShardKey: secondShardKey, - isShardKeyUnique: selectedOption === 'unique-index', + isShardKeyUnique: selectedAdvancedOption === 'unique-index', isCustomShardKeyHashed, presplitHashedZones, numInitialChunks: @@ -155,12 +156,12 @@ function CreateShardKeyForm({ isPreSplitData, numInitialChunks, secondShardKey, - selectedOption, + selectedAdvancedOption, onCreateShardKey, ]); return ( -
+
); } -export function UnshardedState(props: UnshardedStateProps) { +const ConnectedCreateShardKeyForm = connect( + (state: RootState) => ({ + namespace: state.namespace, + isLoading: state.createShardkey.isLoading, + }), + { + onCreateShardKey: createShardKey, + } +)(CreateShardKeyForm); + +export function UnshardedState() { return (
@@ -311,17 +327,9 @@ export function UnshardedState(props: UnshardedStateProps) { {nbsp}See the instructions below for details. - +
); } -export default connect( - (state: RootState) => ({ - namespace: state.namespace, - isLoading: state.createShardkey.isLoading, - }), - { - onCreateShardKey: createShardKey, - } -)(UnshardedState); +export default UnshardedState; diff --git a/packages/compass-global-writes/src/store/index.spec.ts b/packages/compass-global-writes/src/store/index.spec.ts new file mode 100644 index 00000000000..d2b4a8dd276 --- /dev/null +++ b/packages/compass-global-writes/src/store/index.spec.ts @@ -0,0 +1,21 @@ +import { expect } from 'chai'; +import { type GlobalWritesStore } from '.'; +import { setupStore } from '../../tests/create-store'; + +describe('GlobalWritesStore Store', function () { + let store: GlobalWritesStore; + beforeEach(function () { + store = setupStore( + { + namespace: 'test.coll', + }, + { + atlasService: {} as any, + } + ); + }); + + it('sets the namespace', function () { + expect(store.getState().namespace).to.equal('test.coll'); + }); +}); diff --git a/packages/compass-global-writes/src/store/reducer.ts b/packages/compass-global-writes/src/store/reducer.ts index ac7f349c0eb..695a125b249 100644 --- a/packages/compass-global-writes/src/store/reducer.ts +++ b/packages/compass-global-writes/src/store/reducer.ts @@ -1,5 +1,6 @@ import type { Action, Reducer } from 'redux'; import type { GlobalWritesThunkAction } from '.'; +import { openToast } from '@mongodb-js/compass-components'; import type { ManagedNamespace } from '../services/atlas-global-writes-service'; export function isAction
( @@ -40,7 +41,6 @@ type ShardingInProgressFinishedAction = { type ShardingInProgressErroredAction = { type: GlobalWritesActionTypes.ShardingInProgressErrored; - error: Error; }; export enum ShardingStatuses { @@ -68,7 +68,6 @@ export type RootState = { status: ShardingStatus; createShardkey: { isLoading: boolean; - error: Error | null; }; }; @@ -78,7 +77,6 @@ const initialState: RootState = { status: ShardingStatuses.NOT_READY, createShardkey: { isLoading: false, - error: null, }, }; @@ -108,7 +106,6 @@ const reducer: Reducer = (state = initialState, action) => { ...state, createShardkey: { isLoading: true, - error: null, }, }; } @@ -125,7 +122,6 @@ const reducer: Reducer = (state = initialState, action) => { status: ShardingStatuses.SHARDING, createShardkey: { isLoading: false, - error: null, }, }; } @@ -140,7 +136,6 @@ const reducer: Reducer = (state = initialState, action) => { ...state, createShardkey: { isLoading: false, - error: action.error, }, }; } @@ -153,7 +148,7 @@ export const fetchClusterShardingData = async ( dispatch, getState, - { atlasGlobalWritesService, connectionInfoRef } + { atlasGlobalWritesService, connectionInfoRef, logger } ) => { if (!connectionInfoRef.current.atlasMetadata) { return; @@ -162,23 +157,40 @@ export const fetchClusterShardingData = const { namespace } = getState(); const { clusterName, projectId } = connectionInfoRef.current.atlasMetadata; - // Call the API to check if the namespace is managed. If the namespace is managed, - // we would want to fetch more data that is needed to figure out the state and - // accordingly show the UI to the user. - const isNamespaceManaged = - await atlasGlobalWritesService.isNamespaceManaged(namespace, { - projectId, - clusterName, - }); - - if (!isNamespaceManaged) { - dispatch({ - type: GlobalWritesActionTypes.SetIsManagedNamespace, - isNamespaceManaged, + try { + // Call the API to check if the namespace is managed. If the namespace is managed, + // we would want to fetch more data that is needed to figure out the state and + // accordingly show the UI to the user. + const isNamespaceManaged = + await atlasGlobalWritesService.isNamespaceManaged(namespace, { + projectId, + clusterName, + }); + + if (!isNamespaceManaged) { + dispatch({ + type: GlobalWritesActionTypes.SetIsManagedNamespace, + isNamespaceManaged, + }); + return; + } + // TODO (COMPASS-8277): Now fetch the sharding key and possible process error. + } catch (error) { + logger.log.error( + logger.mongoLogId(1_001_000_330), + 'AtlasFetchError', + 'Error fetching cluster sharding data', + error as Error + ); + openToast('global-writes-fetch-shard-info-error', { + title: `Failed to fetch sharding information: ${ + (error as Error).message + }`, + dismissible: true, + timeout: 5000, + variant: 'important', }); - return; } - // TODO (COMPASS-8277): Now fetch the sharding key and possible process error. }; export const createShardKey = @@ -193,7 +205,7 @@ export const createShardKey = async ( dispatch, getState, - { connectionInfoRef, atlasGlobalWritesService } + { connectionInfoRef, atlasGlobalWritesService, logger } ) => { if (!connectionInfoRef.current.atlasMetadata) { return; @@ -215,9 +227,23 @@ export const createShardKey = type: GlobalWritesActionTypes.ShardingInProgressFinished, }); } catch (error) { + logger.log.error( + logger.mongoLogId(1_001_000_331), + 'AtlasFetchError', + 'Error creating cluster shard key', + { + error: error as Error, + data, + } + ); + openToast('global-writes-create-shard-key-error', { + title: `Failed to create shard key: ${(error as Error).message}`, + dismissible: true, + timeout: 5000, + variant: 'important', + }); dispatch({ type: GlobalWritesActionTypes.ShardingInProgressErrored, - error: error as Error, }); } }; From 84fef303af5b91c9117b829ac4258edb8dd0e481 Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Thu, 26 Sep 2024 13:04:04 +0200 Subject: [PATCH 19/37] fix html --- .../src/components/index.tsx | 4 +-- .../src/components/states/unsharded.tsx | 32 +++++++++++-------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/packages/compass-global-writes/src/components/index.tsx b/packages/compass-global-writes/src/components/index.tsx index 2688e6a2284..97b7667859d 100644 --- a/packages/compass-global-writes/src/components/index.tsx +++ b/packages/compass-global-writes/src/components/index.tsx @@ -39,9 +39,9 @@ function getStateViewBasedOnShardingStatus(shardingStatus: ShardingStatus) { switch (shardingStatus) { case ShardingStatuses.NOT_READY: return ( - +
- +
); case ShardingStatuses.UNSHARDED: return ; diff --git a/packages/compass-global-writes/src/components/states/unsharded.tsx b/packages/compass-global-writes/src/components/states/unsharded.tsx index 196cc4329b9..d9209ff212b 100644 --- a/packages/compass-global-writes/src/components/states/unsharded.tsx +++ b/packages/compass-global-writes/src/components/states/unsharded.tsx @@ -231,36 +231,40 @@ function CreateShardKeyForm({ Default + > +
+ + Enforce a uniqueness constraint on the shard key of this Global Collection.{' '} Learn more - - } - > - Use unique index as the shard key + +
+ > +
+ + Improve even distribution of the sharded data by hashing the second field of the shard key.{' '} Learn more - - } - > - Use hashed index as the shard key + +
{selectedAdvancedOption === 'hashed-index' && ( From f644bd7bcb4ed0d2426747b3a4ba7df80f88285c Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Thu, 26 Sep 2024 13:55:05 +0200 Subject: [PATCH 20/37] tests --- .../src/components/states/unsharded.tsx | 36 ++--- .../services/atlas-global-writes-service.ts | 12 +- .../src/store/index.spec.ts | 153 ++++++++++++++++-- .../compass-global-writes/src/store/index.ts | 1 - 4 files changed, 163 insertions(+), 39 deletions(-) diff --git a/packages/compass-global-writes/src/components/states/unsharded.tsx b/packages/compass-global-writes/src/components/states/unsharded.tsx index d9209ff212b..fdaf725c080 100644 --- a/packages/compass-global-writes/src/components/states/unsharded.tsx +++ b/packages/compass-global-writes/src/components/states/unsharded.tsx @@ -111,17 +111,12 @@ function CreateShardKeyDescription() { } type ShardingAdvancedOption = 'default' | 'unique-index' | 'hashed-index'; -type CreateShardKeyFormProps = { - namespace: string; - isLoading: boolean; - onCreateShardKey: (data: CreateShardKeyData) => void; -}; function CreateShardKeyForm({ namespace, isLoading, onCreateShardKey, -}: CreateShardKeyFormProps) { +}: Pick) { const [isAdvancedOptionsOpen, setIsAdvancedOptionsOpen] = useState(false); const [selectedAdvancedOption, setSelectedAdvancedOption] = useState('default'); @@ -309,17 +304,12 @@ function CreateShardKeyForm({ ); } -const ConnectedCreateShardKeyForm = connect( - (state: RootState) => ({ - namespace: state.namespace, - isLoading: state.createShardkey.isLoading, - }), - { - onCreateShardKey: createShardKey, - } -)(CreateShardKeyForm); - -export function UnshardedState() { +type UnshardedStateProps = { + namespace: string; + isLoading: boolean; + onCreateShardKey: (data: CreateShardKeyData) => void; +}; +export function UnshardedState(props: UnshardedStateProps) { return (
@@ -331,9 +321,17 @@ export function UnshardedState() { {nbsp}See the instructions below for details. - +
); } -export default UnshardedState; +export default connect( + (state: RootState) => ({ + namespace: state.namespace, + isLoading: state.createShardkey.isLoading, + }), + { + onCreateShardKey: createShardKey, + } +)(UnshardedState); diff --git a/packages/compass-global-writes/src/services/atlas-global-writes-service.ts b/packages/compass-global-writes/src/services/atlas-global-writes-service.ts index 6ff8fe8f301..3730f8c2aeb 100644 --- a/packages/compass-global-writes/src/services/atlas-global-writes-service.ts +++ b/packages/compass-global-writes/src/services/atlas-global-writes-service.ts @@ -63,9 +63,9 @@ export class AtlasGlobalWritesService { namespace: string, atlasClusterInfo: AtlasCluterInfo ) { - const clusterData = await this.fetchClusterDetails(atlasClusterInfo); + const clusterDetails = await this.fetchClusterDetails(atlasClusterInfo); const { database, collection } = toNS(namespace); - return clusterData.geoSharding.managedNamespaces.some( + return clusterDetails.geoSharding.managedNamespaces.some( (managedNamespace) => { return ( managedNamespace.db === database && @@ -80,14 +80,12 @@ export class AtlasGlobalWritesService { keyData: CreateShardKeyData, atlasClusterInfo: AtlasCluterInfo ) { - const clusterData = await this.fetchClusterDetails(atlasClusterInfo); - + const clusterDetails = await this.fetchClusterDetails(atlasClusterInfo); const { database, collection } = toNS(namespace); - const requestData: GeoShardingData = { - ...clusterData.geoSharding, + ...clusterDetails.geoSharding, managedNamespaces: [ - ...clusterData.geoSharding.managedNamespaces, + ...clusterDetails.geoSharding.managedNamespaces, { db: database, collection: collection, diff --git a/packages/compass-global-writes/src/store/index.spec.ts b/packages/compass-global-writes/src/store/index.spec.ts index d2b4a8dd276..723b2181de8 100644 --- a/packages/compass-global-writes/src/store/index.spec.ts +++ b/packages/compass-global-writes/src/store/index.spec.ts @@ -1,21 +1,150 @@ import { expect } from 'chai'; import { type GlobalWritesStore } from '.'; import { setupStore } from '../../tests/create-store'; +import { + fetchClusterShardingData, + createShardKey, + type CreateShardKeyData, +} from './reducer'; +import sinon from 'sinon'; + +const DB = 'test'; +const COLL = 'coll'; +const NS = `${DB}.${COLL}`; + +function createJsonResponse(data: any) { + return { + json: () => Promise.resolve(data), + }; +} + +function createStore(atlasService: any = {}): GlobalWritesStore { + return setupStore( + { + namespace: NS, + }, + { + atlasService, + } + ); +} describe('GlobalWritesStore Store', function () { - let store: GlobalWritesStore; - beforeEach(function () { - store = setupStore( - { - namespace: 'test.coll', - }, - { - atlasService: {} as any, - } - ); + it('sets the initial state', function () { + const store = createStore(); + expect(store.getState().namespace).to.equal(NS); + expect(store.getState().status).to.equal('NOT_READY'); }); - it('sets the namespace', function () { - expect(store.getState().namespace).to.equal('test.coll'); + context('actions', function () { + context('fetchClusterShardingData', function () { + it('when the namespace is not managed', async function () { + const store = createStore({ + authenticatedFetch: () => + createJsonResponse({ + geoSharding: { customZoneMapping: {}, managedNamespaces: [] }, + }), + }); + await store.dispatch(fetchClusterShardingData()); + expect(store.getState().status).to.equal('UNSHARDED'); + expect(store.getState().isNamespaceSharded).to.equal(false); + }); + + // TODO (COMPASS-8277): Add more test for fetching shard key and process errors + }); + + context('createShardKey', function () { + const shardKeyData: CreateShardKeyData = { + customShardKey: 'test', + isCustomShardKeyHashed: true, + isShardKeyUnique: false, + numInitialChunks: 1, + presplitHashedZones: true, + }; + + it('sets loading state when starting to create shard key and clears it out on success', async function () { + const store = createStore({ + authenticatedFetch: () => + createJsonResponse({ + geoSharding: { customZoneMapping: {}, managedNamespaces: [] }, + }), + }); + + const promise = store.dispatch(createShardKey(shardKeyData)); + expect(store.getState().createShardkey.isLoading).to.equal(true); + expect(store.getState().status).to.equal('NOT_READY'); + + await promise; + + expect(store.getState().createShardkey.isLoading).to.equal(false); + expect(store.getState().status).to.equal('SHARDING'); + }); + + it('sets loading state when starting to create shard key and clears it out on failure', async function () { + const store = createStore({ + authenticatedFetch: () => Promise.reject(new Error('error')), + }); + + const promise = store.dispatch(createShardKey(shardKeyData)); + expect(store.getState().createShardkey.isLoading).to.equal(true); + expect(store.getState().status).to.equal('NOT_READY'); + + await promise; + + expect(store.getState().createShardkey.isLoading).to.equal(false); + expect(store.getState().status).to.equal('NOT_READY'); + }); + + it('sends correct data to the server when creating a shard key', async function () { + const alreadyManagedNamespaces = [ + { + db: 'test', + collection: 'one', + customShardKey: 'test', + isCustomShardKeyHashed: true, + isShardKeyUnique: false, + numInitialChunks: 1, + presplitHashedZones: true, + }, + ]; + + const getClusterInfoApiResponse = createJsonResponse({ + geoSharding: { + customZoneMapping: {}, + managedNamespaces: alreadyManagedNamespaces, + }, + }); + + // We call cluster API when store is activated to get the initial state. + // When creating a shard key, we call the same API to fetch the latest list of + // managed namespaces & then send it to the server along with the shard key data. + // So, we mock first and second call with same data. And then third call + // should be to create the shard key. + const fetchStub = sinon + .stub() + .onFirstCall() + .returns(getClusterInfoApiResponse) + .onSecondCall() + .returns(getClusterInfoApiResponse) + .onThirdCall() + .resolves(); + + const store = createStore({ + authenticatedFetch: fetchStub, + }); + + await store.dispatch(createShardKey(shardKeyData)); + + const options = fetchStub.getCall(2).args[1]; + expect(options.method).to.equal('PATCH'); + expect(JSON.parse(options.body)).to.deep.equal({ + customZoneMapping: {}, + managedNamespaces: [ + ...alreadyManagedNamespaces, + { ...shardKeyData, db: DB, collection: COLL }, + ], + }); + }); + }); }); }); diff --git a/packages/compass-global-writes/src/store/index.ts b/packages/compass-global-writes/src/store/index.ts index 4409bf562d6..13c913ffc75 100644 --- a/packages/compass-global-writes/src/store/index.ts +++ b/packages/compass-global-writes/src/store/index.ts @@ -64,7 +64,6 @@ export function activateGlobalWritesPlugin( isNamespaceSharded: false, status: ShardingStatuses.NOT_READY, createShardkey: { - error: null, isLoading: false, }, }, From 9e3be02fa05dd944979b43750e7c91fe2e258565 Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Thu, 26 Sep 2024 14:15:23 +0200 Subject: [PATCH 21/37] clean up --- .../src/services/atlas-global-writes-service.ts | 3 +-- packages/compass-global-writes/src/store/reducer.ts | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/compass-global-writes/src/services/atlas-global-writes-service.ts b/packages/compass-global-writes/src/services/atlas-global-writes-service.ts index 3730f8c2aeb..0f58f4cbdc1 100644 --- a/packages/compass-global-writes/src/services/atlas-global-writes-service.ts +++ b/packages/compass-global-writes/src/services/atlas-global-writes-service.ts @@ -98,10 +98,9 @@ export class AtlasGlobalWritesService { `nds/clusters/${atlasClusterInfo.projectId}/${atlasClusterInfo.clusterName}/geoSharding` ); - const response = await this.atlasService.authenticatedFetch(uri, { + await this.atlasService.authenticatedFetch(uri, { method: 'PATCH', body: JSON.stringify(requestData), }); - assertDataIsClusterDetailsApiResponse(await response.json()); } } diff --git a/packages/compass-global-writes/src/store/reducer.ts b/packages/compass-global-writes/src/store/reducer.ts index 695a125b249..f588765e8da 100644 --- a/packages/compass-global-writes/src/store/reducer.ts +++ b/packages/compass-global-writes/src/store/reducer.ts @@ -180,7 +180,7 @@ export const fetchClusterShardingData = logger.mongoLogId(1_001_000_330), 'AtlasFetchError', 'Error fetching cluster sharding data', - error as Error + (error as Error).message ); openToast('global-writes-fetch-shard-info-error', { title: `Failed to fetch sharding information: ${ @@ -232,7 +232,7 @@ export const createShardKey = 'AtlasFetchError', 'Error creating cluster shard key', { - error: error as Error, + error: (error as Error).message, data, } ); From 3b384fcec9c35342efd80f9d58ee12a0841bf65b Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Thu, 26 Sep 2024 14:20:53 +0200 Subject: [PATCH 22/37] pedantic details --- .../compass-global-writes/src/components/index.tsx | 2 +- .../src/components/states/sharding.tsx | 6 +++--- .../src/components/states/unsharded.tsx | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/compass-global-writes/src/components/index.tsx b/packages/compass-global-writes/src/components/index.tsx index 97b7667859d..0171cb1c07f 100644 --- a/packages/compass-global-writes/src/components/index.tsx +++ b/packages/compass-global-writes/src/components/index.tsx @@ -40,7 +40,7 @@ function getStateViewBasedOnShardingStatus(shardingStatus: ShardingStatus) { case ShardingStatuses.NOT_READY: return (
- +
); case ShardingStatuses.UNSHARDED: diff --git a/packages/compass-global-writes/src/components/states/sharding.tsx b/packages/compass-global-writes/src/components/states/sharding.tsx index f6c659e7ad0..f64e788a4ab 100644 --- a/packages/compass-global-writes/src/components/states/sharding.tsx +++ b/packages/compass-global-writes/src/components/states/sharding.tsx @@ -21,13 +21,13 @@ export function ShardingState() { return (
- Sharding your collection ... + Sharding your collection … {nbsp}this should not take too long. Once your collection is sharded, this tab will show instructions on - document 'location' field formatting, and provide some common - command examples. + document ‘location’ field formatting, and provide some common command + examples. Configure compound shard key To properly configure Global Writes, your collections must be sharded - using a compound shard key made up of a 'location' field and a - second field of your choosing. + using a compound shard key made up of a ‘location’ field and a second + field of your choosing. - All documents in your collection should contain both the - 'location' field and your chosen second field. + All documents in your collection should contain both the ‘location’ + field and your chosen second field.
    @@ -315,7 +315,7 @@ export function UnshardedState(props: UnshardedStateProps) { To use Global Writes, this collection must be configured with a - compound shard key made up of both a 'location' field and an + compound shard key made up of both a ‘location’ field and an identifier field that you should provide. {nbsp}See the instructions below for details. From 1c8897a6252f3cf49301a060a7d3a238a3fe55ac Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Thu, 26 Sep 2024 14:50:23 +0200 Subject: [PATCH 23/37] add missing header --- .../src/services/atlas-global-writes-service.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/compass-global-writes/src/services/atlas-global-writes-service.ts b/packages/compass-global-writes/src/services/atlas-global-writes-service.ts index 0f58f4cbdc1..d548eae05c3 100644 --- a/packages/compass-global-writes/src/services/atlas-global-writes-service.ts +++ b/packages/compass-global-writes/src/services/atlas-global-writes-service.ts @@ -101,6 +101,9 @@ export class AtlasGlobalWritesService { await this.atlasService.authenticatedFetch(uri, { method: 'PATCH', body: JSON.stringify(requestData), + headers: { + 'Content-Type': 'application/json', + }, }); } } From e8c55b0c95aed92bccb7815c8d8d337f8b448828 Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Thu, 26 Sep 2024 14:55:18 +0200 Subject: [PATCH 24/37] check --- package-lock.json | 36 +++++++++++++++++++ packages/compass-global-writes/package.json | 3 ++ .../src/components/index.spec.tsx | 2 +- .../src/components/index.tsx | 1 - 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 63a4b545584..0949675fac6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45275,10 +45275,13 @@ "@mongodb-js/compass-collection": "^4.41.0", "@mongodb-js/compass-components": "^1.29.4", "@mongodb-js/compass-connections": "^1.42.0", + "@mongodb-js/compass-field-store": "^9.17.0", "@mongodb-js/compass-logging": "^1.4.7", "@mongodb-js/compass-telemetry": "^1.1.7", "hadron-app-registry": "^9.2.6", + "mongodb-ns": "^2.4.2", "react": "^17.0.2", + "react-redux": "^8.1.3", "redux": "^4.2.1", "redux-thunk": "^2.4.2" }, @@ -45368,6 +45371,28 @@ "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", "dev": true }, + "packages/compass-global-writes/node_modules/react-redux": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.1.2.tgz", + "integrity": "sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==", + "dependencies": { + "@types/use-sync-external-store": "^0.0.3", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25", + "react": "^18.0", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "packages/compass-global-writes/node_modules/sinon": { "version": "17.0.1", "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", @@ -57005,6 +57030,7 @@ "@mongodb-js/compass-collection": "^4.41.0", "@mongodb-js/compass-components": "^1.29.4", "@mongodb-js/compass-connections": "^1.42.0", + "@mongodb-js/compass-field-store": "^9.17.0", "@mongodb-js/compass-logging": "^1.4.7", "@mongodb-js/compass-telemetry": "^1.1.7", "@mongodb-js/eslint-config-compass": "^1.1.7", @@ -57023,9 +57049,11 @@ "eslint": "^7.25.0", "hadron-app-registry": "^9.2.6", "mocha": "^10.2.0", + "mongodb-ns": "^2.4.2", "nyc": "^15.1.0", "prettier": "^2.7.1", "react": "^17.0.2", + "react-redux": "^8.1.3", "redux": "^4.2.1", "redux-thunk": "^2.4.2", "sinon": "^17.0.1", @@ -57095,6 +57123,14 @@ "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", "dev": true }, + "react-redux": { + "version": "https://registry.npmjs.org/react-redux/-/react-redux-9.1.2.tgz", + "integrity": "sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==", + "requires": { + "@types/use-sync-external-store": "^0.0.3", + "use-sync-external-store": "^1.0.0" + } + }, "sinon": { "version": "17.0.1", "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", diff --git a/packages/compass-global-writes/package.json b/packages/compass-global-writes/package.json index 917f8d4791a..91e620bf505 100644 --- a/packages/compass-global-writes/package.json +++ b/packages/compass-global-writes/package.json @@ -53,10 +53,13 @@ "@mongodb-js/compass-collection": "^4.41.0", "@mongodb-js/compass-components": "^1.29.4", "@mongodb-js/compass-connections": "^1.42.0", + "@mongodb-js/compass-field-store": "^9.17.0", "@mongodb-js/compass-logging": "^1.4.7", "@mongodb-js/compass-telemetry": "^1.1.7", "hadron-app-registry": "^9.2.6", + "mongodb-ns": "^2.4.2", "react": "^17.0.2", + "react-redux": "^8.1.3", "redux": "^4.2.1", "redux-thunk": "^2.4.2" }, diff --git a/packages/compass-global-writes/src/components/index.spec.tsx b/packages/compass-global-writes/src/components/index.spec.tsx index c6a0b18ef43..f034c0df5a2 100644 --- a/packages/compass-global-writes/src/components/index.spec.tsx +++ b/packages/compass-global-writes/src/components/index.spec.tsx @@ -7,7 +7,7 @@ import { renderWithStore } from './../../tests/create-store'; describe('Compass GlobalWrites Plugin', function () { it('renders plugin in NOT_READY state', function () { renderWithStore(); - expect(screen.getByText('Loading ...')).to.exist; + expect(screen.getByText(/loading/i)).to.exist; }); it('renders plugin in UNSHARDED state', function () { diff --git a/packages/compass-global-writes/src/components/index.tsx b/packages/compass-global-writes/src/components/index.tsx index 0171cb1c07f..a21bfdc6d4b 100644 --- a/packages/compass-global-writes/src/components/index.tsx +++ b/packages/compass-global-writes/src/components/index.tsx @@ -4,7 +4,6 @@ import { css, spacing, WorkspaceContainer, - Body, SpinLoaderWithLabel, } from '@mongodb-js/compass-components'; import type { RootState, ShardingStatus } from '../store/reducer'; From d7745cd61d4542ed5918e0ae18c8aaccb3c97850 Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Thu, 26 Sep 2024 15:08:37 +0200 Subject: [PATCH 25/37] clean up lock file --- package-lock.json | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0949675fac6..44607e5c898 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45371,28 +45371,6 @@ "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", "dev": true }, - "packages/compass-global-writes/node_modules/react-redux": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.1.2.tgz", - "integrity": "sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==", - "dependencies": { - "@types/use-sync-external-store": "^0.0.3", - "use-sync-external-store": "^1.0.0" - }, - "peerDependencies": { - "@types/react": "^18.2.25", - "react": "^18.0", - "redux": "^5.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "redux": { - "optional": true - } - } - }, "packages/compass-global-writes/node_modules/sinon": { "version": "17.0.1", "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", @@ -57123,14 +57101,6 @@ "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", "dev": true }, - "react-redux": { - "version": "https://registry.npmjs.org/react-redux/-/react-redux-9.1.2.tgz", - "integrity": "sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==", - "requires": { - "@types/use-sync-external-store": "^0.0.3", - "use-sync-external-store": "^1.0.0" - } - }, "sinon": { "version": "17.0.1", "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", From a0eb2be8bf1eb53c218238c39110dc3457c3fa2c Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Thu, 26 Sep 2024 15:17:28 +0200 Subject: [PATCH 26/37] clean action name --- .../src/store/reducer.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/compass-global-writes/src/store/reducer.ts b/packages/compass-global-writes/src/store/reducer.ts index f588765e8da..e9b9ccf80f0 100644 --- a/packages/compass-global-writes/src/store/reducer.ts +++ b/packages/compass-global-writes/src/store/reducer.ts @@ -20,14 +20,14 @@ export type CreateShardKeyData = Pick< >; enum GlobalWritesActionTypes { - SetIsManagedNamespace = 'global-writes/SetIsManagedNamespace', + IsManagedNamespaceFetched = 'global-writes/IsManagedNamespaceFetched', ShardingInProgressStarted = 'global-writes/ShardingInProgressStarted', ShardingInProgressFinished = 'global-writes/ShardingInProgressFinished', ShardingInProgressErrored = 'global-writes/ShardingInProgressErrored', } -type SetIsManagedNamespaceAction = { - type: GlobalWritesActionTypes.SetIsManagedNamespace; +type IsManagedNamespaceFetchedAction = { + type: GlobalWritesActionTypes.IsManagedNamespaceFetched; isNamespaceManaged: boolean; }; @@ -82,9 +82,9 @@ const initialState: RootState = { const reducer: Reducer = (state = initialState, action) => { if ( - isAction( + isAction( action, - GlobalWritesActionTypes.SetIsManagedNamespace + GlobalWritesActionTypes.IsManagedNamespaceFetched ) ) { return { @@ -144,7 +144,7 @@ const reducer: Reducer = (state = initialState, action) => { }; export const fetchClusterShardingData = - (): GlobalWritesThunkAction, SetIsManagedNamespaceAction> => + (): GlobalWritesThunkAction, IsManagedNamespaceFetchedAction> => async ( dispatch, getState, @@ -167,11 +167,11 @@ export const fetchClusterShardingData = clusterName, }); + dispatch({ + type: GlobalWritesActionTypes.IsManagedNamespaceFetched, + isNamespaceManaged, + }); if (!isNamespaceManaged) { - dispatch({ - type: GlobalWritesActionTypes.SetIsManagedNamespace, - isNamespaceManaged, - }); return; } // TODO (COMPASS-8277): Now fetch the sharding key and possible process error. From 5f03a497acf04177ae48a213796c80f6553affc2 Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Thu, 26 Sep 2024 15:19:55 +0200 Subject: [PATCH 27/37] func to component --- .../src/components/index.tsx | 37 +++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/packages/compass-global-writes/src/components/index.tsx b/packages/compass-global-writes/src/components/index.tsx index a21bfdc6d4b..f53173abf37 100644 --- a/packages/compass-global-writes/src/components/index.tsx +++ b/packages/compass-global-writes/src/components/index.tsx @@ -34,28 +34,35 @@ type GlobalWritesProps = { shardingStatus: ShardingStatus; }; -function getStateViewBasedOnShardingStatus(shardingStatus: ShardingStatus) { - switch (shardingStatus) { - case ShardingStatuses.NOT_READY: - return ( -
    - -
    - ); - case ShardingStatuses.UNSHARDED: - return ; - case ShardingStatuses.SHARDING: - return ; - default: - return null; +function ShardingStateView({ + shardingStatus, +}: { + shardingStatus: ShardingStatus; +}) { + if (shardingStatus === ShardingStatuses.NOT_READY) { + return ( +
    + +
    + ); + } + + if (shardingStatus === ShardingStatuses.UNSHARDED) { + return ; } + + if (shardingStatus === ShardingStatuses.SHARDING) { + return ; + } + + return null; } export function GlobalWrites({ shardingStatus }: GlobalWritesProps) { return (
    - {getStateViewBasedOnShardingStatus(shardingStatus)} +
    ); From 9437e077079f93b0289e1904be59a654dc37884c Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Thu, 26 Sep 2024 16:10:48 +0200 Subject: [PATCH 28/37] fix check --- packages/compass-global-writes/src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/compass-global-writes/src/index.ts b/packages/compass-global-writes/src/index.ts index 7a000ca91ab..66d8500c283 100644 --- a/packages/compass-global-writes/src/index.ts +++ b/packages/compass-global-writes/src/index.ts @@ -28,6 +28,6 @@ const CompassGlobalWritesHadronPlugin = registerHadronPlugin( export const CompassGlobalWritesPlugin = { name: 'GlobalWrites' as const, provider: CompassGlobalWritesHadronPlugin, - content: GlobalWrites, - header: GlobalWritesTabTitle, + content: GlobalWrites as React.ComponentType, + header: GlobalWritesTabTitle as React.ComponentType, }; From 23c136137c70829d9a7ca778edd358114de24a75 Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Thu, 26 Sep 2024 16:11:31 +0200 Subject: [PATCH 29/37] use state when waiting for server to accept sharding --- .../src/components/index.tsx | 5 +- .../src/components/states/unsharded.tsx | 4 +- .../src/store/reducer.ts | 65 +++++++++---------- 3 files changed, 35 insertions(+), 39 deletions(-) diff --git a/packages/compass-global-writes/src/components/index.tsx b/packages/compass-global-writes/src/components/index.tsx index f53173abf37..ff0a5b02fa4 100644 --- a/packages/compass-global-writes/src/components/index.tsx +++ b/packages/compass-global-writes/src/components/index.tsx @@ -47,7 +47,10 @@ function ShardingStateView({ ); } - if (shardingStatus === ShardingStatuses.UNSHARDED) { + if ( + shardingStatus === ShardingStatuses.UNSHARDED || + shardingStatus === ShardingStatuses.SUBMITTING_FOR_SHARDING + ) { return ; } diff --git a/packages/compass-global-writes/src/components/states/unsharded.tsx b/packages/compass-global-writes/src/components/states/unsharded.tsx index 39b37e17186..a6a0406d93f 100644 --- a/packages/compass-global-writes/src/components/states/unsharded.tsx +++ b/packages/compass-global-writes/src/components/states/unsharded.tsx @@ -23,7 +23,7 @@ import { import { useAutocompleteFields } from '@mongodb-js/compass-field-store'; import { connect } from 'react-redux'; import type { CreateShardKeyData, RootState } from '../../store/reducer'; -import { createShardKey } from '../../store/reducer'; +import { createShardKey, ShardingStatuses } from '../../store/reducer'; const nbsp = '\u00a0'; @@ -329,7 +329,7 @@ export function UnshardedState(props: UnshardedStateProps) { export default connect( (state: RootState) => ({ namespace: state.namespace, - isLoading: state.createShardkey.isLoading, + isLoading: state.status === ShardingStatuses.SUBMITTING_FOR_SHARDING, }), { onCreateShardKey: createShardKey, diff --git a/packages/compass-global-writes/src/store/reducer.ts b/packages/compass-global-writes/src/store/reducer.ts index e9b9ccf80f0..17e1dba65db 100644 --- a/packages/compass-global-writes/src/store/reducer.ts +++ b/packages/compass-global-writes/src/store/reducer.ts @@ -21,9 +21,9 @@ export type CreateShardKeyData = Pick< enum GlobalWritesActionTypes { IsManagedNamespaceFetched = 'global-writes/IsManagedNamespaceFetched', - ShardingInProgressStarted = 'global-writes/ShardingInProgressStarted', - ShardingInProgressFinished = 'global-writes/ShardingInProgressFinished', - ShardingInProgressErrored = 'global-writes/ShardingInProgressErrored', + SubmittingForShardingStarted = 'global-writes/SubmittingForShardingStarted', + SubmittingForShardingFinished = 'global-writes/SubmittingForShardingFinished', + SubmittingForShardingErrored = 'global-writes/SubmittingForShardingErrored', } type IsManagedNamespaceFetchedAction = { @@ -31,16 +31,16 @@ type IsManagedNamespaceFetchedAction = { isNamespaceManaged: boolean; }; -type ShardingInProgressStartedAction = { - type: GlobalWritesActionTypes.ShardingInProgressStarted; +type SubmittingForShardingStartedAction = { + type: GlobalWritesActionTypes.SubmittingForShardingStarted; }; -type ShardingInProgressFinishedAction = { - type: GlobalWritesActionTypes.ShardingInProgressFinished; +type SubmittingForShardingFinishedAction = { + type: GlobalWritesActionTypes.SubmittingForShardingFinished; }; -type ShardingInProgressErroredAction = { - type: GlobalWritesActionTypes.ShardingInProgressErrored; +type SubmittingForShardingErroredAction = { + type: GlobalWritesActionTypes.SubmittingForShardingErrored; }; export enum ShardingStatuses { @@ -54,6 +54,12 @@ export enum ShardingStatuses { */ UNSHARDED = 'UNSHARDED', + /** + * State when user submits namespace to be sharded and + * we are waiting for server to accept the request. + */ + SUBMITTING_FOR_SHARDING = 'SUBMITTING_FOR_SHARDING', + /** * Namespace is being sharded. */ @@ -66,18 +72,12 @@ export type RootState = { namespace: string; isNamespaceSharded: boolean; status: ShardingStatus; - createShardkey: { - isLoading: boolean; - }; }; const initialState: RootState = { namespace: '', isNamespaceSharded: false, status: ShardingStatuses.NOT_READY, - createShardkey: { - isLoading: false, - }, }; const reducer: Reducer = (state = initialState, action) => { @@ -97,46 +97,39 @@ const reducer: Reducer = (state = initialState, action) => { } if ( - isAction( + isAction( action, - GlobalWritesActionTypes.ShardingInProgressStarted + GlobalWritesActionTypes.SubmittingForShardingStarted ) ) { return { ...state, - createShardkey: { - isLoading: true, - }, + status: ShardingStatuses.SUBMITTING_FOR_SHARDING, }; } if ( - isAction( + isAction( action, - GlobalWritesActionTypes.ShardingInProgressFinished + GlobalWritesActionTypes.SubmittingForShardingFinished ) ) { return { ...state, isNamespaceSharded: true, status: ShardingStatuses.SHARDING, - createShardkey: { - isLoading: false, - }, }; } if ( - isAction( + isAction( action, - GlobalWritesActionTypes.ShardingInProgressErrored + GlobalWritesActionTypes.SubmittingForShardingErrored ) ) { return { ...state, - createShardkey: { - isLoading: false, - }, + status: ShardingStatuses.UNSHARDED, }; } @@ -198,9 +191,9 @@ export const createShardKey = data: CreateShardKeyData ): GlobalWritesThunkAction< Promise, - | ShardingInProgressStartedAction - | ShardingInProgressFinishedAction - | ShardingInProgressErroredAction + | SubmittingForShardingStartedAction + | SubmittingForShardingFinishedAction + | SubmittingForShardingErroredAction > => async ( dispatch, @@ -215,7 +208,7 @@ export const createShardKey = const { clusterName, projectId } = connectionInfoRef.current.atlasMetadata; dispatch({ - type: GlobalWritesActionTypes.ShardingInProgressStarted, + type: GlobalWritesActionTypes.SubmittingForShardingStarted, }); try { @@ -224,7 +217,7 @@ export const createShardKey = clusterName, }); dispatch({ - type: GlobalWritesActionTypes.ShardingInProgressFinished, + type: GlobalWritesActionTypes.SubmittingForShardingFinished, }); } catch (error) { logger.log.error( @@ -243,7 +236,7 @@ export const createShardKey = variant: 'important', }); dispatch({ - type: GlobalWritesActionTypes.ShardingInProgressErrored, + type: GlobalWritesActionTypes.SubmittingForShardingErrored, }); } }; From c8be176a71503b07a656bbfc93b5bf4805fd3261 Mon Sep 17 00:00:00 2001 From: Basit Chonka Date: Thu, 26 Sep 2024 16:13:35 +0200 Subject: [PATCH 30/37] name fix --- .../src/components/states/unsharded.tsx | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/compass-global-writes/src/components/states/unsharded.tsx b/packages/compass-global-writes/src/components/states/unsharded.tsx index a6a0406d93f..c34e377e3c8 100644 --- a/packages/compass-global-writes/src/components/states/unsharded.tsx +++ b/packages/compass-global-writes/src/components/states/unsharded.tsx @@ -114,9 +114,12 @@ type ShardingAdvancedOption = 'default' | 'unique-index' | 'hashed-index'; function CreateShardKeyForm({ namespace, - isLoading, + isSubmittingForSharding, onCreateShardKey, -}: Pick) { +}: Pick< + UnshardedStateProps, + 'isSubmittingForSharding' | 'namespace' | 'onCreateShardKey' +>) { const [isAdvancedOptionsOpen, setIsAdvancedOptionsOpen] = useState(false); const [selectedAdvancedOption, setSelectedAdvancedOption] = useState('default'); @@ -291,10 +294,12 @@ function CreateShardKeyForm({