From 297a943a79bc4621fb5c5188c2e9ed7d1a110f90 Mon Sep 17 00:00:00 2001 From: gagik Date: Tue, 20 May 2025 16:59:43 +0200 Subject: [PATCH 01/76] feat(compass-context-menu): add a headless context menu package --- package-lock.json | 104 ++++++++++++++++++ packages/compass-context-menu/.depcheckrc | 8 ++ packages/compass-context-menu/.eslintignore | 2 + packages/compass-context-menu/.eslintrc.js | 9 ++ packages/compass-context-menu/.mocharc.js | 2 + packages/compass-context-menu/package.json | 72 ++++++++++++ .../src/context-menu-content.ts | 23 ++++ .../src/context-menu-provider.tsx | 68 ++++++++++++ .../compass-context-menu/src/context-menu.tsx | 22 ++++ packages/compass-context-menu/src/types.ts | 21 ++++ .../src/use-context-menu.tsx | 55 +++++++++ .../compass-context-menu/tsconfig-lint.json | 5 + packages/compass-context-menu/tsconfig.json | 8 ++ 13 files changed, 399 insertions(+) create mode 100644 packages/compass-context-menu/.depcheckrc create mode 100644 packages/compass-context-menu/.eslintignore create mode 100644 packages/compass-context-menu/.eslintrc.js create mode 100644 packages/compass-context-menu/.mocharc.js create mode 100644 packages/compass-context-menu/package.json create mode 100644 packages/compass-context-menu/src/context-menu-content.ts create mode 100644 packages/compass-context-menu/src/context-menu-provider.tsx create mode 100644 packages/compass-context-menu/src/context-menu.tsx create mode 100644 packages/compass-context-menu/src/types.ts create mode 100644 packages/compass-context-menu/src/use-context-menu.tsx create mode 100644 packages/compass-context-menu/tsconfig-lint.json create mode 100644 packages/compass-context-menu/tsconfig.json diff --git a/package-lock.json b/package-lock.json index 3cfca4a376d..f006b787bbf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7893,6 +7893,10 @@ "resolved": "packages/compass-connections-navigation", "link": true }, + "node_modules/@mongodb-js/compass-context-menu": { + "resolved": "packages/compass-context-menu", + "link": true + }, "node_modules/@mongodb-js/compass-crud": { "resolved": "packages/compass-crud", "link": true @@ -44219,6 +44223,62 @@ "node": ">=0.3.1" } }, + "packages/compass-context-menu": { + "name": "@mongodb-js/compass-context-menu", + "version": "0.0.1", + "license": "SSPL", + "dependencies": { + "react": "^17.0.2" + }, + "devDependencies": { + "@mongodb-js/eslint-config-compass": "^1.3.8", + "@mongodb-js/mocha-config-compass": "^1.6.8", + "@mongodb-js/prettier-config-compass": "^1.2.8", + "@mongodb-js/tsconfig-compass": "^1.2.8", + "@types/chai": "^4.2.21", + "@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", + "gen-esm-wrapper": "^1.1.0", + "mocha": "^10.2.0", + "nyc": "^15.1.0", + "sinon": "^9.2.3", + "typescript": "^5.0.4" + } + }, + "packages/compass-context-menu/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "packages/compass-context-menu/node_modules/sinon": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.4.tgz", + "integrity": "sha512-zljcULZQsJxVra28qIAL6ow1Z9tpattkCTEJR4RBP3TGc00FcttsP5pK284Nas5WjMZU5Yzy3kAIp3B3KRf5Yg==", + "deprecated": "16.1.1", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^1.8.1", + "@sinonjs/fake-timers": "^6.0.1", + "@sinonjs/samsam": "^5.3.1", + "diff": "^4.0.2", + "nise": "^4.0.4", + "supports-color": "^7.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, "packages/compass-crud": { "name": "@mongodb-js/compass-crud", "version": "13.56.1", @@ -56727,6 +56787,50 @@ } } }, + "@mongodb-js/compass-context-menu": { + "version": "file:packages/compass-context-menu", + "requires": { + "@mongodb-js/eslint-config-compass": "^1.3.8", + "@mongodb-js/mocha-config-compass": "^1.6.8", + "@mongodb-js/prettier-config-compass": "^1.2.8", + "@mongodb-js/tsconfig-compass": "^1.2.8", + "@types/chai": "^4.2.21", + "@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", + "gen-esm-wrapper": "^1.1.0", + "mocha": "^10.2.0", + "nyc": "^15.1.0", + "react": "^17.0.2", + "sinon": "^9.2.3", + "typescript": "^5.0.4" + }, + "dependencies": { + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "sinon": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.4.tgz", + "integrity": "sha512-zljcULZQsJxVra28qIAL6ow1Z9tpattkCTEJR4RBP3TGc00FcttsP5pK284Nas5WjMZU5Yzy3kAIp3B3KRf5Yg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.8.1", + "@sinonjs/fake-timers": "^6.0.1", + "@sinonjs/samsam": "^5.3.1", + "diff": "^4.0.2", + "nise": "^4.0.4", + "supports-color": "^7.1.0" + } + } + } + }, "@mongodb-js/compass-crud": { "version": "file:packages/compass-crud", "requires": { diff --git a/packages/compass-context-menu/.depcheckrc b/packages/compass-context-menu/.depcheckrc new file mode 100644 index 00000000000..ab0ef21b740 --- /dev/null +++ b/packages/compass-context-menu/.depcheckrc @@ -0,0 +1,8 @@ +ignores: + - '@mongodb-js/prettier-config-compass' + - '@mongodb-js/tsconfig-compass' + - '@types/chai' + - '@types/sinon-chai' + - 'sinon' +ignore-patterns: + - 'dist' diff --git a/packages/compass-context-menu/.eslintignore b/packages/compass-context-menu/.eslintignore new file mode 100644 index 00000000000..85a8a75e68c --- /dev/null +++ b/packages/compass-context-menu/.eslintignore @@ -0,0 +1,2 @@ +.nyc-output +dist diff --git a/packages/compass-context-menu/.eslintrc.js b/packages/compass-context-menu/.eslintrc.js new file mode 100644 index 00000000000..9c3ab95632f --- /dev/null +++ b/packages/compass-context-menu/.eslintrc.js @@ -0,0 +1,9 @@ +'use strict'; +module.exports = { + root: true, + extends: ['@mongodb-js/eslint-config-compass'], + parserOptions: { + tsconfigRootDir: __dirname, + project: ['./tsconfig-lint.json'], + }, +}; diff --git a/packages/compass-context-menu/.mocharc.js b/packages/compass-context-menu/.mocharc.js new file mode 100644 index 00000000000..e7eaccd61fa --- /dev/null +++ b/packages/compass-context-menu/.mocharc.js @@ -0,0 +1,2 @@ +'use strict'; +module.exports = require('@mongodb-js/mocha-config-compass'); diff --git a/packages/compass-context-menu/package.json b/packages/compass-context-menu/package.json new file mode 100644 index 00000000000..3cf186e0270 --- /dev/null +++ b/packages/compass-context-menu/package.json @@ -0,0 +1,72 @@ +{ + "name": "@mongodb-js/compass-context-menu", + "author": { + "name": "MongoDB Inc", + "email": "compass@mongodb.com" + }, + "publishConfig": { + "access": "public" + }, + "bugs": { + "url": "https://jira.mongodb.org/projects/COMPASS/issues", + "email": "compass@mongodb.com" + }, + "homepage": "https://github.com/mongodb-js/compass", + "version": "0.0.1", + "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", + "prepublishOnly": "npm run compile && compass-scripts check-exports-exist", + "compile": "tsc -p tsconfig.json && gen-esm-wrapper . ./dist/.esm-wrapper.mjs", + "typecheck": "tsc -p tsconfig-lint.json --noEmit", + "eslint": "eslint-compass", + "prettier": "prettier-compass", + "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-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", + "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." + }, + "dependencies": { + "react": "^17.0.2" + }, + "devDependencies": { + "@mongodb-js/eslint-config-compass": "^1.3.8", + "@mongodb-js/mocha-config-compass": "^1.6.8", + "@mongodb-js/prettier-config-compass": "^1.2.8", + "@mongodb-js/tsconfig-compass": "^1.2.8", + "@types/chai": "^4.2.21", + "@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", + "gen-esm-wrapper": "^1.1.0", + "mocha": "^10.2.0", + "nyc": "^15.1.0", + "sinon": "^9.2.3", + "typescript": "^5.0.4" + } +} diff --git a/packages/compass-context-menu/src/context-menu-content.ts b/packages/compass-context-menu/src/context-menu-content.ts new file mode 100644 index 00000000000..6856f1fe684 --- /dev/null +++ b/packages/compass-context-menu/src/context-menu-content.ts @@ -0,0 +1,23 @@ +const CONTEXT_MENUS_SYMBOL = Symbol('context_menus'); + +export type EnhancedMouseEvent = MouseEvent & { + [CONTEXT_MENUS_SYMBOL]?: React.ComponentType[]; +}; + +export function getContextMenuContent( + event: EnhancedMouseEvent +): React.ComponentType[] { + return event[CONTEXT_MENUS_SYMBOL] ?? []; +} + +export function appendContextMenuContent( + event: EnhancedMouseEvent, + content: React.ComponentType +) { + // Initialize if not already patched + if (event[CONTEXT_MENUS_SYMBOL] === undefined) { + event[CONTEXT_MENUS_SYMBOL] = [content]; + return; + } + event[CONTEXT_MENUS_SYMBOL].push(content); +} diff --git a/packages/compass-context-menu/src/context-menu-provider.tsx b/packages/compass-context-menu/src/context-menu-provider.tsx new file mode 100644 index 00000000000..7a7b59bf16a --- /dev/null +++ b/packages/compass-context-menu/src/context-menu-provider.tsx @@ -0,0 +1,68 @@ +import React, { + useCallback, + useEffect, + useState, + useMemo, + createContext, +} from 'react'; +import type { ContextMenuContext, MenuState } from './types'; +import { ContextMenu } from './context-menu'; +import type { EnhancedMouseEvent } from './context-menu-content'; +import { getContextMenuContent } from './context-menu-content'; + +export const Context = createContext(null); + +export function ContextMenuProvider({ + children, +}: React.PropsWithChildren) { + const [menu, setMenu] = useState({ isOpen: false }); + const close = useCallback(() => setMenu({ isOpen: false }), [setMenu]); + + useEffect(() => { + function handleContextMenu(event: MouseEvent) { + event.preventDefault(); + setMenu({ + isOpen: true, + children: getContextMenuContent(event as EnhancedMouseEvent).map( + (Content, index) => + ), + position: { + // TODO: Fix handling offset while scrolling + x: event.clientX, + y: event.clientY, + }, + }); + } + document.addEventListener('contextmenu', handleContextMenu); + + function handleClosingEvent(event: Event) { + if (!event.defaultPrevented) { + setMenu({ isOpen: false }); + } + } + document.addEventListener('click', handleClosingEvent); + window.addEventListener('resize', handleClosingEvent); + + return () => { + document.removeEventListener('contextmenu', handleContextMenu); + document.removeEventListener('click', handleClosingEvent); + window.removeEventListener('resize', handleClosingEvent); + }; + }, [setMenu]); + + const value = useMemo( + () => ({ + close, + }), + [close] + ); + + return ( + <> + {children} + {menu.isOpen && ( + {menu.children} + )} + + ); +} diff --git a/packages/compass-context-menu/src/context-menu.tsx b/packages/compass-context-menu/src/context-menu.tsx new file mode 100644 index 00000000000..b053bc4963a --- /dev/null +++ b/packages/compass-context-menu/src/context-menu.tsx @@ -0,0 +1,22 @@ +import { createPortal } from 'react-dom'; +import React from 'react'; + +type ContextMenuProps = React.PropsWithChildren<{ + position: { + x: number; + y: number; + }; +}>; + +export function ContextMenu({ children, position }: ContextMenuProps) { + const container = document.getElementById('context-menu-container'); + if (container === null) { + throw new Error('Expected a container for the context menu in the DOM'); + } + return createPortal( +
+ {children} +
, + container + ); +} diff --git a/packages/compass-context-menu/src/types.ts b/packages/compass-context-menu/src/types.ts new file mode 100644 index 00000000000..07efb491ac6 --- /dev/null +++ b/packages/compass-context-menu/src/types.ts @@ -0,0 +1,21 @@ +export type MenuState = + | { + isOpen: false; + } + | { + isOpen: true; + children: React.ReactNode; + position: { + x: number; + y: number; + }; + }; + +export type ContextMenuContext = { + close(): void; +}; + +export type MenuItem = { + label: string; + onAction: (event: Event) => void; +}; diff --git a/packages/compass-context-menu/src/use-context-menu.tsx b/packages/compass-context-menu/src/use-context-menu.tsx new file mode 100644 index 00000000000..d7ccc114a90 --- /dev/null +++ b/packages/compass-context-menu/src/use-context-menu.tsx @@ -0,0 +1,55 @@ +import React, { useContext, useMemo, useRef } from 'react'; +import { Context } from './context-menu-provider'; +import { appendContextMenuContent } from './context-menu-content'; +import type { MenuItem } from './types'; + +/** + * @returns an object with methods to {@link register} content for the menu and {@link close} the menu + */ +export function useContextMenu({ + Menu, +}: { + Menu: React.ComponentType<{ + items: MenuItem[]; + }>; +}) { + // Get the close function from the ContextProvider + const context = useContext(Context); + const previous = useRef void]>( + null + ); + + return useMemo(() => { + if (!context) { + throw new Error('useContextMenu called outside of the provider'); + } + + return { + close: context.close.bind(context), + /** + * @returns a callback ref, passed onto the element responsible for triggering the menu. + */ + register(content: React.ComponentType) { + function listener(event: MouseEvent) { + appendContextMenuContent(event, content); + } + return (trigger: HTMLElement | null) => { + if (previous.current) { + const [previousTrigger, previousListener] = previous.current; + previousTrigger.removeEventListener( + 'contextmenu', + previousListener + ); + } + if (trigger) { + trigger.addEventListener('contextmenu', listener); + previous.current = [trigger, listener]; + } + }; + }, + registerItems(items: MenuItem[]) { + return this.register(() => ); + }, + }; + }, [context, Menu]); +} diff --git a/packages/compass-context-menu/tsconfig-lint.json b/packages/compass-context-menu/tsconfig-lint.json new file mode 100644 index 00000000000..6bdef84f322 --- /dev/null +++ b/packages/compass-context-menu/tsconfig-lint.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.json", + "include": ["**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/compass-context-menu/tsconfig.json b/packages/compass-context-menu/tsconfig.json new file mode 100644 index 00000000000..79bc84584ce --- /dev/null +++ b/packages/compass-context-menu/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@mongodb-js/tsconfig-compass/tsconfig.react.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": ["src/**/*"], + "exclude": ["./src/**/*.spec.*"] +} From 68385135f237ac6811ad22055a28678637c4e440 Mon Sep 17 00:00:00 2001 From: gagik Date: Wed, 21 May 2025 13:41:30 +0200 Subject: [PATCH 02/76] wip --- .../src/context-menu-provider.tsx | 4 +- .../src/use-context-menu.spec.tsx | 144 ++++++++++++++++++ 2 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 packages/compass-context-menu/src/use-context-menu.spec.tsx diff --git a/packages/compass-context-menu/src/context-menu-provider.tsx b/packages/compass-context-menu/src/context-menu-provider.tsx index 7a7b59bf16a..499d9c273c1 100644 --- a/packages/compass-context-menu/src/context-menu-provider.tsx +++ b/packages/compass-context-menu/src/context-menu-provider.tsx @@ -14,7 +14,9 @@ export const Context = createContext(null); export function ContextMenuProvider({ children, -}: React.PropsWithChildren) { +}: { + children: React.ReactNode; +}) { const [menu, setMenu] = useState({ isOpen: false }); const close = useCallback(() => setMenu({ isOpen: false }), [setMenu]); diff --git a/packages/compass-context-menu/src/use-context-menu.spec.tsx b/packages/compass-context-menu/src/use-context-menu.spec.tsx new file mode 100644 index 00000000000..7337463e819 --- /dev/null +++ b/packages/compass-context-menu/src/use-context-menu.spec.tsx @@ -0,0 +1,144 @@ +import React from 'react'; +import { + render, + screen, + cleanup, + userEvent, +} from '@mongodb-js/testing-library-compass'; +import { expect } from 'chai'; +import sinon from 'sinon'; +import { useContextMenu } from './use-context-menu'; +import { ContextMenuProvider } from './context-menu-provider'; +import type { MenuItem } from './types'; + +describe('useContextMenu', function () { + const TestMenu: React.FC<{ items: MenuItem[] }> = ({ items }) => ( +
+ {items.map((item, idx) => ( +
+ {item.label} +
+ ))} +
+ ); + + const TestComponent = ({ + onRegister, + }: { + onRegister?: (ref: any) => void; + }) => { + const contextMenu = useContextMenu({ Menu: TestMenu }); + const items: MenuItem[] = [ + { + label: 'Test Item', + onAction: () => { + /* noop */ + }, + }, + ]; + const ref = contextMenu.registerItems(items); + + React.useEffect(() => { + onRegister?.(ref); + }, [ref, onRegister]); + + return ( +
+ Test Component +
+ ); + }; + + afterEach(cleanup); + + describe('when used outside provider', function () { + it('throws an error', function () { + expect(() => { + render(); + }).to.throw('useContextMenu called outside of the provider'); + }); + }); + + describe('when used inside provider', function () { + beforeEach(() => { + // Create the container for the context menu portal + const container = document.createElement('div'); + container.id = 'context-menu-container'; + document.body.appendChild(container); + }); + + afterEach(() => { + // Clean up the container + const container = document.getElementById('context-menu-container'); + if (container) { + document.body.removeChild(container); + } + }); + + it('renders without error', function () { + render( + + + + ); + + expect(screen.getByTestId('test-trigger')).to.exist; + }); + + it('registers context menu event listener', function () { + const onRegister = sinon.spy(); + + render( + + + + ); + + expect(onRegister).to.have.been.calledOnce; + expect(onRegister.firstCall.args[0]).to.be.a('function'); + }); + + it('shows context menu on right click', function () { + render( + + + + ); + + const trigger = screen.getByTestId('test-trigger'); + userEvent.click(trigger, { button: 2 }); + + // The menu should be rendered in the portal + expect(screen.getByTestId('menu-item-Test Item')).to.exist; + }); + + it('cleans up previous event listener when ref changes', function () { + const removeEventListenerSpy = sinon.spy(); + const addEventListenerSpy = sinon.spy(); + + const { rerender } = render( + + + + ); + + // Simulate ref change + const ref = screen.getByTestId('test-trigger'); + Object.defineProperty(ref, 'addEventListener', { + value: addEventListenerSpy, + }); + Object.defineProperty(ref, 'removeEventListener', { + value: removeEventListenerSpy, + }); + + rerender( + + + + ); + + expect(removeEventListenerSpy).to.have.been.calledWith('contextmenu'); + expect(addEventListenerSpy).to.have.been.calledWith('contextmenu'); + }); + }); +}); From 8e30a83b02f6c897c960905d4e2b310b3d2aba48 Mon Sep 17 00:00:00 2001 From: gagik Date: Wed, 21 May 2025 15:16:18 +0200 Subject: [PATCH 03/76] fix: add tests --- packages/compass-context-menu/.mocharc.js | 2 +- .../src/use-context-menu.spec.tsx | 58 +++++++------------ .../src/use-context-menu.tsx | 6 +- 3 files changed, 26 insertions(+), 40 deletions(-) diff --git a/packages/compass-context-menu/.mocharc.js b/packages/compass-context-menu/.mocharc.js index e7eaccd61fa..5a33f216327 100644 --- a/packages/compass-context-menu/.mocharc.js +++ b/packages/compass-context-menu/.mocharc.js @@ -1,2 +1,2 @@ 'use strict'; -module.exports = require('@mongodb-js/mocha-config-compass'); +module.exports = require('@mongodb-js/mocha-config-compass/react'); diff --git a/packages/compass-context-menu/src/use-context-menu.spec.tsx b/packages/compass-context-menu/src/use-context-menu.spec.tsx index 7337463e819..8a5cb4b6fee 100644 --- a/packages/compass-context-menu/src/use-context-menu.spec.tsx +++ b/packages/compass-context-menu/src/use-context-menu.spec.tsx @@ -1,36 +1,37 @@ import React from 'react'; -import { - render, - screen, - cleanup, - userEvent, -} from '@mongodb-js/testing-library-compass'; +import { render, screen, userEvent } from '@mongodb-js/testing-library-compass'; import { expect } from 'chai'; import sinon from 'sinon'; import { useContextMenu } from './use-context-menu'; import { ContextMenuProvider } from './context-menu-provider'; import type { MenuItem } from './types'; +type TestMenuItem = MenuItem & { id: number }; + describe('useContextMenu', function () { - const TestMenu: React.FC<{ items: MenuItem[] }> = ({ items }) => ( + const TestMenu: React.FC<{ items: TestMenuItem[] }> = ({ items }) => (
{items.map((item, idx) => ( -
+
{item.label}
))}
); - const TestComponent = ({ - onRegister, - }: { - onRegister?: (ref: any) => void; - }) => { + const TestComponent = () => { const contextMenu = useContextMenu({ Menu: TestMenu }); - const items: MenuItem[] = [ + const items: TestMenuItem[] = [ { - label: 'Test Item', + id: 1, + label: 'Test A', + onAction: () => { + /* noop */ + }, + }, + { + id: 2, + label: 'Test B', onAction: () => { /* noop */ }, @@ -38,10 +39,6 @@ describe('useContextMenu', function () { ]; const ref = contextMenu.registerItems(items); - React.useEffect(() => { - onRegister?.(ref); - }, [ref, onRegister]); - return (
Test Component @@ -49,8 +46,6 @@ describe('useContextMenu', function () { ); }; - afterEach(cleanup); - describe('when used outside provider', function () { it('throws an error', function () { expect(() => { @@ -59,7 +54,7 @@ describe('useContextMenu', function () { }); }); - describe('when used inside provider', function () { + describe('with valid provider', function () { beforeEach(() => { // Create the container for the context menu portal const container = document.createElement('div'); @@ -85,19 +80,6 @@ describe('useContextMenu', function () { expect(screen.getByTestId('test-trigger')).to.exist; }); - it('registers context menu event listener', function () { - const onRegister = sinon.spy(); - - render( - - - - ); - - expect(onRegister).to.have.been.calledOnce; - expect(onRegister.firstCall.args[0]).to.be.a('function'); - }); - it('shows context menu on right click', function () { render( @@ -105,11 +87,15 @@ describe('useContextMenu', function () { ); + expect(screen.queryByTestId('menu-item-1')).not.to.exist; + expect(screen.queryByTestId('menu-item-2')).not.to.exist; + const trigger = screen.getByTestId('test-trigger'); userEvent.click(trigger, { button: 2 }); // The menu should be rendered in the portal - expect(screen.getByTestId('menu-item-Test Item')).to.exist; + expect(screen.getByTestId('menu-item-1')).to.exist; + expect(screen.getByTestId('menu-item-2')).to.exist; }); it('cleans up previous event listener when ref changes', function () { diff --git a/packages/compass-context-menu/src/use-context-menu.tsx b/packages/compass-context-menu/src/use-context-menu.tsx index d7ccc114a90..37993b04f15 100644 --- a/packages/compass-context-menu/src/use-context-menu.tsx +++ b/packages/compass-context-menu/src/use-context-menu.tsx @@ -6,11 +6,11 @@ import type { MenuItem } from './types'; /** * @returns an object with methods to {@link register} content for the menu and {@link close} the menu */ -export function useContextMenu({ +export function useContextMenu({ Menu, }: { Menu: React.ComponentType<{ - items: MenuItem[]; + items: T[]; }>; }) { // Get the close function from the ContextProvider @@ -47,7 +47,7 @@ export function useContextMenu({ } }; }, - registerItems(items: MenuItem[]) { + registerItems(items: T[]) { return this.register(() => ); }, }; From 7d17984cae90058b8ed9fd0e15d2c5525d9c64a7 Mon Sep 17 00:00:00 2001 From: gagik Date: Wed, 21 May 2025 16:50:00 +0200 Subject: [PATCH 04/76] fix: add tests and fix types --- packages/compass-context-menu/src/types.ts | 2 +- .../src/use-context-menu.spec.tsx | 219 ++++++++++++++---- 2 files changed, 175 insertions(+), 46 deletions(-) diff --git a/packages/compass-context-menu/src/types.ts b/packages/compass-context-menu/src/types.ts index 07efb491ac6..e9ac549ba63 100644 --- a/packages/compass-context-menu/src/types.ts +++ b/packages/compass-context-menu/src/types.ts @@ -17,5 +17,5 @@ export type ContextMenuContext = { export type MenuItem = { label: string; - onAction: (event: Event) => void; + onAction: (event: React.KeyboardEvent | React.MouseEvent) => void; }; diff --git a/packages/compass-context-menu/src/use-context-menu.spec.tsx b/packages/compass-context-menu/src/use-context-menu.spec.tsx index 8a5cb4b6fee..1d099272104 100644 --- a/packages/compass-context-menu/src/use-context-menu.spec.tsx +++ b/packages/compass-context-menu/src/use-context-menu.spec.tsx @@ -6,39 +6,48 @@ import { useContextMenu } from './use-context-menu'; import { ContextMenuProvider } from './context-menu-provider'; import type { MenuItem } from './types'; -type TestMenuItem = MenuItem & { id: number }; - describe('useContextMenu', function () { - const TestMenu: React.FC<{ items: TestMenuItem[] }> = ({ items }) => ( + const TestMenu: React.FC<{ items: MenuItem[] }> = ({ items }) => (
{items.map((item, idx) => ( -
+
item.onAction?.(event)} + onKeyDown={(event) => { + if (event.key === 'Enter') { + item.onAction?.(event); + } + }} + > {item.label}
))}
); - const TestComponent = () => { + const TestComponent = ({ + onRegister, + onAction, + }: { + onRegister?: (ref: any) => void; + onAction?: (id) => void; + }) => { const contextMenu = useContextMenu({ Menu: TestMenu }); - const items: TestMenuItem[] = [ - { - id: 1, - label: 'Test A', - onAction: () => { - /* noop */ - }, - }, + const items: MenuItem[] = [ { - id: 2, - label: 'Test B', - onAction: () => { - /* noop */ - }, + label: 'Test Item', + onAction: () => onAction?.(1), }, ]; const ref = contextMenu.registerItems(items); + React.useEffect(() => { + onRegister?.(ref); + }, [ref, onRegister]); + return (
Test Component @@ -46,6 +55,60 @@ describe('useContextMenu', function () { ); }; + // Add new test components for nested context menu scenario + const ParentComponent = ({ + onAction, + children, + }: { + onAction?: (id: number) => void; + children?: React.ReactNode; + }) => { + const contextMenu = useContextMenu({ Menu: TestMenu }); + const parentItems: MenuItem[] = [ + { + label: 'Parent Item 1', + onAction: () => onAction?.(1), + }, + { + label: 'Parent Item 2', + onAction: () => onAction?.(2), + }, + ]; + const ref = contextMenu.registerItems(parentItems); + + return ( +
+
Parent Component
+ {children} +
+ ); + }; + + const ChildComponent = ({ + onAction, + }: { + onAction?: (id: number) => void; + }) => { + const contextMenu = useContextMenu({ Menu: TestMenu }); + const childItems: MenuItem[] = [ + { + label: 'Child Item 1', + onAction: () => onAction?.(1), + }, + { + label: 'Child Item 2', + onAction: () => onAction?.(2), + }, + ]; + const ref = contextMenu.registerItems(childItems); + + return ( +
+ Child Component +
+ ); + }; + describe('when used outside provider', function () { it('throws an error', function () { expect(() => { @@ -54,7 +117,7 @@ describe('useContextMenu', function () { }); }); - describe('with valid provider', function () { + describe('with a valid provider', function () { beforeEach(() => { // Create the container for the context menu portal const container = document.createElement('div'); @@ -80,6 +143,19 @@ describe('useContextMenu', function () { expect(screen.getByTestId('test-trigger')).to.exist; }); + it('registers context menu event listener', function () { + const onRegister = sinon.spy(); + + render( + + + + ); + + expect(onRegister).to.have.been.calledOnce; + expect(onRegister.firstCall.args[0]).to.be.a('function'); + }); + it('shows context menu on right click', function () { render( @@ -87,44 +163,97 @@ describe('useContextMenu', function () { ); - expect(screen.queryByTestId('menu-item-1')).not.to.exist; - expect(screen.queryByTestId('menu-item-2')).not.to.exist; - const trigger = screen.getByTestId('test-trigger'); userEvent.click(trigger, { button: 2 }); // The menu should be rendered in the portal - expect(screen.getByTestId('menu-item-1')).to.exist; - expect(screen.getByTestId('menu-item-2')).to.exist; + expect(screen.getByTestId('menu-item-Test Item')).to.exist; }); - it('cleans up previous event listener when ref changes', function () { - const removeEventListenerSpy = sinon.spy(); - const addEventListenerSpy = sinon.spy(); + describe('with nested context menus', function () { + it('shows only parent items when right clicking parent area', function () { + render( + + + + ); - const { rerender } = render( - - - - ); + const parentTrigger = screen.getByTestId('parent-trigger'); + userEvent.click(parentTrigger, { button: 2 }); + + // Should show parent items + expect(screen.getByTestId('menu-item-Parent Item 1')).to.exist; + expect(screen.getByTestId('menu-item-Parent Item 2')).to.exist; - // Simulate ref change - const ref = screen.getByTestId('test-trigger'); - Object.defineProperty(ref, 'addEventListener', { - value: addEventListenerSpy, + // Should not show child items + expect(() => screen.getByTestId('menu-item-Child Item 1')).to.throw; + expect(() => screen.getByTestId('menu-item-Child Item 2')).to.throw; }); - Object.defineProperty(ref, 'removeEventListener', { - value: removeEventListenerSpy, + + it('shows both parent and child items when right clicking child area', function () { + render( + + + + + + ); + + const childTrigger = screen.getByTestId('child-trigger'); + userEvent.click(childTrigger, { button: 2 }); + + // Should show both parent and child items + expect(screen.getByTestId('menu-item-Parent Item 1')).to.exist; + expect(screen.getByTestId('menu-item-Parent Item 2')).to.exist; + expect(screen.getByTestId('menu-item-Child Item 1')).to.exist; + expect(screen.getByTestId('menu-item-Child Item 2')).to.exist; }); - rerender( - - - - ); + it('triggers only the child action when clicking child menu item', function () { + const parentOnAction = sinon.spy(); + const childOnAction = sinon.spy(); + + render( + + + + + + ); + + const childTrigger = screen.getByTestId('child-trigger'); + userEvent.click(childTrigger, { button: 2 }); + + const childItem1 = screen.getByTestId('menu-item-Child Item 1'); + userEvent.click(childItem1); - expect(removeEventListenerSpy).to.have.been.calledWith('contextmenu'); - expect(addEventListenerSpy).to.have.been.calledWith('contextmenu'); + expect(childOnAction).to.have.been.calledOnceWithExactly(1); + expect(parentOnAction).to.not.have.been.called; + expect(() => screen.getByTestId('test-menu')).to.throw; + }); + + it('triggers only the parent action when clicking a parent menu item from child context', function () { + const parentOnAction = sinon.spy(); + const childOnAction = sinon.spy(); + + render( + + + + + + ); + + const childTrigger = screen.getByTestId('child-trigger'); + userEvent.click(childTrigger, { button: 2 }); + + const parentItem1 = screen.getByTestId('menu-item-Parent Item 1'); + userEvent.click(parentItem1); + + expect(parentOnAction).to.have.been.calledOnceWithExactly(1); + expect(childOnAction).to.not.have.been.called; + expect(() => screen.getByTestId('test-menu')).to.throw; + }); }); }); }); From 6004593f9a0c8a458436eab3c9212fdc7d177e37 Mon Sep 17 00:00:00 2001 From: gagik Date: Wed, 21 May 2025 17:07:53 +0200 Subject: [PATCH 05/76] refactor: minor stylistic changes --- .../src/context-menu-provider.tsx | 3 +- .../src/use-context-menu.tsx | 53 +++++++++++-------- 2 files changed, 32 insertions(+), 24 deletions(-) diff --git a/packages/compass-context-menu/src/context-menu-provider.tsx b/packages/compass-context-menu/src/context-menu-provider.tsx index 499d9c273c1..43d9e893367 100644 --- a/packages/compass-context-menu/src/context-menu-provider.tsx +++ b/packages/compass-context-menu/src/context-menu-provider.tsx @@ -35,13 +35,14 @@ export function ContextMenuProvider({ }, }); } - document.addEventListener('contextmenu', handleContextMenu); function handleClosingEvent(event: Event) { if (!event.defaultPrevented) { setMenu({ isOpen: false }); } } + + document.addEventListener('contextmenu', handleContextMenu); document.addEventListener('click', handleClosingEvent); window.addEventListener('resize', handleClosingEvent); diff --git a/packages/compass-context-menu/src/use-context-menu.tsx b/packages/compass-context-menu/src/use-context-menu.tsx index 37993b04f15..91f51c2f849 100644 --- a/packages/compass-context-menu/src/use-context-menu.tsx +++ b/packages/compass-context-menu/src/use-context-menu.tsx @@ -3,16 +3,25 @@ import { Context } from './context-menu-provider'; import { appendContextMenuContent } from './context-menu-content'; import type { MenuItem } from './types'; -/** - * @returns an object with methods to {@link register} content for the menu and {@link close} the menu - */ +export type ContextMenuMethods = { + /** + * Close the context menu. + */ + close: () => void; + /** + * Register the menu items for the context menu. + * @returns a callback ref to be passed onto the element responsible for triggering the menu. + */ + registerItems: (items: T[]) => (trigger: HTMLElement | null) => void; +}; + export function useContextMenu({ Menu, }: { Menu: React.ComponentType<{ items: T[]; }>; -}) { +}): ContextMenuMethods { // Get the close function from the ContextProvider const context = useContext(Context); const previous = useRef void]>( @@ -24,31 +33,29 @@ export function useContextMenu({ throw new Error('useContextMenu called outside of the provider'); } + const register = (content: React.ComponentType) => { + function listener(event: MouseEvent) { + appendContextMenuContent(event, content); + } + return (trigger: HTMLElement | null) => { + if (previous.current) { + const [previousTrigger, previousListener] = previous.current; + previousTrigger.removeEventListener('contextmenu', previousListener); + } + if (trigger) { + trigger.addEventListener('contextmenu', listener); + previous.current = [trigger, listener]; + } + }; + }; + return { close: context.close.bind(context), /** * @returns a callback ref, passed onto the element responsible for triggering the menu. */ - register(content: React.ComponentType) { - function listener(event: MouseEvent) { - appendContextMenuContent(event, content); - } - return (trigger: HTMLElement | null) => { - if (previous.current) { - const [previousTrigger, previousListener] = previous.current; - previousTrigger.removeEventListener( - 'contextmenu', - previousListener - ); - } - if (trigger) { - trigger.addEventListener('contextmenu', listener); - previous.current = [trigger, listener]; - } - }; - }, registerItems(items: T[]) { - return this.register(() => ); + return register(() => ); }, }; }, [context, Menu]); From 43023f4173ec9eb95f0c756c8997d54762c2af38 Mon Sep 17 00:00:00 2001 From: gagik Date: Thu, 22 May 2025 11:59:55 +0200 Subject: [PATCH 06/76] fix: export types and rename MenuItem --- packages/compass-context-menu/src/index.ts | 2 ++ packages/compass-context-menu/src/types.ts | 2 +- .../compass-context-menu/src/use-context-menu.spec.tsx | 10 +++++----- packages/compass-context-menu/src/use-context-menu.tsx | 6 +++--- 4 files changed, 11 insertions(+), 9 deletions(-) create mode 100644 packages/compass-context-menu/src/index.ts diff --git a/packages/compass-context-menu/src/index.ts b/packages/compass-context-menu/src/index.ts new file mode 100644 index 00000000000..d60a97e04b3 --- /dev/null +++ b/packages/compass-context-menu/src/index.ts @@ -0,0 +1,2 @@ +export { useContextMenu } from './use-context-menu'; +export type { ContextMenuItem } from './types'; diff --git a/packages/compass-context-menu/src/types.ts b/packages/compass-context-menu/src/types.ts index e9ac549ba63..f453930dcb3 100644 --- a/packages/compass-context-menu/src/types.ts +++ b/packages/compass-context-menu/src/types.ts @@ -15,7 +15,7 @@ export type ContextMenuContext = { close(): void; }; -export type MenuItem = { +export type ContextMenuItem = { label: string; onAction: (event: React.KeyboardEvent | React.MouseEvent) => void; }; diff --git a/packages/compass-context-menu/src/use-context-menu.spec.tsx b/packages/compass-context-menu/src/use-context-menu.spec.tsx index 1d099272104..eb857db9aeb 100644 --- a/packages/compass-context-menu/src/use-context-menu.spec.tsx +++ b/packages/compass-context-menu/src/use-context-menu.spec.tsx @@ -4,10 +4,10 @@ import { expect } from 'chai'; import sinon from 'sinon'; import { useContextMenu } from './use-context-menu'; import { ContextMenuProvider } from './context-menu-provider'; -import type { MenuItem } from './types'; +import type { ContextMenuItem } from './types'; describe('useContextMenu', function () { - const TestMenu: React.FC<{ items: MenuItem[] }> = ({ items }) => ( + const TestMenu: React.FC<{ items: ContextMenuItem[] }> = ({ items }) => (
{items.map((item, idx) => (
void; }) => { const contextMenu = useContextMenu({ Menu: TestMenu }); - const items: MenuItem[] = [ + const items: ContextMenuItem[] = [ { label: 'Test Item', onAction: () => onAction?.(1), @@ -64,7 +64,7 @@ describe('useContextMenu', function () { children?: React.ReactNode; }) => { const contextMenu = useContextMenu({ Menu: TestMenu }); - const parentItems: MenuItem[] = [ + const parentItems: ContextMenuItem[] = [ { label: 'Parent Item 1', onAction: () => onAction?.(1), @@ -90,7 +90,7 @@ describe('useContextMenu', function () { onAction?: (id: number) => void; }) => { const contextMenu = useContextMenu({ Menu: TestMenu }); - const childItems: MenuItem[] = [ + const childItems: ContextMenuItem[] = [ { label: 'Child Item 1', onAction: () => onAction?.(1), diff --git a/packages/compass-context-menu/src/use-context-menu.tsx b/packages/compass-context-menu/src/use-context-menu.tsx index 91f51c2f849..a0c97dead9b 100644 --- a/packages/compass-context-menu/src/use-context-menu.tsx +++ b/packages/compass-context-menu/src/use-context-menu.tsx @@ -1,9 +1,9 @@ import React, { useContext, useMemo, useRef } from 'react'; import { Context } from './context-menu-provider'; import { appendContextMenuContent } from './context-menu-content'; -import type { MenuItem } from './types'; +import type { ContextMenuItem } from './types'; -export type ContextMenuMethods = { +export type ContextMenuMethods = { /** * Close the context menu. */ @@ -15,7 +15,7 @@ export type ContextMenuMethods = { registerItems: (items: T[]) => (trigger: HTMLElement | null) => void; }; -export function useContextMenu({ +export function useContextMenu({ Menu, }: { Menu: React.ComponentType<{ From 971b0fdca53b556f73888e7acc0a7bd8e5781084 Mon Sep 17 00:00:00 2001 From: gagik Date: Thu, 22 May 2025 12:13:53 +0200 Subject: [PATCH 07/76] fix: basic UI implementation --- .../src/components/context-menu.tsx | 54 +++++++++++++++++++ packages/compass-context-menu/src/index.ts | 2 + packages/compass-context-menu/src/types.ts | 2 +- .../src/use-context-menu.spec.tsx | 10 ++-- .../src/use-context-menu.tsx | 12 +++-- 5 files changed, 69 insertions(+), 11 deletions(-) create mode 100644 packages/compass-components/src/components/context-menu.tsx create mode 100644 packages/compass-context-menu/src/index.ts diff --git a/packages/compass-components/src/components/context-menu.tsx b/packages/compass-components/src/components/context-menu.tsx new file mode 100644 index 00000000000..9bb9440a217 --- /dev/null +++ b/packages/compass-components/src/components/context-menu.tsx @@ -0,0 +1,54 @@ +import React from 'react'; +import { css, cx } from '@leafygreen-ui/emotion'; +import { Menu, MenuItem, MenuSeparator } from './leafygreen'; +import type { ContextMenuItem } from '@mongodb-js/compass-context-menu'; +import { useContextMenu } from '@mongodb-js/compass-context-menu'; + +const menuStyle = css({ + position: 'fixed', + zIndex: 9999, +}); + +export type ContextMenuProps = { + items: ContextMenuItem[]; + className?: string; + 'data-testid'?: string; +}; + +export function ContextMenu({ + items, + className, + 'data-testid': dataTestId, +}: ContextMenuProps) { + return ( + + {items.map((item, idx) => { + const { label, onAction } = item; + const isLastItem = idx === items.length - 1; + + return ( + <> + {!isLastItem && } + { + evt.stopPropagation(); + onAction?.(evt); + }} + > + {label} + + + ); + })} + + ); +} + +export function useContextMenuItems( + items: ContextMenuItem[] +): React.RefCallback { + const contextMenu = useContextMenu({ Menu: ContextMenu }); + return contextMenu.registerItems(items); +} diff --git a/packages/compass-context-menu/src/index.ts b/packages/compass-context-menu/src/index.ts new file mode 100644 index 00000000000..d60a97e04b3 --- /dev/null +++ b/packages/compass-context-menu/src/index.ts @@ -0,0 +1,2 @@ +export { useContextMenu } from './use-context-menu'; +export type { ContextMenuItem } from './types'; diff --git a/packages/compass-context-menu/src/types.ts b/packages/compass-context-menu/src/types.ts index e9ac549ba63..f453930dcb3 100644 --- a/packages/compass-context-menu/src/types.ts +++ b/packages/compass-context-menu/src/types.ts @@ -15,7 +15,7 @@ export type ContextMenuContext = { close(): void; }; -export type MenuItem = { +export type ContextMenuItem = { label: string; onAction: (event: React.KeyboardEvent | React.MouseEvent) => void; }; diff --git a/packages/compass-context-menu/src/use-context-menu.spec.tsx b/packages/compass-context-menu/src/use-context-menu.spec.tsx index 1d099272104..eb857db9aeb 100644 --- a/packages/compass-context-menu/src/use-context-menu.spec.tsx +++ b/packages/compass-context-menu/src/use-context-menu.spec.tsx @@ -4,10 +4,10 @@ import { expect } from 'chai'; import sinon from 'sinon'; import { useContextMenu } from './use-context-menu'; import { ContextMenuProvider } from './context-menu-provider'; -import type { MenuItem } from './types'; +import type { ContextMenuItem } from './types'; describe('useContextMenu', function () { - const TestMenu: React.FC<{ items: MenuItem[] }> = ({ items }) => ( + const TestMenu: React.FC<{ items: ContextMenuItem[] }> = ({ items }) => (
{items.map((item, idx) => (
void; }) => { const contextMenu = useContextMenu({ Menu: TestMenu }); - const items: MenuItem[] = [ + const items: ContextMenuItem[] = [ { label: 'Test Item', onAction: () => onAction?.(1), @@ -64,7 +64,7 @@ describe('useContextMenu', function () { children?: React.ReactNode; }) => { const contextMenu = useContextMenu({ Menu: TestMenu }); - const parentItems: MenuItem[] = [ + const parentItems: ContextMenuItem[] = [ { label: 'Parent Item 1', onAction: () => onAction?.(1), @@ -90,7 +90,7 @@ describe('useContextMenu', function () { onAction?: (id: number) => void; }) => { const contextMenu = useContextMenu({ Menu: TestMenu }); - const childItems: MenuItem[] = [ + const childItems: ContextMenuItem[] = [ { label: 'Child Item 1', onAction: () => onAction?.(1), diff --git a/packages/compass-context-menu/src/use-context-menu.tsx b/packages/compass-context-menu/src/use-context-menu.tsx index 91f51c2f849..fa9e20e7537 100644 --- a/packages/compass-context-menu/src/use-context-menu.tsx +++ b/packages/compass-context-menu/src/use-context-menu.tsx @@ -1,9 +1,9 @@ import React, { useContext, useMemo, useRef } from 'react'; import { Context } from './context-menu-provider'; import { appendContextMenuContent } from './context-menu-content'; -import type { MenuItem } from './types'; +import type { ContextMenuItem } from './types'; -export type ContextMenuMethods = { +export type ContextMenuMethods = { /** * Close the context menu. */ @@ -12,10 +12,10 @@ export type ContextMenuMethods = { * Register the menu items for the context menu. * @returns a callback ref to be passed onto the element responsible for triggering the menu. */ - registerItems: (items: T[]) => (trigger: HTMLElement | null) => void; + registerItems: (items: T[]) => React.RefCallback; }; -export function useContextMenu({ +export function useContextMenu({ Menu, }: { Menu: React.ComponentType<{ @@ -33,7 +33,9 @@ export function useContextMenu({ throw new Error('useContextMenu called outside of the provider'); } - const register = (content: React.ComponentType) => { + const register = ( + content: React.ComponentType + ): React.RefCallback => { function listener(event: MouseEvent) { appendContextMenuContent(event, content); } From 0ad7a63fe23680212cc8be9e00c199a7b144dd2d Mon Sep 17 00:00:00 2001 From: gagik Date: Thu, 22 May 2025 12:14:37 +0200 Subject: [PATCH 08/76] fix: use React.RefCallback --- packages/compass-context-menu/src/use-context-menu.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/compass-context-menu/src/use-context-menu.tsx b/packages/compass-context-menu/src/use-context-menu.tsx index a0c97dead9b..fa9e20e7537 100644 --- a/packages/compass-context-menu/src/use-context-menu.tsx +++ b/packages/compass-context-menu/src/use-context-menu.tsx @@ -12,7 +12,7 @@ export type ContextMenuMethods = { * Register the menu items for the context menu. * @returns a callback ref to be passed onto the element responsible for triggering the menu. */ - registerItems: (items: T[]) => (trigger: HTMLElement | null) => void; + registerItems: (items: T[]) => React.RefCallback; }; export function useContextMenu({ @@ -33,7 +33,9 @@ export function useContextMenu({ throw new Error('useContextMenu called outside of the provider'); } - const register = (content: React.ComponentType) => { + const register = ( + content: React.ComponentType + ): React.RefCallback => { function listener(event: MouseEvent) { appendContextMenuContent(event, content); } From aa9f0cf6dc8ecb7f43cff89b4be35dd286cf29db Mon Sep 17 00:00:00 2001 From: gagik Date: Fri, 23 May 2025 12:08:50 +0200 Subject: [PATCH 09/76] fix: switch to item-based organization --- .../src/components/context-menu.spec.tsx | 149 ++++++++++++++++++ .../src/components/context-menu.tsx | 68 ++++---- packages/compass-components/src/index.ts | 5 + .../src/compass-context-menu.tsx | 24 +++ .../src/context-menu-content.ts | 13 +- .../src/context-menu-provider.tsx | 42 +++-- .../compass-context-menu/src/context-menu.tsx | 22 --- packages/compass-context-menu/src/index.ts | 3 +- packages/compass-context-menu/src/types.ts | 9 +- .../src/use-context-menu.spec.tsx | 63 ++++---- .../src/use-context-menu.tsx | 59 ++++--- .../src/components/desktop-welcome-tab.tsx | 73 ++++++--- .../compass/src/app/components/entrypoint.tsx | 6 +- 13 files changed, 381 insertions(+), 155 deletions(-) create mode 100644 packages/compass-components/src/components/context-menu.spec.tsx create mode 100644 packages/compass-context-menu/src/compass-context-menu.tsx delete mode 100644 packages/compass-context-menu/src/context-menu.tsx diff --git a/packages/compass-components/src/components/context-menu.spec.tsx b/packages/compass-components/src/components/context-menu.spec.tsx new file mode 100644 index 00000000000..e3f84335acf --- /dev/null +++ b/packages/compass-components/src/components/context-menu.spec.tsx @@ -0,0 +1,149 @@ +import React from 'react'; +import { render, screen, userEvent } from '@mongodb-js/testing-library-compass'; +import { expect } from 'chai'; +import sinon from 'sinon'; +import { ContextMenuProvider } from '@mongodb-js/compass-context-menu'; +import { useContextMenuItems, ContextMenu } from './context-menu'; +import type { ContextMenuItem } from '@mongodb-js/compass-context-menu'; + +describe('useContextMenuItems', function () { + const TestComponent = ({ items }: { items: ContextMenuItem[] }) => { + const ref = useContextMenuItems(items); + + return ( +
+ Test Component +
+ ); + }; + + describe('when used outside provider', function () { + it('throws an error', function () { + const items = [ + { + label: 'Test Item', + onAction: () => {}, + }, + ]; + + expect(() => { + render(); + }).to.throw('useContextMenu called outside of the provider'); + }); + }); + + describe('with a valid provider', function () { + beforeEach(() => { + // Create the container for the context menu portal + const container = document.createElement('div'); + container.id = 'context-menu-container'; + document.body.appendChild(container); + }); + + afterEach(() => { + // Clean up the container + const container = document.getElementById('context-menu-container'); + if (container) { + document.body.removeChild(container); + } + }); + + it('renders without error', function () { + const items = [ + { + label: 'Test Item', + onAction: () => {}, + }, + ]; + + render( + + + + ); + + expect(screen.getByTestId('test-trigger')).to.exist; + }); + + it('shows context menu with items on right click', function () { + const items = [ + { + label: 'Test Item 1', + onAction: () => {}, + }, + { + label: 'Test Item 2', + onAction: () => {}, + }, + ]; + + render( + + + + ); + + const trigger = screen.getByTestId('test-trigger'); + userEvent.click(trigger, { button: 2 }); + + // The menu items should be rendered + expect(screen.getByTestId('context-menu-item-Test Item 1')).to.exist; + expect(screen.getByTestId('context-menu-item-Test Item 2')).to.exist; + }); + + it('triggers the correct action when menu item is clicked', function () { + const onAction = sinon.spy(); + const items = [ + { + label: 'Test Item 1', + onAction: () => onAction(1), + }, + { + label: 'Test Item 2', + onAction: () => onAction(2), + }, + ]; + + render( + + + + ); + + const trigger = screen.getByTestId('test-trigger'); + userEvent.click(trigger, { button: 2 }); + + const menuItem = screen.getByTestId('context-menu-item-Test Item 2'); + userEvent.click(menuItem); + + expect(onAction).to.have.been.calledOnceWithExactly(2); + }); + + it('renders menu items with separators', function () { + const items = [ + { + label: 'Test Item 1', + onAction: () => {}, + }, + { + label: 'Test Item 2', + onAction: () => {}, + }, + ]; + + render( + + + + ); + + const trigger = screen.getByTestId('test-trigger'); + userEvent.click(trigger, { button: 2 }); + + // Should find both menu items and a separator between them + expect(screen.getByTestId('context-menu-item-Test Item 1')).to.exist; + expect(screen.getByRole('separator')).to.exist; + expect(screen.getByTestId('context-menu-item-Test Item 2')).to.exist; + }); + }); +}); diff --git a/packages/compass-components/src/components/context-menu.tsx b/packages/compass-components/src/components/context-menu.tsx index 9bb9440a217..9790f306772 100644 --- a/packages/compass-components/src/components/context-menu.tsx +++ b/packages/compass-components/src/components/context-menu.tsx @@ -1,45 +1,53 @@ import React from 'react'; -import { css, cx } from '@leafygreen-ui/emotion'; import { Menu, MenuItem, MenuSeparator } from './leafygreen'; import type { ContextMenuItem } from '@mongodb-js/compass-context-menu'; import { useContextMenu } from '@mongodb-js/compass-context-menu'; +import { ContextMenuProvider as ContextMenuProviderBase } from '@mongodb-js/compass-context-menu'; +import type { ContextMenuItemGroup } from '@mongodb-js/compass-context-menu/dist/types'; -const menuStyle = css({ - position: 'fixed', - zIndex: 9999, -}); +export function ContextMenuProvider({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + {children} + + ); +} export type ContextMenuProps = { - items: ContextMenuItem[]; + itemGroups: ContextMenuItemGroup[]; className?: string; 'data-testid'?: string; }; -export function ContextMenu({ - items, - className, - 'data-testid': dataTestId, -}: ContextMenuProps) { +export function ContextMenu({ itemGroups }: ContextMenuProps) { return ( - - {items.map((item, idx) => { - const { label, onAction } = item; - const isLastItem = idx === items.length - 1; - + + {itemGroups.map((itemGroup: ContextMenuItemGroup, groupIndex: number) => { return ( - <> - {!isLastItem && } - { - evt.stopPropagation(); - onAction?.(evt); - }} - > - {label} - - +
+ {itemGroup.items.map((item: ContextMenuItem, itemIndex: number) => { + return ( + { + console.log('clicked', evt); + item.onAction?.(evt); + }} + > + {item.label} {itemIndex} {groupIndex} + + ); + })} + {groupIndex < itemGroups.length - 1 && ( + + )} +
); })}
@@ -49,6 +57,6 @@ export function ContextMenu({ export function useContextMenuItems( items: ContextMenuItem[] ): React.RefCallback { - const contextMenu = useContextMenu({ Menu: ContextMenu }); + const contextMenu = useContextMenu(); return contextMenu.registerItems(items); } diff --git a/packages/compass-components/src/index.ts b/packages/compass-components/src/index.ts index 1857adc3c99..7cabba8e671 100644 --- a/packages/compass-components/src/index.ts +++ b/packages/compass-components/src/index.ts @@ -93,6 +93,11 @@ export { ModalHeader } from './components/modals/modal-header'; export { FormModal } from './components/modals/form-modal'; export { InfoModal } from './components/modals/info-modal'; +export { + ContextMenuProvider, + useContextMenuItems, +} from './components/context-menu'; + export type { FileInputBackend, ItemAction, diff --git a/packages/compass-context-menu/src/compass-context-menu.tsx b/packages/compass-context-menu/src/compass-context-menu.tsx new file mode 100644 index 00000000000..113b441a25e --- /dev/null +++ b/packages/compass-context-menu/src/compass-context-menu.tsx @@ -0,0 +1,24 @@ +import React from 'react'; + +type ContextMenuProps = React.PropsWithChildren<{ + position: { + x: number; + y: number; + }; +}>; + +export function ContextMenu({ children, position }: ContextMenuProps) { + console.log('ContextMenu', position); + return ( +
+ {children} +
+ ); +} diff --git a/packages/compass-context-menu/src/context-menu-content.ts b/packages/compass-context-menu/src/context-menu-content.ts index 6856f1fe684..c301983a679 100644 --- a/packages/compass-context-menu/src/context-menu-content.ts +++ b/packages/compass-context-menu/src/context-menu-content.ts @@ -1,23 +1,24 @@ +import type { ContextMenuItemGroup } from './types'; + const CONTEXT_MENUS_SYMBOL = Symbol('context_menus'); export type EnhancedMouseEvent = MouseEvent & { - [CONTEXT_MENUS_SYMBOL]?: React.ComponentType[]; + [CONTEXT_MENUS_SYMBOL]?: ContextMenuItemGroup[]; }; export function getContextMenuContent( event: EnhancedMouseEvent -): React.ComponentType[] { +): ContextMenuItemGroup[] { return event[CONTEXT_MENUS_SYMBOL] ?? []; } export function appendContextMenuContent( event: EnhancedMouseEvent, - content: React.ComponentType + content: ContextMenuItemGroup ) { // Initialize if not already patched - if (event[CONTEXT_MENUS_SYMBOL] === undefined) { - event[CONTEXT_MENUS_SYMBOL] = [content]; - return; + if (!event[CONTEXT_MENUS_SYMBOL]) { + event[CONTEXT_MENUS_SYMBOL] = []; } event[CONTEXT_MENUS_SYMBOL].push(content); } diff --git a/packages/compass-context-menu/src/context-menu-provider.tsx b/packages/compass-context-menu/src/context-menu-provider.tsx index 43d9e893367..621ac7281bc 100644 --- a/packages/compass-context-menu/src/context-menu-provider.tsx +++ b/packages/compass-context-menu/src/context-menu-provider.tsx @@ -5,8 +5,12 @@ import React, { useMemo, createContext, } from 'react'; -import type { ContextMenuContext, MenuState } from './types'; -import { ContextMenu } from './context-menu'; +import type { + ContextMenuContext, + ContextMenuItemGroup, + ContextMenuState, +} from './types'; +import { ContextMenu } from './compass-context-menu'; import type { EnhancedMouseEvent } from './context-menu-content'; import { getContextMenuContent } from './context-menu-content'; @@ -14,20 +18,22 @@ export const Context = createContext(null); export function ContextMenuProvider({ children, + wrapper, }: { children: React.ReactNode; + wrapper: React.ComponentType<{ itemGroups: ContextMenuItemGroup[] }>; }) { - const [menu, setMenu] = useState({ isOpen: false }); + const [menu, setMenu] = useState({ isOpen: false }); const close = useCallback(() => setMenu({ isOpen: false }), [setMenu]); useEffect(() => { function handleContextMenu(event: MouseEvent) { + console.log('handleContextMenu', event); event.preventDefault(); + setMenu({ isOpen: true, - children: getContextMenuContent(event as EnhancedMouseEvent).map( - (Content, index) => - ), + itemGroups: getContextMenuContent(event as EnhancedMouseEvent), position: { // TODO: Fix handling offset while scrolling x: event.clientX, @@ -37,16 +43,20 @@ export function ContextMenuProvider({ } function handleClosingEvent(event: Event) { + console.log('handleClosingEvent', event); if (!event.defaultPrevented) { + console.log('setting menu to false'); setMenu({ isOpen: false }); } } + console.log('adding event listeners'); document.addEventListener('contextmenu', handleContextMenu); - document.addEventListener('click', handleClosingEvent); + window.addEventListener('click', handleClosingEvent); window.addEventListener('resize', handleClosingEvent); return () => { + console.log('removing event listeners'); document.removeEventListener('contextmenu', handleContextMenu); document.removeEventListener('click', handleClosingEvent); window.removeEventListener('resize', handleClosingEvent); @@ -60,12 +70,18 @@ export function ContextMenuProvider({ [close] ); + const Wrapper = wrapper ?? React.Fragment; + return ( - <> - {children} - {menu.isOpen && ( - {menu.children} - )} - + + <> + {children} + {menu.isOpen && ( + + + + )} + + ); } diff --git a/packages/compass-context-menu/src/context-menu.tsx b/packages/compass-context-menu/src/context-menu.tsx deleted file mode 100644 index b053bc4963a..00000000000 --- a/packages/compass-context-menu/src/context-menu.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { createPortal } from 'react-dom'; -import React from 'react'; - -type ContextMenuProps = React.PropsWithChildren<{ - position: { - x: number; - y: number; - }; -}>; - -export function ContextMenu({ children, position }: ContextMenuProps) { - const container = document.getElementById('context-menu-container'); - if (container === null) { - throw new Error('Expected a container for the context menu in the DOM'); - } - return createPortal( -
- {children} -
, - container - ); -} diff --git a/packages/compass-context-menu/src/index.ts b/packages/compass-context-menu/src/index.ts index d60a97e04b3..f024b578aa9 100644 --- a/packages/compass-context-menu/src/index.ts +++ b/packages/compass-context-menu/src/index.ts @@ -1,2 +1,3 @@ export { useContextMenu } from './use-context-menu'; -export type { ContextMenuItem } from './types'; +export { ContextMenuProvider } from './context-menu-provider'; +export type { ContextMenuItem, ContextMenuItemGroup } from './types'; diff --git a/packages/compass-context-menu/src/types.ts b/packages/compass-context-menu/src/types.ts index f453930dcb3..a0019fa88df 100644 --- a/packages/compass-context-menu/src/types.ts +++ b/packages/compass-context-menu/src/types.ts @@ -1,10 +1,15 @@ -export type MenuState = +export interface ContextMenuItemGroup { + items: ContextMenuItem[]; + originListener: (event: MouseEvent) => void; +} + +export type ContextMenuState = | { isOpen: false; } | { isOpen: true; - children: React.ReactNode; + itemGroups: ContextMenuItemGroup[]; position: { x: number; y: number; diff --git a/packages/compass-context-menu/src/use-context-menu.spec.tsx b/packages/compass-context-menu/src/use-context-menu.spec.tsx index eb857db9aeb..b9ef7b5cd06 100644 --- a/packages/compass-context-menu/src/use-context-menu.spec.tsx +++ b/packages/compass-context-menu/src/use-context-menu.spec.tsx @@ -4,27 +4,31 @@ import { expect } from 'chai'; import sinon from 'sinon'; import { useContextMenu } from './use-context-menu'; import { ContextMenuProvider } from './context-menu-provider'; -import type { ContextMenuItem } from './types'; +import type { ContextMenuItem, ContextMenuItemGroup } from './types'; describe('useContextMenu', function () { - const TestMenu: React.FC<{ items: ContextMenuItem[] }> = ({ items }) => ( + const TestMenu: React.FC<{ itemGroups: ContextMenuItemGroup[] }> = ({ + itemGroups, + }) => (
- {items.map((item, idx) => ( -
item.onAction?.(event)} - onKeyDown={(event) => { - if (event.key === 'Enter') { - item.onAction?.(event); - } - }} - > - {item.label} -
- ))} + {itemGroups.flatMap((group, groupIdx) => + group.items.map((item, idx) => ( +
item.onAction?.(event)} + onKeyDown={(event) => { + if (event.key === 'Enter') { + item.onAction?.(event); + } + }} + > + {item.label} +
+ )) + )}
); @@ -33,9 +37,9 @@ describe('useContextMenu', function () { onAction, }: { onRegister?: (ref: any) => void; - onAction?: (id) => void; + onAction?: (id: number) => void; }) => { - const contextMenu = useContextMenu({ Menu: TestMenu }); + const contextMenu = useContextMenu(); const items: ContextMenuItem[] = [ { label: 'Test Item', @@ -55,7 +59,6 @@ describe('useContextMenu', function () { ); }; - // Add new test components for nested context menu scenario const ParentComponent = ({ onAction, children, @@ -63,7 +66,7 @@ describe('useContextMenu', function () { onAction?: (id: number) => void; children?: React.ReactNode; }) => { - const contextMenu = useContextMenu({ Menu: TestMenu }); + const contextMenu = useContextMenu(); const parentItems: ContextMenuItem[] = [ { label: 'Parent Item 1', @@ -89,7 +92,7 @@ describe('useContextMenu', function () { }: { onAction?: (id: number) => void; }) => { - const contextMenu = useContextMenu({ Menu: TestMenu }); + const contextMenu = useContextMenu(); const childItems: ContextMenuItem[] = [ { label: 'Child Item 1', @@ -135,7 +138,7 @@ describe('useContextMenu', function () { it('renders without error', function () { render( - + ); @@ -147,7 +150,7 @@ describe('useContextMenu', function () { const onRegister = sinon.spy(); render( - + ); @@ -158,7 +161,7 @@ describe('useContextMenu', function () { it('shows context menu on right click', function () { render( - + ); @@ -173,7 +176,7 @@ describe('useContextMenu', function () { describe('with nested context menus', function () { it('shows only parent items when right clicking parent area', function () { render( - + ); @@ -192,7 +195,7 @@ describe('useContextMenu', function () { it('shows both parent and child items when right clicking child area', function () { render( - + @@ -214,7 +217,7 @@ describe('useContextMenu', function () { const childOnAction = sinon.spy(); render( - + @@ -237,7 +240,7 @@ describe('useContextMenu', function () { const childOnAction = sinon.spy(); render( - + diff --git a/packages/compass-context-menu/src/use-context-menu.tsx b/packages/compass-context-menu/src/use-context-menu.tsx index fa9e20e7537..02b94383faa 100644 --- a/packages/compass-context-menu/src/use-context-menu.tsx +++ b/packages/compass-context-menu/src/use-context-menu.tsx @@ -1,9 +1,10 @@ -import React, { useContext, useMemo, useRef } from 'react'; +import type { RefCallback } from 'react'; +import { useContext, useMemo, useRef } from 'react'; import { Context } from './context-menu-provider'; import { appendContextMenuContent } from './context-menu-content'; import type { ContextMenuItem } from './types'; -export type ContextMenuMethods = { +export type ContextMenuMethods = { /** * Close the context menu. */ @@ -12,17 +13,10 @@ export type ContextMenuMethods = { * Register the menu items for the context menu. * @returns a callback ref to be passed onto the element responsible for triggering the menu. */ - registerItems: (items: T[]) => React.RefCallback; + registerItems: (items: ContextMenuItem[]) => RefCallback; }; -export function useContextMenu({ - Menu, -}: { - Menu: React.ComponentType<{ - items: T[]; - }>; -}): ContextMenuMethods { - // Get the close function from the ContextProvider +export function useContextMenu(): ContextMenuMethods { const context = useContext(Context); const previous = useRef void]>( null @@ -33,32 +27,33 @@ export function useContextMenu({ throw new Error('useContextMenu called outside of the provider'); } - const register = ( - content: React.ComponentType - ): React.RefCallback => { - function listener(event: MouseEvent) { - appendContextMenuContent(event, content); - } - return (trigger: HTMLElement | null) => { - if (previous.current) { - const [previousTrigger, previousListener] = previous.current; - previousTrigger.removeEventListener('contextmenu', previousListener); - } - if (trigger) { - trigger.addEventListener('contextmenu', listener); - previous.current = [trigger, listener]; - } - }; - }; - return { close: context.close.bind(context), /** * @returns a callback ref, passed onto the element responsible for triggering the menu. */ - registerItems(items: T[]) { - return register(() => ); + registerItems(items: ContextMenuItem[]) { + function listener(event: MouseEvent): void { + appendContextMenuContent(event, { + items, + originListener: listener, + }); + } + + return (trigger: HTMLElement | null) => { + if (previous.current) { + const [previousTrigger, previousListener] = previous.current; + previousTrigger.removeEventListener( + 'contextmenu', + previousListener + ); + } + if (trigger) { + trigger.addEventListener('contextmenu', listener); + previous.current = [trigger, listener]; + } + }; }, }; - }, [context, Menu]); + }, [context]); } diff --git a/packages/compass-welcome/src/components/desktop-welcome-tab.tsx b/packages/compass-welcome/src/components/desktop-welcome-tab.tsx index 768a73e0b92..9c39d1005f7 100644 --- a/packages/compass-welcome/src/components/desktop-welcome-tab.tsx +++ b/packages/compass-welcome/src/components/desktop-welcome-tab.tsx @@ -14,6 +14,7 @@ import { cx, useDarkMode, Icon, + useContextMenuItems, } from '@mongodb-js/compass-components'; import { useTelemetry } from '@mongodb-js/compass-telemetry/provider'; import { useConnectionActions } from '@mongodb-js/compass-connections/provider'; @@ -66,11 +67,22 @@ const createClusterButtonLightModeStyles = css({ }); function AtlasHelpSection(): React.ReactElement { - const track = useTelemetry(); const darkMode = useDarkMode(); + const track = useTelemetry(); + const contextRef = useContextMenuItems([ + { + label: '1', + onAction: () => track('Atlas Link Clicked', { screen: 'connect' }), + }, + { + label: '2', + onAction: () => track('Atlas Link Clicked', { screen: 'connect' }), + }, + ]); return (
-
- -
+
); } +function TestClusterButton() { + const track = useTelemetry(); + const darkMode = useDarkMode(); + const contextRef = useContextMenuItems([ + { + label: '123', + onAction: () => track('Atlas Link Clicked', { screen: 'connect' }), + }, + ]); + return ( +
+ +
+ ); +} const welcomeTabStyles = css({ display: 'flex', alignItems: 'center', @@ -125,9 +151,20 @@ export default function DesktopWelcomeTab() { const enableCreatingNewConnections = usePreference( 'enableCreatingNewConnections' ); + const track = useTelemetry(); + const contextRef = useContextMenuItems([ + { + label: '4', + onAction: () => track('Atlas Link Clicked', { screen: 'connect' }), + }, + { + label: '5', + onAction: () => track('Atlas Link Clicked', { screen: 'connect' }), + }, + ]); return ( -
+

Welcome to MongoDB Compass

diff --git a/packages/compass/src/app/components/entrypoint.tsx b/packages/compass/src/app/components/entrypoint.tsx index 493580e6564..1bbcb163d43 100644 --- a/packages/compass/src/app/components/entrypoint.tsx +++ b/packages/compass/src/app/components/entrypoint.tsx @@ -30,6 +30,8 @@ import { createIpcSendTrack, } from '@mongodb-js/compass-telemetry'; import { DataModelStorageServiceProviderElectron } from '@mongodb-js/compass-data-modeling/renderer'; +import { Context } from '@segment/analytics-node'; +import { ContextMenuProvider } from '@mongodb-js/compass-components'; const WithPreferencesAndLoggerProviders: React.FC = ({ children }) => { const loggerProviderValue = useRef({ @@ -101,7 +103,9 @@ export const CompassElectron = (props: React.ComponentProps) => { - + + + From 58df56aa142486422235df04077c702bc7053d0a Mon Sep 17 00:00:00 2001 From: gagik Date: Fri, 23 May 2025 13:41:54 +0200 Subject: [PATCH 10/76] fix: cleanup and switch to menu prop --- .../src/components/context-menu.spec.tsx | 120 +++++++++++------- .../src/components/context-menu.tsx | 96 +++++++++----- .../src/compass-context-menu.tsx | 24 ---- .../src/context-menu-provider.tsx | 61 +++++---- packages/compass-context-menu/src/index.ts | 6 +- packages/compass-context-menu/src/types.ts | 24 ++-- .../src/use-context-menu.spec.tsx | 8 +- .../src/components/desktop-welcome-tab.tsx | 6 +- 8 files changed, 186 insertions(+), 159 deletions(-) delete mode 100644 packages/compass-context-menu/src/compass-context-menu.tsx diff --git a/packages/compass-components/src/components/context-menu.spec.tsx b/packages/compass-components/src/components/context-menu.spec.tsx index e3f84335acf..14177575d9d 100644 --- a/packages/compass-components/src/components/context-menu.spec.tsx +++ b/packages/compass-components/src/components/context-menu.spec.tsx @@ -7,12 +7,23 @@ import { useContextMenuItems, ContextMenu } from './context-menu'; import type { ContextMenuItem } from '@mongodb-js/compass-context-menu'; describe('useContextMenuItems', function () { - const TestComponent = ({ items }: { items: ContextMenuItem[] }) => { + const menuTestTriggerId = 'test-trigger'; + + const TestComponent = ({ + items, + children, + 'data-testid': dataTestId = menuTestTriggerId, + }: { + items: ContextMenuItem[]; + children?: React.ReactNode; + 'data-testid'?: string; + }) => { const ref = useContextMenuItems(items); return ( -
+
Test Component + {children}
); }; @@ -33,21 +44,6 @@ describe('useContextMenuItems', function () { }); describe('with a valid provider', function () { - beforeEach(() => { - // Create the container for the context menu portal - const container = document.createElement('div'); - container.id = 'context-menu-container'; - document.body.appendChild(container); - }); - - afterEach(() => { - // Clean up the container - const container = document.getElementById('context-menu-container'); - if (container) { - document.body.removeChild(container); - } - }); - it('renders without error', function () { const items = [ { @@ -62,7 +58,7 @@ describe('useContextMenuItems', function () { ); - expect(screen.getByTestId('test-trigger')).to.exist; + expect(screen.getByTestId(menuTestTriggerId)).to.exist; }); it('shows context menu with items on right click', function () { @@ -83,12 +79,12 @@ describe('useContextMenuItems', function () { ); - const trigger = screen.getByTestId('test-trigger'); + const trigger = screen.getByTestId(menuTestTriggerId); userEvent.click(trigger, { button: 2 }); // The menu items should be rendered - expect(screen.getByTestId('context-menu-item-Test Item 1')).to.exist; - expect(screen.getByTestId('context-menu-item-Test Item 2')).to.exist; + expect(screen.getByTestId('menu-group-0-item-0')).to.exist; + expect(screen.getByTestId('menu-group-0-item-1')).to.exist; }); it('triggers the correct action when menu item is clicked', function () { @@ -110,40 +106,68 @@ describe('useContextMenuItems', function () { ); - const trigger = screen.getByTestId('test-trigger'); + const trigger = screen.getByTestId(menuTestTriggerId); userEvent.click(trigger, { button: 2 }); - const menuItem = screen.getByTestId('context-menu-item-Test Item 2'); + const menuItem = screen.getByTestId('menu-group-0-item-1'); userEvent.click(menuItem); expect(onAction).to.have.been.calledOnceWithExactly(2); }); - it('renders menu items with separators', function () { - const items = [ - { - label: 'Test Item 1', - onAction: () => {}, - }, - { - label: 'Test Item 2', - onAction: () => {}, - }, - ]; - - render( - - - - ); - - const trigger = screen.getByTestId('test-trigger'); - userEvent.click(trigger, { button: 2 }); - - // Should find both menu items and a separator between them - expect(screen.getByTestId('context-menu-item-Test Item 1')).to.exist; - expect(screen.getByRole('separator')).to.exist; - expect(screen.getByTestId('context-menu-item-Test Item 2')).to.exist; + describe('with nested components', function () { + const childTriggerId = 'child-trigger'; + + beforeEach(function () { + const items = [ + { + label: 'Test Item 1', + onAction: () => {}, + }, + { + label: 'Test Item 2', + onAction: () => {}, + }, + ]; + + const childItems = [ + { + label: 'Child Item 1', + onAction: () => {}, + }, + ]; + + render( + + + + + + ); + }); + + it('renders menu items with separators', function () { + const trigger = screen.getByTestId(childTriggerId); + userEvent.click(trigger, { button: 2 }); + + // Should find the menu item and the separator + expect(screen.getByTestId('menu-group-0').children.length).to.equal(2); + expect( + screen.getByTestId('menu-group-0').children.item(0)?.textContent + ).to.equal('Child Item 1'); + + expect(screen.getByTestId('menu-group-0-separator')).to.exist; + + expect(screen.getByTestId('menu-group-1').children.length).to.equal(2); + expect( + screen.getByTestId('menu-group-1').children.item(0)?.textContent + ).to.equal('Test Item 1'); + expect( + screen.getByTestId('menu-group-1').children.item(1)?.textContent + ).to.equal('Test Item 2'); + + expect(screen.queryByTestId('menu-group-1-separator')).not.to.exist; + }); }); }); }); diff --git a/packages/compass-components/src/components/context-menu.tsx b/packages/compass-components/src/components/context-menu.tsx index 9790f306772..c3fdae09d68 100644 --- a/packages/compass-components/src/components/context-menu.tsx +++ b/packages/compass-components/src/components/context-menu.tsx @@ -1,9 +1,12 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { Menu, MenuItem, MenuSeparator } from './leafygreen'; import type { ContextMenuItem } from '@mongodb-js/compass-context-menu'; import { useContextMenu } from '@mongodb-js/compass-context-menu'; import { ContextMenuProvider as ContextMenuProviderBase } from '@mongodb-js/compass-context-menu'; -import type { ContextMenuItemGroup } from '@mongodb-js/compass-context-menu/dist/types'; +import type { + ContextMenuItemGroup, + ContextMenuWrapperProps, +} from '@mongodb-js/compass-context-menu/dist/types'; export function ContextMenuProvider({ children, @@ -17,40 +20,65 @@ export function ContextMenuProvider({ ); } -export type ContextMenuProps = { - itemGroups: ContextMenuItemGroup[]; - className?: string; - 'data-testid'?: string; -}; +export function ContextMenu({ menu }: ContextMenuWrapperProps) { + const position = menu.position; + const itemGroups = menu.itemGroups; + + useEffect(() => { + if (!menu.isOpen) { + menu.close(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [menu.isOpen]); -export function ContextMenu({ itemGroups }: ContextMenuProps) { return ( - - {itemGroups.map((itemGroup: ContextMenuItemGroup, groupIndex: number) => { - return ( -
- {itemGroup.items.map((item: ContextMenuItem, itemIndex: number) => { - return ( - { - console.log('clicked', evt); - item.onAction?.(evt); - }} - > - {item.label} {itemIndex} {groupIndex} - - ); - })} - {groupIndex < itemGroups.length - 1 && ( - - )} -
- ); - })} -
+
+ + {itemGroups.map( + (itemGroup: ContextMenuItemGroup, groupIndex: number) => { + return ( +
+ {itemGroup.items.map( + (item: ContextMenuItem, itemIndex: number) => { + return ( + { + item.onAction?.(evt); + menu.close(); + }} + > + {item.label} + + ); + } + )} + {groupIndex < itemGroups.length - 1 && ( +
+ +
+ )} +
+ ); + } + )} +
+
); } diff --git a/packages/compass-context-menu/src/compass-context-menu.tsx b/packages/compass-context-menu/src/compass-context-menu.tsx deleted file mode 100644 index 113b441a25e..00000000000 --- a/packages/compass-context-menu/src/compass-context-menu.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; - -type ContextMenuProps = React.PropsWithChildren<{ - position: { - x: number; - y: number; - }; -}>; - -export function ContextMenu({ children, position }: ContextMenuProps) { - console.log('ContextMenu', position); - return ( -
- {children} -
- ); -} diff --git a/packages/compass-context-menu/src/context-menu-provider.tsx b/packages/compass-context-menu/src/context-menu-provider.tsx index 621ac7281bc..5dd1cba2cff 100644 --- a/packages/compass-context-menu/src/context-menu-provider.tsx +++ b/packages/compass-context-menu/src/context-menu-provider.tsx @@ -5,12 +5,7 @@ import React, { useMemo, createContext, } from 'react'; -import type { - ContextMenuContext, - ContextMenuItemGroup, - ContextMenuState, -} from './types'; -import { ContextMenu } from './compass-context-menu'; +import type { ContextMenuContext, ContextMenuState } from './types'; import type { EnhancedMouseEvent } from './context-menu-content'; import { getContextMenuContent } from './context-menu-content'; @@ -21,19 +16,39 @@ export function ContextMenuProvider({ wrapper, }: { children: React.ReactNode; - wrapper: React.ComponentType<{ itemGroups: ContextMenuItemGroup[] }>; + wrapper: React.ComponentType<{ + menu: ContextMenuState & { close: () => void }; + }>; }) { - const [menu, setMenu] = useState({ isOpen: false }); - const close = useCallback(() => setMenu({ isOpen: false }), [setMenu]); + const [menu, setMenu] = useState({ + isOpen: false, + itemGroups: [], + position: { x: 0, y: 0 }, + }); + const close = useCallback(() => setMenu({ ...menu, isOpen: false }), [menu]); + + const handleClosingEvent = useCallback( + (event: Event) => { + if (!event.defaultPrevented) { + setMenu({ ...menu, isOpen: false }); + } + }, + [menu] + ); useEffect(() => { function handleContextMenu(event: MouseEvent) { - console.log('handleContextMenu', event); event.preventDefault(); + const itemGroups = getContextMenuContent(event as EnhancedMouseEvent); + + if (itemGroups.length === 0) { + return; + } + setMenu({ isOpen: true, - itemGroups: getContextMenuContent(event as EnhancedMouseEvent), + itemGroups, position: { // TODO: Fix handling offset while scrolling x: event.clientX, @@ -42,26 +57,14 @@ export function ContextMenuProvider({ }); } - function handleClosingEvent(event: Event) { - console.log('handleClosingEvent', event); - if (!event.defaultPrevented) { - console.log('setting menu to false'); - setMenu({ isOpen: false }); - } - } - - console.log('adding event listeners'); document.addEventListener('contextmenu', handleContextMenu); - window.addEventListener('click', handleClosingEvent); window.addEventListener('resize', handleClosingEvent); return () => { - console.log('removing event listeners'); document.removeEventListener('contextmenu', handleContextMenu); - document.removeEventListener('click', handleClosingEvent); window.removeEventListener('resize', handleClosingEvent); }; - }, [setMenu]); + }, [handleClosingEvent]); const value = useMemo( () => ({ @@ -74,14 +77,8 @@ export function ContextMenuProvider({ return ( - <> - {children} - {menu.isOpen && ( - - - - )} - + {children} + ); } diff --git a/packages/compass-context-menu/src/index.ts b/packages/compass-context-menu/src/index.ts index f024b578aa9..75d933ef767 100644 --- a/packages/compass-context-menu/src/index.ts +++ b/packages/compass-context-menu/src/index.ts @@ -1,3 +1,7 @@ export { useContextMenu } from './use-context-menu'; export { ContextMenuProvider } from './context-menu-provider'; -export type { ContextMenuItem, ContextMenuItemGroup } from './types'; +export type { + ContextMenuItem, + ContextMenuItemGroup, + ContextMenuWrapperProps, +} from './types'; diff --git a/packages/compass-context-menu/src/types.ts b/packages/compass-context-menu/src/types.ts index a0019fa88df..91e8d65cdcc 100644 --- a/packages/compass-context-menu/src/types.ts +++ b/packages/compass-context-menu/src/types.ts @@ -3,18 +3,18 @@ export interface ContextMenuItemGroup { originListener: (event: MouseEvent) => void; } -export type ContextMenuState = - | { - isOpen: false; - } - | { - isOpen: true; - itemGroups: ContextMenuItemGroup[]; - position: { - x: number; - y: number; - }; - }; +export type ContextMenuState = { + isOpen: boolean; + itemGroups: ContextMenuItemGroup[]; + position: { + x: number; + y: number; + }; +}; + +export type ContextMenuWrapperProps = { + menu: ContextMenuState & { close: () => void }; +}; export type ContextMenuContext = { close(): void; diff --git a/packages/compass-context-menu/src/use-context-menu.spec.tsx b/packages/compass-context-menu/src/use-context-menu.spec.tsx index b9ef7b5cd06..cfe0bfc7ef4 100644 --- a/packages/compass-context-menu/src/use-context-menu.spec.tsx +++ b/packages/compass-context-menu/src/use-context-menu.spec.tsx @@ -4,14 +4,12 @@ import { expect } from 'chai'; import sinon from 'sinon'; import { useContextMenu } from './use-context-menu'; import { ContextMenuProvider } from './context-menu-provider'; -import type { ContextMenuItem, ContextMenuItemGroup } from './types'; +import type { ContextMenuItem, ContextMenuWrapperProps } from './types'; describe('useContextMenu', function () { - const TestMenu: React.FC<{ itemGroups: ContextMenuItemGroup[] }> = ({ - itemGroups, - }) => ( + const TestMenu: React.FC = ({ menu }) => (
- {itemGroups.flatMap((group, groupIdx) => + {menu.itemGroups.flatMap((group, groupIdx) => group.items.map((item, idx) => (
track('Atlas Link Clicked', { screen: 'connect' }), + onAction: () => console.log('Atlas Link Clicked', { screen: 'connect' }), }, { label: '2', - onAction: () => track('Atlas Link Clicked', { screen: 'connect' }), + onAction: () => console.log('Atlas Link Clicked', { screen: 'connect' }), }, ]); @@ -111,7 +111,7 @@ function TestClusterButton() { const contextRef = useContextMenuItems([ { label: '123', - onAction: () => track('Atlas Link Clicked', { screen: 'connect' }), + onAction: () => console.log('Atlas Link Clicked', { screen: 'connect' }), }, ]); return ( From a54c7384b434ccfce970eacd5e32db7597d45555 Mon Sep 17 00:00:00 2001 From: gagik Date: Fri, 23 May 2025 13:50:50 +0200 Subject: [PATCH 11/76] refactor: use item groups instead of React elements, use wrapper, keep menu state consistent The refactor is meant to make the Leafygreen integration more straightforward: 1. We stick to item groups and instead have a single wrapper to handle any rendering differences between groups. This allows the wrapper to always have context of all items when rendering which is useful when inserting menu seperators in Leafygreen. Also encourages consistent UI (while allowing per-case customization if needed at wrapper-level). We could introduce itemWrappers instead of itemGroups but having one wrapper handling all seems cleaner to me. 2. More of the responsibility is moved to a parent wrapper component that will house the context menu. This allows us to standardize the right click menu and make better use of Leafygreen's menu component including its click handling (which has been removed from the context menu library). 3. Menu state (i.e. position) is now preserved even closed; this is useful for leafygreen's menu to animate in the same position instead of losing the position all together. --- .../src/context-menu-content.ts | 13 ++-- .../src/context-menu-provider.tsx | 57 ++++++++++------- packages/compass-context-menu/src/index.ts | 7 ++- packages/compass-context-menu/src/types.ts | 29 +++++---- .../src/use-context-menu.spec.tsx | 61 ++++++++++--------- .../src/use-context-menu.tsx | 59 +++++++++--------- 6 files changed, 124 insertions(+), 102 deletions(-) diff --git a/packages/compass-context-menu/src/context-menu-content.ts b/packages/compass-context-menu/src/context-menu-content.ts index 6856f1fe684..c301983a679 100644 --- a/packages/compass-context-menu/src/context-menu-content.ts +++ b/packages/compass-context-menu/src/context-menu-content.ts @@ -1,23 +1,24 @@ +import type { ContextMenuItemGroup } from './types'; + const CONTEXT_MENUS_SYMBOL = Symbol('context_menus'); export type EnhancedMouseEvent = MouseEvent & { - [CONTEXT_MENUS_SYMBOL]?: React.ComponentType[]; + [CONTEXT_MENUS_SYMBOL]?: ContextMenuItemGroup[]; }; export function getContextMenuContent( event: EnhancedMouseEvent -): React.ComponentType[] { +): ContextMenuItemGroup[] { return event[CONTEXT_MENUS_SYMBOL] ?? []; } export function appendContextMenuContent( event: EnhancedMouseEvent, - content: React.ComponentType + content: ContextMenuItemGroup ) { // Initialize if not already patched - if (event[CONTEXT_MENUS_SYMBOL] === undefined) { - event[CONTEXT_MENUS_SYMBOL] = [content]; - return; + if (!event[CONTEXT_MENUS_SYMBOL]) { + event[CONTEXT_MENUS_SYMBOL] = []; } event[CONTEXT_MENUS_SYMBOL].push(content); } diff --git a/packages/compass-context-menu/src/context-menu-provider.tsx b/packages/compass-context-menu/src/context-menu-provider.tsx index 43d9e893367..5dd1cba2cff 100644 --- a/packages/compass-context-menu/src/context-menu-provider.tsx +++ b/packages/compass-context-menu/src/context-menu-provider.tsx @@ -5,8 +5,7 @@ import React, { useMemo, createContext, } from 'react'; -import type { ContextMenuContext, MenuState } from './types'; -import { ContextMenu } from './context-menu'; +import type { ContextMenuContext, ContextMenuState } from './types'; import type { EnhancedMouseEvent } from './context-menu-content'; import { getContextMenuContent } from './context-menu-content'; @@ -14,20 +13,42 @@ export const Context = createContext(null); export function ContextMenuProvider({ children, + wrapper, }: { children: React.ReactNode; + wrapper: React.ComponentType<{ + menu: ContextMenuState & { close: () => void }; + }>; }) { - const [menu, setMenu] = useState({ isOpen: false }); - const close = useCallback(() => setMenu({ isOpen: false }), [setMenu]); + const [menu, setMenu] = useState({ + isOpen: false, + itemGroups: [], + position: { x: 0, y: 0 }, + }); + const close = useCallback(() => setMenu({ ...menu, isOpen: false }), [menu]); + + const handleClosingEvent = useCallback( + (event: Event) => { + if (!event.defaultPrevented) { + setMenu({ ...menu, isOpen: false }); + } + }, + [menu] + ); useEffect(() => { function handleContextMenu(event: MouseEvent) { event.preventDefault(); + + const itemGroups = getContextMenuContent(event as EnhancedMouseEvent); + + if (itemGroups.length === 0) { + return; + } + setMenu({ isOpen: true, - children: getContextMenuContent(event as EnhancedMouseEvent).map( - (Content, index) => - ), + itemGroups, position: { // TODO: Fix handling offset while scrolling x: event.clientX, @@ -36,22 +57,14 @@ export function ContextMenuProvider({ }); } - function handleClosingEvent(event: Event) { - if (!event.defaultPrevented) { - setMenu({ isOpen: false }); - } - } - document.addEventListener('contextmenu', handleContextMenu); - document.addEventListener('click', handleClosingEvent); window.addEventListener('resize', handleClosingEvent); return () => { document.removeEventListener('contextmenu', handleContextMenu); - document.removeEventListener('click', handleClosingEvent); window.removeEventListener('resize', handleClosingEvent); }; - }, [setMenu]); + }, [handleClosingEvent]); const value = useMemo( () => ({ @@ -60,12 +73,12 @@ export function ContextMenuProvider({ [close] ); + const Wrapper = wrapper ?? React.Fragment; + return ( - <> - {children} - {menu.isOpen && ( - {menu.children} - )} - + + {children} + + ); } diff --git a/packages/compass-context-menu/src/index.ts b/packages/compass-context-menu/src/index.ts index d60a97e04b3..75d933ef767 100644 --- a/packages/compass-context-menu/src/index.ts +++ b/packages/compass-context-menu/src/index.ts @@ -1,2 +1,7 @@ export { useContextMenu } from './use-context-menu'; -export type { ContextMenuItem } from './types'; +export { ContextMenuProvider } from './context-menu-provider'; +export type { + ContextMenuItem, + ContextMenuItemGroup, + ContextMenuWrapperProps, +} from './types'; diff --git a/packages/compass-context-menu/src/types.ts b/packages/compass-context-menu/src/types.ts index f453930dcb3..91e8d65cdcc 100644 --- a/packages/compass-context-menu/src/types.ts +++ b/packages/compass-context-menu/src/types.ts @@ -1,15 +1,20 @@ -export type MenuState = - | { - isOpen: false; - } - | { - isOpen: true; - children: React.ReactNode; - position: { - x: number; - y: number; - }; - }; +export interface ContextMenuItemGroup { + items: ContextMenuItem[]; + originListener: (event: MouseEvent) => void; +} + +export type ContextMenuState = { + isOpen: boolean; + itemGroups: ContextMenuItemGroup[]; + position: { + x: number; + y: number; + }; +}; + +export type ContextMenuWrapperProps = { + menu: ContextMenuState & { close: () => void }; +}; export type ContextMenuContext = { close(): void; diff --git a/packages/compass-context-menu/src/use-context-menu.spec.tsx b/packages/compass-context-menu/src/use-context-menu.spec.tsx index eb857db9aeb..cfe0bfc7ef4 100644 --- a/packages/compass-context-menu/src/use-context-menu.spec.tsx +++ b/packages/compass-context-menu/src/use-context-menu.spec.tsx @@ -4,27 +4,29 @@ import { expect } from 'chai'; import sinon from 'sinon'; import { useContextMenu } from './use-context-menu'; import { ContextMenuProvider } from './context-menu-provider'; -import type { ContextMenuItem } from './types'; +import type { ContextMenuItem, ContextMenuWrapperProps } from './types'; describe('useContextMenu', function () { - const TestMenu: React.FC<{ items: ContextMenuItem[] }> = ({ items }) => ( + const TestMenu: React.FC = ({ menu }) => (
- {items.map((item, idx) => ( -
item.onAction?.(event)} - onKeyDown={(event) => { - if (event.key === 'Enter') { - item.onAction?.(event); - } - }} - > - {item.label} -
- ))} + {menu.itemGroups.flatMap((group, groupIdx) => + group.items.map((item, idx) => ( +
item.onAction?.(event)} + onKeyDown={(event) => { + if (event.key === 'Enter') { + item.onAction?.(event); + } + }} + > + {item.label} +
+ )) + )}
); @@ -33,9 +35,9 @@ describe('useContextMenu', function () { onAction, }: { onRegister?: (ref: any) => void; - onAction?: (id) => void; + onAction?: (id: number) => void; }) => { - const contextMenu = useContextMenu({ Menu: TestMenu }); + const contextMenu = useContextMenu(); const items: ContextMenuItem[] = [ { label: 'Test Item', @@ -55,7 +57,6 @@ describe('useContextMenu', function () { ); }; - // Add new test components for nested context menu scenario const ParentComponent = ({ onAction, children, @@ -63,7 +64,7 @@ describe('useContextMenu', function () { onAction?: (id: number) => void; children?: React.ReactNode; }) => { - const contextMenu = useContextMenu({ Menu: TestMenu }); + const contextMenu = useContextMenu(); const parentItems: ContextMenuItem[] = [ { label: 'Parent Item 1', @@ -89,7 +90,7 @@ describe('useContextMenu', function () { }: { onAction?: (id: number) => void; }) => { - const contextMenu = useContextMenu({ Menu: TestMenu }); + const contextMenu = useContextMenu(); const childItems: ContextMenuItem[] = [ { label: 'Child Item 1', @@ -135,7 +136,7 @@ describe('useContextMenu', function () { it('renders without error', function () { render( - + ); @@ -147,7 +148,7 @@ describe('useContextMenu', function () { const onRegister = sinon.spy(); render( - + ); @@ -158,7 +159,7 @@ describe('useContextMenu', function () { it('shows context menu on right click', function () { render( - + ); @@ -173,7 +174,7 @@ describe('useContextMenu', function () { describe('with nested context menus', function () { it('shows only parent items when right clicking parent area', function () { render( - + ); @@ -192,7 +193,7 @@ describe('useContextMenu', function () { it('shows both parent and child items when right clicking child area', function () { render( - + @@ -214,7 +215,7 @@ describe('useContextMenu', function () { const childOnAction = sinon.spy(); render( - + @@ -237,7 +238,7 @@ describe('useContextMenu', function () { const childOnAction = sinon.spy(); render( - + diff --git a/packages/compass-context-menu/src/use-context-menu.tsx b/packages/compass-context-menu/src/use-context-menu.tsx index fa9e20e7537..a60aeba4c69 100644 --- a/packages/compass-context-menu/src/use-context-menu.tsx +++ b/packages/compass-context-menu/src/use-context-menu.tsx @@ -1,4 +1,5 @@ -import React, { useContext, useMemo, useRef } from 'react'; +import type { RefCallback } from 'react'; +import { useContext, useMemo, useRef } from 'react'; import { Context } from './context-menu-provider'; import { appendContextMenuContent } from './context-menu-content'; import type { ContextMenuItem } from './types'; @@ -12,17 +13,12 @@ export type ContextMenuMethods = { * Register the menu items for the context menu. * @returns a callback ref to be passed onto the element responsible for triggering the menu. */ - registerItems: (items: T[]) => React.RefCallback; + registerItems: (items: T[]) => RefCallback; }; -export function useContextMenu({ - Menu, -}: { - Menu: React.ComponentType<{ - items: T[]; - }>; -}): ContextMenuMethods { - // Get the close function from the ContextProvider +export function useContextMenu< + T extends ContextMenuItem = ContextMenuItem +>(): ContextMenuMethods { const context = useContext(Context); const previous = useRef void]>( null @@ -33,32 +29,33 @@ export function useContextMenu({ throw new Error('useContextMenu called outside of the provider'); } - const register = ( - content: React.ComponentType - ): React.RefCallback => { - function listener(event: MouseEvent) { - appendContextMenuContent(event, content); - } - return (trigger: HTMLElement | null) => { - if (previous.current) { - const [previousTrigger, previousListener] = previous.current; - previousTrigger.removeEventListener('contextmenu', previousListener); - } - if (trigger) { - trigger.addEventListener('contextmenu', listener); - previous.current = [trigger, listener]; - } - }; - }; - return { close: context.close.bind(context), /** * @returns a callback ref, passed onto the element responsible for triggering the menu. */ - registerItems(items: T[]) { - return register(() => ); + registerItems(items: ContextMenuItem[]) { + function listener(event: MouseEvent): void { + appendContextMenuContent(event, { + items, + originListener: listener, + }); + } + + return (trigger: HTMLElement | null) => { + if (previous.current) { + const [previousTrigger, previousListener] = previous.current; + previousTrigger.removeEventListener( + 'contextmenu', + previousListener + ); + } + if (trigger) { + trigger.addEventListener('contextmenu', listener); + previous.current = [trigger, listener]; + } + }; }, }; - }, [context, Menu]); + }, [context]); } From 56d11b60c1c6136466a7f14a3508d01789ae86bb Mon Sep 17 00:00:00 2001 From: gagik Date: Fri, 23 May 2025 15:17:47 +0200 Subject: [PATCH 12/76] fix: revert test environment --- .../src/components/desktop-welcome-tab.tsx | 73 +++++-------------- 1 file changed, 18 insertions(+), 55 deletions(-) diff --git a/packages/compass-welcome/src/components/desktop-welcome-tab.tsx b/packages/compass-welcome/src/components/desktop-welcome-tab.tsx index 59eaeba9da7..768a73e0b92 100644 --- a/packages/compass-welcome/src/components/desktop-welcome-tab.tsx +++ b/packages/compass-welcome/src/components/desktop-welcome-tab.tsx @@ -14,7 +14,6 @@ import { cx, useDarkMode, Icon, - useContextMenuItems, } from '@mongodb-js/compass-components'; import { useTelemetry } from '@mongodb-js/compass-telemetry/provider'; import { useConnectionActions } from '@mongodb-js/compass-connections/provider'; @@ -67,22 +66,11 @@ const createClusterButtonLightModeStyles = css({ }); function AtlasHelpSection(): React.ReactElement { - const darkMode = useDarkMode(); const track = useTelemetry(); - const contextRef = useContextMenuItems([ - { - label: '1', - onAction: () => console.log('Atlas Link Clicked', { screen: 'connect' }), - }, - { - label: '2', - onAction: () => console.log('Atlas Link Clicked', { screen: 'connect' }), - }, - ]); + const darkMode = useDarkMode(); return (
- +
+ +
); } -function TestClusterButton() { - const track = useTelemetry(); - const darkMode = useDarkMode(); - const contextRef = useContextMenuItems([ - { - label: '123', - onAction: () => console.log('Atlas Link Clicked', { screen: 'connect' }), - }, - ]); - return ( -
- -
- ); -} const welcomeTabStyles = css({ display: 'flex', alignItems: 'center', @@ -151,20 +125,9 @@ export default function DesktopWelcomeTab() { const enableCreatingNewConnections = usePreference( 'enableCreatingNewConnections' ); - const track = useTelemetry(); - const contextRef = useContextMenuItems([ - { - label: '4', - onAction: () => track('Atlas Link Clicked', { screen: 'connect' }), - }, - { - label: '5', - onAction: () => track('Atlas Link Clicked', { screen: 'connect' }), - }, - ]); return ( -
+

Welcome to MongoDB Compass

From 743f0688320a1ad0c87782ac828f24b8c96d1284 Mon Sep 17 00:00:00 2001 From: gagik Date: Fri, 23 May 2025 16:54:04 +0200 Subject: [PATCH 13/76] fix: justify start to make it prefer right way popups and remove comment --- .../compass-components/src/components/context-menu.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/compass-components/src/components/context-menu.tsx b/packages/compass-components/src/components/context-menu.tsx index c3fdae09d68..c78519e399c 100644 --- a/packages/compass-components/src/components/context-menu.tsx +++ b/packages/compass-components/src/components/context-menu.tsx @@ -28,8 +28,7 @@ export function ContextMenu({ menu }: ContextMenuWrapperProps) { if (!menu.isOpen) { menu.close(); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [menu.isOpen]); + }, [menu, menu.isOpen]); return (
- + {itemGroups.map( (itemGroup: ContextMenuItemGroup, groupIndex: number) => { return ( From ab14b78be3f20211d9f189789d3906afd2dd908a Mon Sep 17 00:00:00 2001 From: gagik Date: Fri, 23 May 2025 17:19:26 +0200 Subject: [PATCH 14/76] fix: remove redundant context import --- packages/compass/src/app/components/entrypoint.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/compass/src/app/components/entrypoint.tsx b/packages/compass/src/app/components/entrypoint.tsx index 1bbcb163d43..866b3414188 100644 --- a/packages/compass/src/app/components/entrypoint.tsx +++ b/packages/compass/src/app/components/entrypoint.tsx @@ -30,7 +30,6 @@ import { createIpcSendTrack, } from '@mongodb-js/compass-telemetry'; import { DataModelStorageServiceProviderElectron } from '@mongodb-js/compass-data-modeling/renderer'; -import { Context } from '@segment/analytics-node'; import { ContextMenuProvider } from '@mongodb-js/compass-components'; const WithPreferencesAndLoggerProviders: React.FC = ({ children }) => { From 1d279caf13cf4a1fe03dc39fda901441f8d28c3d Mon Sep 17 00:00:00 2001 From: gagik Date: Tue, 20 May 2025 16:59:43 +0200 Subject: [PATCH 15/76] feat(compass-context-menu): add a headless context menu package --- package-lock.json | 104 ++++++++++++++++++ packages/compass-context-menu/.depcheckrc | 8 ++ packages/compass-context-menu/.eslintignore | 2 + packages/compass-context-menu/.eslintrc.js | 9 ++ packages/compass-context-menu/.mocharc.js | 2 + packages/compass-context-menu/package.json | 72 ++++++++++++ .../src/context-menu-content.ts | 23 ++++ .../src/context-menu-provider.tsx | 68 ++++++++++++ .../compass-context-menu/src/context-menu.tsx | 22 ++++ packages/compass-context-menu/src/types.ts | 21 ++++ .../src/use-context-menu.tsx | 55 +++++++++ .../compass-context-menu/tsconfig-lint.json | 5 + packages/compass-context-menu/tsconfig.json | 8 ++ 13 files changed, 399 insertions(+) create mode 100644 packages/compass-context-menu/.depcheckrc create mode 100644 packages/compass-context-menu/.eslintignore create mode 100644 packages/compass-context-menu/.eslintrc.js create mode 100644 packages/compass-context-menu/.mocharc.js create mode 100644 packages/compass-context-menu/package.json create mode 100644 packages/compass-context-menu/src/context-menu-content.ts create mode 100644 packages/compass-context-menu/src/context-menu-provider.tsx create mode 100644 packages/compass-context-menu/src/context-menu.tsx create mode 100644 packages/compass-context-menu/src/types.ts create mode 100644 packages/compass-context-menu/src/use-context-menu.tsx create mode 100644 packages/compass-context-menu/tsconfig-lint.json create mode 100644 packages/compass-context-menu/tsconfig.json diff --git a/package-lock.json b/package-lock.json index 284ef8ff00f..136ac1527de 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7864,6 +7864,10 @@ "resolved": "packages/compass-connections-navigation", "link": true }, + "node_modules/@mongodb-js/compass-context-menu": { + "resolved": "packages/compass-context-menu", + "link": true + }, "node_modules/@mongodb-js/compass-crud": { "resolved": "packages/compass-crud", "link": true @@ -44105,6 +44109,62 @@ "node": ">=0.3.1" } }, + "packages/compass-context-menu": { + "name": "@mongodb-js/compass-context-menu", + "version": "0.0.1", + "license": "SSPL", + "dependencies": { + "react": "^17.0.2" + }, + "devDependencies": { + "@mongodb-js/eslint-config-compass": "^1.3.8", + "@mongodb-js/mocha-config-compass": "^1.6.8", + "@mongodb-js/prettier-config-compass": "^1.2.8", + "@mongodb-js/tsconfig-compass": "^1.2.8", + "@types/chai": "^4.2.21", + "@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", + "gen-esm-wrapper": "^1.1.0", + "mocha": "^10.2.0", + "nyc": "^15.1.0", + "sinon": "^9.2.3", + "typescript": "^5.0.4" + } + }, + "packages/compass-context-menu/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "packages/compass-context-menu/node_modules/sinon": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.4.tgz", + "integrity": "sha512-zljcULZQsJxVra28qIAL6ow1Z9tpattkCTEJR4RBP3TGc00FcttsP5pK284Nas5WjMZU5Yzy3kAIp3B3KRf5Yg==", + "deprecated": "16.1.1", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^1.8.1", + "@sinonjs/fake-timers": "^6.0.1", + "@sinonjs/samsam": "^5.3.1", + "diff": "^4.0.2", + "nise": "^4.0.4", + "supports-color": "^7.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, "packages/compass-crud": { "name": "@mongodb-js/compass-crud", "version": "13.56.1", @@ -56549,6 +56609,50 @@ } } }, + "@mongodb-js/compass-context-menu": { + "version": "file:packages/compass-context-menu", + "requires": { + "@mongodb-js/eslint-config-compass": "^1.3.8", + "@mongodb-js/mocha-config-compass": "^1.6.8", + "@mongodb-js/prettier-config-compass": "^1.2.8", + "@mongodb-js/tsconfig-compass": "^1.2.8", + "@types/chai": "^4.2.21", + "@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", + "gen-esm-wrapper": "^1.1.0", + "mocha": "^10.2.0", + "nyc": "^15.1.0", + "react": "^17.0.2", + "sinon": "^9.2.3", + "typescript": "^5.0.4" + }, + "dependencies": { + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "sinon": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.4.tgz", + "integrity": "sha512-zljcULZQsJxVra28qIAL6ow1Z9tpattkCTEJR4RBP3TGc00FcttsP5pK284Nas5WjMZU5Yzy3kAIp3B3KRf5Yg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.8.1", + "@sinonjs/fake-timers": "^6.0.1", + "@sinonjs/samsam": "^5.3.1", + "diff": "^4.0.2", + "nise": "^4.0.4", + "supports-color": "^7.1.0" + } + } + } + }, "@mongodb-js/compass-crud": { "version": "file:packages/compass-crud", "requires": { diff --git a/packages/compass-context-menu/.depcheckrc b/packages/compass-context-menu/.depcheckrc new file mode 100644 index 00000000000..ab0ef21b740 --- /dev/null +++ b/packages/compass-context-menu/.depcheckrc @@ -0,0 +1,8 @@ +ignores: + - '@mongodb-js/prettier-config-compass' + - '@mongodb-js/tsconfig-compass' + - '@types/chai' + - '@types/sinon-chai' + - 'sinon' +ignore-patterns: + - 'dist' diff --git a/packages/compass-context-menu/.eslintignore b/packages/compass-context-menu/.eslintignore new file mode 100644 index 00000000000..85a8a75e68c --- /dev/null +++ b/packages/compass-context-menu/.eslintignore @@ -0,0 +1,2 @@ +.nyc-output +dist diff --git a/packages/compass-context-menu/.eslintrc.js b/packages/compass-context-menu/.eslintrc.js new file mode 100644 index 00000000000..9c3ab95632f --- /dev/null +++ b/packages/compass-context-menu/.eslintrc.js @@ -0,0 +1,9 @@ +'use strict'; +module.exports = { + root: true, + extends: ['@mongodb-js/eslint-config-compass'], + parserOptions: { + tsconfigRootDir: __dirname, + project: ['./tsconfig-lint.json'], + }, +}; diff --git a/packages/compass-context-menu/.mocharc.js b/packages/compass-context-menu/.mocharc.js new file mode 100644 index 00000000000..e7eaccd61fa --- /dev/null +++ b/packages/compass-context-menu/.mocharc.js @@ -0,0 +1,2 @@ +'use strict'; +module.exports = require('@mongodb-js/mocha-config-compass'); diff --git a/packages/compass-context-menu/package.json b/packages/compass-context-menu/package.json new file mode 100644 index 00000000000..3cf186e0270 --- /dev/null +++ b/packages/compass-context-menu/package.json @@ -0,0 +1,72 @@ +{ + "name": "@mongodb-js/compass-context-menu", + "author": { + "name": "MongoDB Inc", + "email": "compass@mongodb.com" + }, + "publishConfig": { + "access": "public" + }, + "bugs": { + "url": "https://jira.mongodb.org/projects/COMPASS/issues", + "email": "compass@mongodb.com" + }, + "homepage": "https://github.com/mongodb-js/compass", + "version": "0.0.1", + "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", + "prepublishOnly": "npm run compile && compass-scripts check-exports-exist", + "compile": "tsc -p tsconfig.json && gen-esm-wrapper . ./dist/.esm-wrapper.mjs", + "typecheck": "tsc -p tsconfig-lint.json --noEmit", + "eslint": "eslint-compass", + "prettier": "prettier-compass", + "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-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", + "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." + }, + "dependencies": { + "react": "^17.0.2" + }, + "devDependencies": { + "@mongodb-js/eslint-config-compass": "^1.3.8", + "@mongodb-js/mocha-config-compass": "^1.6.8", + "@mongodb-js/prettier-config-compass": "^1.2.8", + "@mongodb-js/tsconfig-compass": "^1.2.8", + "@types/chai": "^4.2.21", + "@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", + "gen-esm-wrapper": "^1.1.0", + "mocha": "^10.2.0", + "nyc": "^15.1.0", + "sinon": "^9.2.3", + "typescript": "^5.0.4" + } +} diff --git a/packages/compass-context-menu/src/context-menu-content.ts b/packages/compass-context-menu/src/context-menu-content.ts new file mode 100644 index 00000000000..6856f1fe684 --- /dev/null +++ b/packages/compass-context-menu/src/context-menu-content.ts @@ -0,0 +1,23 @@ +const CONTEXT_MENUS_SYMBOL = Symbol('context_menus'); + +export type EnhancedMouseEvent = MouseEvent & { + [CONTEXT_MENUS_SYMBOL]?: React.ComponentType[]; +}; + +export function getContextMenuContent( + event: EnhancedMouseEvent +): React.ComponentType[] { + return event[CONTEXT_MENUS_SYMBOL] ?? []; +} + +export function appendContextMenuContent( + event: EnhancedMouseEvent, + content: React.ComponentType +) { + // Initialize if not already patched + if (event[CONTEXT_MENUS_SYMBOL] === undefined) { + event[CONTEXT_MENUS_SYMBOL] = [content]; + return; + } + event[CONTEXT_MENUS_SYMBOL].push(content); +} diff --git a/packages/compass-context-menu/src/context-menu-provider.tsx b/packages/compass-context-menu/src/context-menu-provider.tsx new file mode 100644 index 00000000000..7a7b59bf16a --- /dev/null +++ b/packages/compass-context-menu/src/context-menu-provider.tsx @@ -0,0 +1,68 @@ +import React, { + useCallback, + useEffect, + useState, + useMemo, + createContext, +} from 'react'; +import type { ContextMenuContext, MenuState } from './types'; +import { ContextMenu } from './context-menu'; +import type { EnhancedMouseEvent } from './context-menu-content'; +import { getContextMenuContent } from './context-menu-content'; + +export const Context = createContext(null); + +export function ContextMenuProvider({ + children, +}: React.PropsWithChildren) { + const [menu, setMenu] = useState({ isOpen: false }); + const close = useCallback(() => setMenu({ isOpen: false }), [setMenu]); + + useEffect(() => { + function handleContextMenu(event: MouseEvent) { + event.preventDefault(); + setMenu({ + isOpen: true, + children: getContextMenuContent(event as EnhancedMouseEvent).map( + (Content, index) => + ), + position: { + // TODO: Fix handling offset while scrolling + x: event.clientX, + y: event.clientY, + }, + }); + } + document.addEventListener('contextmenu', handleContextMenu); + + function handleClosingEvent(event: Event) { + if (!event.defaultPrevented) { + setMenu({ isOpen: false }); + } + } + document.addEventListener('click', handleClosingEvent); + window.addEventListener('resize', handleClosingEvent); + + return () => { + document.removeEventListener('contextmenu', handleContextMenu); + document.removeEventListener('click', handleClosingEvent); + window.removeEventListener('resize', handleClosingEvent); + }; + }, [setMenu]); + + const value = useMemo( + () => ({ + close, + }), + [close] + ); + + return ( + <> + {children} + {menu.isOpen && ( + {menu.children} + )} + + ); +} diff --git a/packages/compass-context-menu/src/context-menu.tsx b/packages/compass-context-menu/src/context-menu.tsx new file mode 100644 index 00000000000..b053bc4963a --- /dev/null +++ b/packages/compass-context-menu/src/context-menu.tsx @@ -0,0 +1,22 @@ +import { createPortal } from 'react-dom'; +import React from 'react'; + +type ContextMenuProps = React.PropsWithChildren<{ + position: { + x: number; + y: number; + }; +}>; + +export function ContextMenu({ children, position }: ContextMenuProps) { + const container = document.getElementById('context-menu-container'); + if (container === null) { + throw new Error('Expected a container for the context menu in the DOM'); + } + return createPortal( +
+ {children} +
, + container + ); +} diff --git a/packages/compass-context-menu/src/types.ts b/packages/compass-context-menu/src/types.ts new file mode 100644 index 00000000000..07efb491ac6 --- /dev/null +++ b/packages/compass-context-menu/src/types.ts @@ -0,0 +1,21 @@ +export type MenuState = + | { + isOpen: false; + } + | { + isOpen: true; + children: React.ReactNode; + position: { + x: number; + y: number; + }; + }; + +export type ContextMenuContext = { + close(): void; +}; + +export type MenuItem = { + label: string; + onAction: (event: Event) => void; +}; diff --git a/packages/compass-context-menu/src/use-context-menu.tsx b/packages/compass-context-menu/src/use-context-menu.tsx new file mode 100644 index 00000000000..d7ccc114a90 --- /dev/null +++ b/packages/compass-context-menu/src/use-context-menu.tsx @@ -0,0 +1,55 @@ +import React, { useContext, useMemo, useRef } from 'react'; +import { Context } from './context-menu-provider'; +import { appendContextMenuContent } from './context-menu-content'; +import type { MenuItem } from './types'; + +/** + * @returns an object with methods to {@link register} content for the menu and {@link close} the menu + */ +export function useContextMenu({ + Menu, +}: { + Menu: React.ComponentType<{ + items: MenuItem[]; + }>; +}) { + // Get the close function from the ContextProvider + const context = useContext(Context); + const previous = useRef void]>( + null + ); + + return useMemo(() => { + if (!context) { + throw new Error('useContextMenu called outside of the provider'); + } + + return { + close: context.close.bind(context), + /** + * @returns a callback ref, passed onto the element responsible for triggering the menu. + */ + register(content: React.ComponentType) { + function listener(event: MouseEvent) { + appendContextMenuContent(event, content); + } + return (trigger: HTMLElement | null) => { + if (previous.current) { + const [previousTrigger, previousListener] = previous.current; + previousTrigger.removeEventListener( + 'contextmenu', + previousListener + ); + } + if (trigger) { + trigger.addEventListener('contextmenu', listener); + previous.current = [trigger, listener]; + } + }; + }, + registerItems(items: MenuItem[]) { + return this.register(() => ); + }, + }; + }, [context, Menu]); +} diff --git a/packages/compass-context-menu/tsconfig-lint.json b/packages/compass-context-menu/tsconfig-lint.json new file mode 100644 index 00000000000..6bdef84f322 --- /dev/null +++ b/packages/compass-context-menu/tsconfig-lint.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.json", + "include": ["**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/compass-context-menu/tsconfig.json b/packages/compass-context-menu/tsconfig.json new file mode 100644 index 00000000000..79bc84584ce --- /dev/null +++ b/packages/compass-context-menu/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@mongodb-js/tsconfig-compass/tsconfig.react.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": ["src/**/*"], + "exclude": ["./src/**/*.spec.*"] +} From 1efdb2ecc1ed8a7d8211bc61ef391238273405a7 Mon Sep 17 00:00:00 2001 From: gagik Date: Wed, 21 May 2025 13:41:30 +0200 Subject: [PATCH 16/76] wip --- .../src/context-menu-provider.tsx | 4 +- .../src/use-context-menu.spec.tsx | 144 ++++++++++++++++++ 2 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 packages/compass-context-menu/src/use-context-menu.spec.tsx diff --git a/packages/compass-context-menu/src/context-menu-provider.tsx b/packages/compass-context-menu/src/context-menu-provider.tsx index 7a7b59bf16a..499d9c273c1 100644 --- a/packages/compass-context-menu/src/context-menu-provider.tsx +++ b/packages/compass-context-menu/src/context-menu-provider.tsx @@ -14,7 +14,9 @@ export const Context = createContext(null); export function ContextMenuProvider({ children, -}: React.PropsWithChildren) { +}: { + children: React.ReactNode; +}) { const [menu, setMenu] = useState({ isOpen: false }); const close = useCallback(() => setMenu({ isOpen: false }), [setMenu]); diff --git a/packages/compass-context-menu/src/use-context-menu.spec.tsx b/packages/compass-context-menu/src/use-context-menu.spec.tsx new file mode 100644 index 00000000000..7337463e819 --- /dev/null +++ b/packages/compass-context-menu/src/use-context-menu.spec.tsx @@ -0,0 +1,144 @@ +import React from 'react'; +import { + render, + screen, + cleanup, + userEvent, +} from '@mongodb-js/testing-library-compass'; +import { expect } from 'chai'; +import sinon from 'sinon'; +import { useContextMenu } from './use-context-menu'; +import { ContextMenuProvider } from './context-menu-provider'; +import type { MenuItem } from './types'; + +describe('useContextMenu', function () { + const TestMenu: React.FC<{ items: MenuItem[] }> = ({ items }) => ( +
+ {items.map((item, idx) => ( +
+ {item.label} +
+ ))} +
+ ); + + const TestComponent = ({ + onRegister, + }: { + onRegister?: (ref: any) => void; + }) => { + const contextMenu = useContextMenu({ Menu: TestMenu }); + const items: MenuItem[] = [ + { + label: 'Test Item', + onAction: () => { + /* noop */ + }, + }, + ]; + const ref = contextMenu.registerItems(items); + + React.useEffect(() => { + onRegister?.(ref); + }, [ref, onRegister]); + + return ( +
+ Test Component +
+ ); + }; + + afterEach(cleanup); + + describe('when used outside provider', function () { + it('throws an error', function () { + expect(() => { + render(); + }).to.throw('useContextMenu called outside of the provider'); + }); + }); + + describe('when used inside provider', function () { + beforeEach(() => { + // Create the container for the context menu portal + const container = document.createElement('div'); + container.id = 'context-menu-container'; + document.body.appendChild(container); + }); + + afterEach(() => { + // Clean up the container + const container = document.getElementById('context-menu-container'); + if (container) { + document.body.removeChild(container); + } + }); + + it('renders without error', function () { + render( + + + + ); + + expect(screen.getByTestId('test-trigger')).to.exist; + }); + + it('registers context menu event listener', function () { + const onRegister = sinon.spy(); + + render( + + + + ); + + expect(onRegister).to.have.been.calledOnce; + expect(onRegister.firstCall.args[0]).to.be.a('function'); + }); + + it('shows context menu on right click', function () { + render( + + + + ); + + const trigger = screen.getByTestId('test-trigger'); + userEvent.click(trigger, { button: 2 }); + + // The menu should be rendered in the portal + expect(screen.getByTestId('menu-item-Test Item')).to.exist; + }); + + it('cleans up previous event listener when ref changes', function () { + const removeEventListenerSpy = sinon.spy(); + const addEventListenerSpy = sinon.spy(); + + const { rerender } = render( + + + + ); + + // Simulate ref change + const ref = screen.getByTestId('test-trigger'); + Object.defineProperty(ref, 'addEventListener', { + value: addEventListenerSpy, + }); + Object.defineProperty(ref, 'removeEventListener', { + value: removeEventListenerSpy, + }); + + rerender( + + + + ); + + expect(removeEventListenerSpy).to.have.been.calledWith('contextmenu'); + expect(addEventListenerSpy).to.have.been.calledWith('contextmenu'); + }); + }); +}); From 0f5303bb41171aa96d959cba498984d0678a0026 Mon Sep 17 00:00:00 2001 From: gagik Date: Wed, 21 May 2025 15:16:18 +0200 Subject: [PATCH 17/76] fix: add tests --- packages/compass-context-menu/.mocharc.js | 2 +- .../src/use-context-menu.spec.tsx | 58 +++++++------------ .../src/use-context-menu.tsx | 6 +- 3 files changed, 26 insertions(+), 40 deletions(-) diff --git a/packages/compass-context-menu/.mocharc.js b/packages/compass-context-menu/.mocharc.js index e7eaccd61fa..5a33f216327 100644 --- a/packages/compass-context-menu/.mocharc.js +++ b/packages/compass-context-menu/.mocharc.js @@ -1,2 +1,2 @@ 'use strict'; -module.exports = require('@mongodb-js/mocha-config-compass'); +module.exports = require('@mongodb-js/mocha-config-compass/react'); diff --git a/packages/compass-context-menu/src/use-context-menu.spec.tsx b/packages/compass-context-menu/src/use-context-menu.spec.tsx index 7337463e819..8a5cb4b6fee 100644 --- a/packages/compass-context-menu/src/use-context-menu.spec.tsx +++ b/packages/compass-context-menu/src/use-context-menu.spec.tsx @@ -1,36 +1,37 @@ import React from 'react'; -import { - render, - screen, - cleanup, - userEvent, -} from '@mongodb-js/testing-library-compass'; +import { render, screen, userEvent } from '@mongodb-js/testing-library-compass'; import { expect } from 'chai'; import sinon from 'sinon'; import { useContextMenu } from './use-context-menu'; import { ContextMenuProvider } from './context-menu-provider'; import type { MenuItem } from './types'; +type TestMenuItem = MenuItem & { id: number }; + describe('useContextMenu', function () { - const TestMenu: React.FC<{ items: MenuItem[] }> = ({ items }) => ( + const TestMenu: React.FC<{ items: TestMenuItem[] }> = ({ items }) => (
{items.map((item, idx) => ( -
+
{item.label}
))}
); - const TestComponent = ({ - onRegister, - }: { - onRegister?: (ref: any) => void; - }) => { + const TestComponent = () => { const contextMenu = useContextMenu({ Menu: TestMenu }); - const items: MenuItem[] = [ + const items: TestMenuItem[] = [ { - label: 'Test Item', + id: 1, + label: 'Test A', + onAction: () => { + /* noop */ + }, + }, + { + id: 2, + label: 'Test B', onAction: () => { /* noop */ }, @@ -38,10 +39,6 @@ describe('useContextMenu', function () { ]; const ref = contextMenu.registerItems(items); - React.useEffect(() => { - onRegister?.(ref); - }, [ref, onRegister]); - return (
Test Component @@ -49,8 +46,6 @@ describe('useContextMenu', function () { ); }; - afterEach(cleanup); - describe('when used outside provider', function () { it('throws an error', function () { expect(() => { @@ -59,7 +54,7 @@ describe('useContextMenu', function () { }); }); - describe('when used inside provider', function () { + describe('with valid provider', function () { beforeEach(() => { // Create the container for the context menu portal const container = document.createElement('div'); @@ -85,19 +80,6 @@ describe('useContextMenu', function () { expect(screen.getByTestId('test-trigger')).to.exist; }); - it('registers context menu event listener', function () { - const onRegister = sinon.spy(); - - render( - - - - ); - - expect(onRegister).to.have.been.calledOnce; - expect(onRegister.firstCall.args[0]).to.be.a('function'); - }); - it('shows context menu on right click', function () { render( @@ -105,11 +87,15 @@ describe('useContextMenu', function () { ); + expect(screen.queryByTestId('menu-item-1')).not.to.exist; + expect(screen.queryByTestId('menu-item-2')).not.to.exist; + const trigger = screen.getByTestId('test-trigger'); userEvent.click(trigger, { button: 2 }); // The menu should be rendered in the portal - expect(screen.getByTestId('menu-item-Test Item')).to.exist; + expect(screen.getByTestId('menu-item-1')).to.exist; + expect(screen.getByTestId('menu-item-2')).to.exist; }); it('cleans up previous event listener when ref changes', function () { diff --git a/packages/compass-context-menu/src/use-context-menu.tsx b/packages/compass-context-menu/src/use-context-menu.tsx index d7ccc114a90..37993b04f15 100644 --- a/packages/compass-context-menu/src/use-context-menu.tsx +++ b/packages/compass-context-menu/src/use-context-menu.tsx @@ -6,11 +6,11 @@ import type { MenuItem } from './types'; /** * @returns an object with methods to {@link register} content for the menu and {@link close} the menu */ -export function useContextMenu({ +export function useContextMenu({ Menu, }: { Menu: React.ComponentType<{ - items: MenuItem[]; + items: T[]; }>; }) { // Get the close function from the ContextProvider @@ -47,7 +47,7 @@ export function useContextMenu({ } }; }, - registerItems(items: MenuItem[]) { + registerItems(items: T[]) { return this.register(() => ); }, }; From 33b2a41af24cf04760ac4e24f75873c8402e7f36 Mon Sep 17 00:00:00 2001 From: gagik Date: Wed, 21 May 2025 16:50:00 +0200 Subject: [PATCH 18/76] fix: add tests and fix types --- packages/compass-context-menu/src/types.ts | 2 +- .../src/use-context-menu.spec.tsx | 219 ++++++++++++++---- 2 files changed, 175 insertions(+), 46 deletions(-) diff --git a/packages/compass-context-menu/src/types.ts b/packages/compass-context-menu/src/types.ts index 07efb491ac6..e9ac549ba63 100644 --- a/packages/compass-context-menu/src/types.ts +++ b/packages/compass-context-menu/src/types.ts @@ -17,5 +17,5 @@ export type ContextMenuContext = { export type MenuItem = { label: string; - onAction: (event: Event) => void; + onAction: (event: React.KeyboardEvent | React.MouseEvent) => void; }; diff --git a/packages/compass-context-menu/src/use-context-menu.spec.tsx b/packages/compass-context-menu/src/use-context-menu.spec.tsx index 8a5cb4b6fee..1d099272104 100644 --- a/packages/compass-context-menu/src/use-context-menu.spec.tsx +++ b/packages/compass-context-menu/src/use-context-menu.spec.tsx @@ -6,39 +6,48 @@ import { useContextMenu } from './use-context-menu'; import { ContextMenuProvider } from './context-menu-provider'; import type { MenuItem } from './types'; -type TestMenuItem = MenuItem & { id: number }; - describe('useContextMenu', function () { - const TestMenu: React.FC<{ items: TestMenuItem[] }> = ({ items }) => ( + const TestMenu: React.FC<{ items: MenuItem[] }> = ({ items }) => (
{items.map((item, idx) => ( -
+
item.onAction?.(event)} + onKeyDown={(event) => { + if (event.key === 'Enter') { + item.onAction?.(event); + } + }} + > {item.label}
))}
); - const TestComponent = () => { + const TestComponent = ({ + onRegister, + onAction, + }: { + onRegister?: (ref: any) => void; + onAction?: (id) => void; + }) => { const contextMenu = useContextMenu({ Menu: TestMenu }); - const items: TestMenuItem[] = [ - { - id: 1, - label: 'Test A', - onAction: () => { - /* noop */ - }, - }, + const items: MenuItem[] = [ { - id: 2, - label: 'Test B', - onAction: () => { - /* noop */ - }, + label: 'Test Item', + onAction: () => onAction?.(1), }, ]; const ref = contextMenu.registerItems(items); + React.useEffect(() => { + onRegister?.(ref); + }, [ref, onRegister]); + return (
Test Component @@ -46,6 +55,60 @@ describe('useContextMenu', function () { ); }; + // Add new test components for nested context menu scenario + const ParentComponent = ({ + onAction, + children, + }: { + onAction?: (id: number) => void; + children?: React.ReactNode; + }) => { + const contextMenu = useContextMenu({ Menu: TestMenu }); + const parentItems: MenuItem[] = [ + { + label: 'Parent Item 1', + onAction: () => onAction?.(1), + }, + { + label: 'Parent Item 2', + onAction: () => onAction?.(2), + }, + ]; + const ref = contextMenu.registerItems(parentItems); + + return ( +
+
Parent Component
+ {children} +
+ ); + }; + + const ChildComponent = ({ + onAction, + }: { + onAction?: (id: number) => void; + }) => { + const contextMenu = useContextMenu({ Menu: TestMenu }); + const childItems: MenuItem[] = [ + { + label: 'Child Item 1', + onAction: () => onAction?.(1), + }, + { + label: 'Child Item 2', + onAction: () => onAction?.(2), + }, + ]; + const ref = contextMenu.registerItems(childItems); + + return ( +
+ Child Component +
+ ); + }; + describe('when used outside provider', function () { it('throws an error', function () { expect(() => { @@ -54,7 +117,7 @@ describe('useContextMenu', function () { }); }); - describe('with valid provider', function () { + describe('with a valid provider', function () { beforeEach(() => { // Create the container for the context menu portal const container = document.createElement('div'); @@ -80,6 +143,19 @@ describe('useContextMenu', function () { expect(screen.getByTestId('test-trigger')).to.exist; }); + it('registers context menu event listener', function () { + const onRegister = sinon.spy(); + + render( + + + + ); + + expect(onRegister).to.have.been.calledOnce; + expect(onRegister.firstCall.args[0]).to.be.a('function'); + }); + it('shows context menu on right click', function () { render( @@ -87,44 +163,97 @@ describe('useContextMenu', function () { ); - expect(screen.queryByTestId('menu-item-1')).not.to.exist; - expect(screen.queryByTestId('menu-item-2')).not.to.exist; - const trigger = screen.getByTestId('test-trigger'); userEvent.click(trigger, { button: 2 }); // The menu should be rendered in the portal - expect(screen.getByTestId('menu-item-1')).to.exist; - expect(screen.getByTestId('menu-item-2')).to.exist; + expect(screen.getByTestId('menu-item-Test Item')).to.exist; }); - it('cleans up previous event listener when ref changes', function () { - const removeEventListenerSpy = sinon.spy(); - const addEventListenerSpy = sinon.spy(); + describe('with nested context menus', function () { + it('shows only parent items when right clicking parent area', function () { + render( + + + + ); - const { rerender } = render( - - - - ); + const parentTrigger = screen.getByTestId('parent-trigger'); + userEvent.click(parentTrigger, { button: 2 }); + + // Should show parent items + expect(screen.getByTestId('menu-item-Parent Item 1')).to.exist; + expect(screen.getByTestId('menu-item-Parent Item 2')).to.exist; - // Simulate ref change - const ref = screen.getByTestId('test-trigger'); - Object.defineProperty(ref, 'addEventListener', { - value: addEventListenerSpy, + // Should not show child items + expect(() => screen.getByTestId('menu-item-Child Item 1')).to.throw; + expect(() => screen.getByTestId('menu-item-Child Item 2')).to.throw; }); - Object.defineProperty(ref, 'removeEventListener', { - value: removeEventListenerSpy, + + it('shows both parent and child items when right clicking child area', function () { + render( + + + + + + ); + + const childTrigger = screen.getByTestId('child-trigger'); + userEvent.click(childTrigger, { button: 2 }); + + // Should show both parent and child items + expect(screen.getByTestId('menu-item-Parent Item 1')).to.exist; + expect(screen.getByTestId('menu-item-Parent Item 2')).to.exist; + expect(screen.getByTestId('menu-item-Child Item 1')).to.exist; + expect(screen.getByTestId('menu-item-Child Item 2')).to.exist; }); - rerender( - - - - ); + it('triggers only the child action when clicking child menu item', function () { + const parentOnAction = sinon.spy(); + const childOnAction = sinon.spy(); + + render( + + + + + + ); + + const childTrigger = screen.getByTestId('child-trigger'); + userEvent.click(childTrigger, { button: 2 }); + + const childItem1 = screen.getByTestId('menu-item-Child Item 1'); + userEvent.click(childItem1); - expect(removeEventListenerSpy).to.have.been.calledWith('contextmenu'); - expect(addEventListenerSpy).to.have.been.calledWith('contextmenu'); + expect(childOnAction).to.have.been.calledOnceWithExactly(1); + expect(parentOnAction).to.not.have.been.called; + expect(() => screen.getByTestId('test-menu')).to.throw; + }); + + it('triggers only the parent action when clicking a parent menu item from child context', function () { + const parentOnAction = sinon.spy(); + const childOnAction = sinon.spy(); + + render( + + + + + + ); + + const childTrigger = screen.getByTestId('child-trigger'); + userEvent.click(childTrigger, { button: 2 }); + + const parentItem1 = screen.getByTestId('menu-item-Parent Item 1'); + userEvent.click(parentItem1); + + expect(parentOnAction).to.have.been.calledOnceWithExactly(1); + expect(childOnAction).to.not.have.been.called; + expect(() => screen.getByTestId('test-menu')).to.throw; + }); }); }); }); From 4a2f0327b37be0b3e506db54288f3e0a16daf168 Mon Sep 17 00:00:00 2001 From: gagik Date: Wed, 21 May 2025 17:07:53 +0200 Subject: [PATCH 19/76] refactor: minor stylistic changes --- .../src/context-menu-provider.tsx | 3 +- .../src/use-context-menu.tsx | 53 +++++++++++-------- 2 files changed, 32 insertions(+), 24 deletions(-) diff --git a/packages/compass-context-menu/src/context-menu-provider.tsx b/packages/compass-context-menu/src/context-menu-provider.tsx index 499d9c273c1..43d9e893367 100644 --- a/packages/compass-context-menu/src/context-menu-provider.tsx +++ b/packages/compass-context-menu/src/context-menu-provider.tsx @@ -35,13 +35,14 @@ export function ContextMenuProvider({ }, }); } - document.addEventListener('contextmenu', handleContextMenu); function handleClosingEvent(event: Event) { if (!event.defaultPrevented) { setMenu({ isOpen: false }); } } + + document.addEventListener('contextmenu', handleContextMenu); document.addEventListener('click', handleClosingEvent); window.addEventListener('resize', handleClosingEvent); diff --git a/packages/compass-context-menu/src/use-context-menu.tsx b/packages/compass-context-menu/src/use-context-menu.tsx index 37993b04f15..91f51c2f849 100644 --- a/packages/compass-context-menu/src/use-context-menu.tsx +++ b/packages/compass-context-menu/src/use-context-menu.tsx @@ -3,16 +3,25 @@ import { Context } from './context-menu-provider'; import { appendContextMenuContent } from './context-menu-content'; import type { MenuItem } from './types'; -/** - * @returns an object with methods to {@link register} content for the menu and {@link close} the menu - */ +export type ContextMenuMethods = { + /** + * Close the context menu. + */ + close: () => void; + /** + * Register the menu items for the context menu. + * @returns a callback ref to be passed onto the element responsible for triggering the menu. + */ + registerItems: (items: T[]) => (trigger: HTMLElement | null) => void; +}; + export function useContextMenu({ Menu, }: { Menu: React.ComponentType<{ items: T[]; }>; -}) { +}): ContextMenuMethods { // Get the close function from the ContextProvider const context = useContext(Context); const previous = useRef void]>( @@ -24,31 +33,29 @@ export function useContextMenu({ throw new Error('useContextMenu called outside of the provider'); } + const register = (content: React.ComponentType) => { + function listener(event: MouseEvent) { + appendContextMenuContent(event, content); + } + return (trigger: HTMLElement | null) => { + if (previous.current) { + const [previousTrigger, previousListener] = previous.current; + previousTrigger.removeEventListener('contextmenu', previousListener); + } + if (trigger) { + trigger.addEventListener('contextmenu', listener); + previous.current = [trigger, listener]; + } + }; + }; + return { close: context.close.bind(context), /** * @returns a callback ref, passed onto the element responsible for triggering the menu. */ - register(content: React.ComponentType) { - function listener(event: MouseEvent) { - appendContextMenuContent(event, content); - } - return (trigger: HTMLElement | null) => { - if (previous.current) { - const [previousTrigger, previousListener] = previous.current; - previousTrigger.removeEventListener( - 'contextmenu', - previousListener - ); - } - if (trigger) { - trigger.addEventListener('contextmenu', listener); - previous.current = [trigger, listener]; - } - }; - }, registerItems(items: T[]) { - return this.register(() => ); + return register(() => ); }, }; }, [context, Menu]); From 8e1feb3f6ba7591fa1b6bae14e3465fa04cb300b Mon Sep 17 00:00:00 2001 From: gagik Date: Thu, 22 May 2025 11:59:55 +0200 Subject: [PATCH 20/76] fix: export types and rename MenuItem --- packages/compass-context-menu/src/index.ts | 2 ++ packages/compass-context-menu/src/types.ts | 2 +- .../compass-context-menu/src/use-context-menu.spec.tsx | 10 +++++----- packages/compass-context-menu/src/use-context-menu.tsx | 6 +++--- 4 files changed, 11 insertions(+), 9 deletions(-) create mode 100644 packages/compass-context-menu/src/index.ts diff --git a/packages/compass-context-menu/src/index.ts b/packages/compass-context-menu/src/index.ts new file mode 100644 index 00000000000..d60a97e04b3 --- /dev/null +++ b/packages/compass-context-menu/src/index.ts @@ -0,0 +1,2 @@ +export { useContextMenu } from './use-context-menu'; +export type { ContextMenuItem } from './types'; diff --git a/packages/compass-context-menu/src/types.ts b/packages/compass-context-menu/src/types.ts index e9ac549ba63..f453930dcb3 100644 --- a/packages/compass-context-menu/src/types.ts +++ b/packages/compass-context-menu/src/types.ts @@ -15,7 +15,7 @@ export type ContextMenuContext = { close(): void; }; -export type MenuItem = { +export type ContextMenuItem = { label: string; onAction: (event: React.KeyboardEvent | React.MouseEvent) => void; }; diff --git a/packages/compass-context-menu/src/use-context-menu.spec.tsx b/packages/compass-context-menu/src/use-context-menu.spec.tsx index 1d099272104..eb857db9aeb 100644 --- a/packages/compass-context-menu/src/use-context-menu.spec.tsx +++ b/packages/compass-context-menu/src/use-context-menu.spec.tsx @@ -4,10 +4,10 @@ import { expect } from 'chai'; import sinon from 'sinon'; import { useContextMenu } from './use-context-menu'; import { ContextMenuProvider } from './context-menu-provider'; -import type { MenuItem } from './types'; +import type { ContextMenuItem } from './types'; describe('useContextMenu', function () { - const TestMenu: React.FC<{ items: MenuItem[] }> = ({ items }) => ( + const TestMenu: React.FC<{ items: ContextMenuItem[] }> = ({ items }) => (
{items.map((item, idx) => (
void; }) => { const contextMenu = useContextMenu({ Menu: TestMenu }); - const items: MenuItem[] = [ + const items: ContextMenuItem[] = [ { label: 'Test Item', onAction: () => onAction?.(1), @@ -64,7 +64,7 @@ describe('useContextMenu', function () { children?: React.ReactNode; }) => { const contextMenu = useContextMenu({ Menu: TestMenu }); - const parentItems: MenuItem[] = [ + const parentItems: ContextMenuItem[] = [ { label: 'Parent Item 1', onAction: () => onAction?.(1), @@ -90,7 +90,7 @@ describe('useContextMenu', function () { onAction?: (id: number) => void; }) => { const contextMenu = useContextMenu({ Menu: TestMenu }); - const childItems: MenuItem[] = [ + const childItems: ContextMenuItem[] = [ { label: 'Child Item 1', onAction: () => onAction?.(1), diff --git a/packages/compass-context-menu/src/use-context-menu.tsx b/packages/compass-context-menu/src/use-context-menu.tsx index 91f51c2f849..a0c97dead9b 100644 --- a/packages/compass-context-menu/src/use-context-menu.tsx +++ b/packages/compass-context-menu/src/use-context-menu.tsx @@ -1,9 +1,9 @@ import React, { useContext, useMemo, useRef } from 'react'; import { Context } from './context-menu-provider'; import { appendContextMenuContent } from './context-menu-content'; -import type { MenuItem } from './types'; +import type { ContextMenuItem } from './types'; -export type ContextMenuMethods = { +export type ContextMenuMethods = { /** * Close the context menu. */ @@ -15,7 +15,7 @@ export type ContextMenuMethods = { registerItems: (items: T[]) => (trigger: HTMLElement | null) => void; }; -export function useContextMenu({ +export function useContextMenu({ Menu, }: { Menu: React.ComponentType<{ From 1f55000a7789f08ab8e1b40eb34611f4737aee95 Mon Sep 17 00:00:00 2001 From: gagik Date: Thu, 22 May 2025 12:14:37 +0200 Subject: [PATCH 21/76] fix: use React.RefCallback --- packages/compass-context-menu/src/use-context-menu.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/compass-context-menu/src/use-context-menu.tsx b/packages/compass-context-menu/src/use-context-menu.tsx index a0c97dead9b..fa9e20e7537 100644 --- a/packages/compass-context-menu/src/use-context-menu.tsx +++ b/packages/compass-context-menu/src/use-context-menu.tsx @@ -12,7 +12,7 @@ export type ContextMenuMethods = { * Register the menu items for the context menu. * @returns a callback ref to be passed onto the element responsible for triggering the menu. */ - registerItems: (items: T[]) => (trigger: HTMLElement | null) => void; + registerItems: (items: T[]) => React.RefCallback; }; export function useContextMenu({ @@ -33,7 +33,9 @@ export function useContextMenu({ throw new Error('useContextMenu called outside of the provider'); } - const register = (content: React.ComponentType) => { + const register = ( + content: React.ComponentType + ): React.RefCallback => { function listener(event: MouseEvent) { appendContextMenuContent(event, content); } From 9618e8e187e761e80fdc421a9516e69db87badd7 Mon Sep 17 00:00:00 2001 From: gagik Date: Fri, 23 May 2025 13:50:50 +0200 Subject: [PATCH 22/76] refactor: use item groups instead of React elements, use wrapper, keep menu state consistent The refactor is meant to make the Leafygreen integration more straightforward: 1. We stick to item groups and instead have a single wrapper to handle any rendering differences between groups. This allows the wrapper to always have context of all items when rendering which is useful when inserting menu seperators in Leafygreen. Also encourages consistent UI (while allowing per-case customization if needed at wrapper-level). We could introduce itemWrappers instead of itemGroups but having one wrapper handling all seems cleaner to me. 2. More of the responsibility is moved to a parent wrapper component that will house the context menu. This allows us to standardize the right click menu and make better use of Leafygreen's menu component including its click handling (which has been removed from the context menu library). 3. Menu state (i.e. position) is now preserved even closed; this is useful for leafygreen's menu to animate in the same position instead of losing the position all together. --- .../src/context-menu-content.ts | 13 ++-- .../src/context-menu-provider.tsx | 57 ++++++++++------- packages/compass-context-menu/src/index.ts | 7 ++- packages/compass-context-menu/src/types.ts | 29 +++++---- .../src/use-context-menu.spec.tsx | 61 ++++++++++--------- .../src/use-context-menu.tsx | 59 +++++++++--------- 6 files changed, 124 insertions(+), 102 deletions(-) diff --git a/packages/compass-context-menu/src/context-menu-content.ts b/packages/compass-context-menu/src/context-menu-content.ts index 6856f1fe684..c301983a679 100644 --- a/packages/compass-context-menu/src/context-menu-content.ts +++ b/packages/compass-context-menu/src/context-menu-content.ts @@ -1,23 +1,24 @@ +import type { ContextMenuItemGroup } from './types'; + const CONTEXT_MENUS_SYMBOL = Symbol('context_menus'); export type EnhancedMouseEvent = MouseEvent & { - [CONTEXT_MENUS_SYMBOL]?: React.ComponentType[]; + [CONTEXT_MENUS_SYMBOL]?: ContextMenuItemGroup[]; }; export function getContextMenuContent( event: EnhancedMouseEvent -): React.ComponentType[] { +): ContextMenuItemGroup[] { return event[CONTEXT_MENUS_SYMBOL] ?? []; } export function appendContextMenuContent( event: EnhancedMouseEvent, - content: React.ComponentType + content: ContextMenuItemGroup ) { // Initialize if not already patched - if (event[CONTEXT_MENUS_SYMBOL] === undefined) { - event[CONTEXT_MENUS_SYMBOL] = [content]; - return; + if (!event[CONTEXT_MENUS_SYMBOL]) { + event[CONTEXT_MENUS_SYMBOL] = []; } event[CONTEXT_MENUS_SYMBOL].push(content); } diff --git a/packages/compass-context-menu/src/context-menu-provider.tsx b/packages/compass-context-menu/src/context-menu-provider.tsx index 43d9e893367..5dd1cba2cff 100644 --- a/packages/compass-context-menu/src/context-menu-provider.tsx +++ b/packages/compass-context-menu/src/context-menu-provider.tsx @@ -5,8 +5,7 @@ import React, { useMemo, createContext, } from 'react'; -import type { ContextMenuContext, MenuState } from './types'; -import { ContextMenu } from './context-menu'; +import type { ContextMenuContext, ContextMenuState } from './types'; import type { EnhancedMouseEvent } from './context-menu-content'; import { getContextMenuContent } from './context-menu-content'; @@ -14,20 +13,42 @@ export const Context = createContext(null); export function ContextMenuProvider({ children, + wrapper, }: { children: React.ReactNode; + wrapper: React.ComponentType<{ + menu: ContextMenuState & { close: () => void }; + }>; }) { - const [menu, setMenu] = useState({ isOpen: false }); - const close = useCallback(() => setMenu({ isOpen: false }), [setMenu]); + const [menu, setMenu] = useState({ + isOpen: false, + itemGroups: [], + position: { x: 0, y: 0 }, + }); + const close = useCallback(() => setMenu({ ...menu, isOpen: false }), [menu]); + + const handleClosingEvent = useCallback( + (event: Event) => { + if (!event.defaultPrevented) { + setMenu({ ...menu, isOpen: false }); + } + }, + [menu] + ); useEffect(() => { function handleContextMenu(event: MouseEvent) { event.preventDefault(); + + const itemGroups = getContextMenuContent(event as EnhancedMouseEvent); + + if (itemGroups.length === 0) { + return; + } + setMenu({ isOpen: true, - children: getContextMenuContent(event as EnhancedMouseEvent).map( - (Content, index) => - ), + itemGroups, position: { // TODO: Fix handling offset while scrolling x: event.clientX, @@ -36,22 +57,14 @@ export function ContextMenuProvider({ }); } - function handleClosingEvent(event: Event) { - if (!event.defaultPrevented) { - setMenu({ isOpen: false }); - } - } - document.addEventListener('contextmenu', handleContextMenu); - document.addEventListener('click', handleClosingEvent); window.addEventListener('resize', handleClosingEvent); return () => { document.removeEventListener('contextmenu', handleContextMenu); - document.removeEventListener('click', handleClosingEvent); window.removeEventListener('resize', handleClosingEvent); }; - }, [setMenu]); + }, [handleClosingEvent]); const value = useMemo( () => ({ @@ -60,12 +73,12 @@ export function ContextMenuProvider({ [close] ); + const Wrapper = wrapper ?? React.Fragment; + return ( - <> - {children} - {menu.isOpen && ( - {menu.children} - )} - + + {children} + + ); } diff --git a/packages/compass-context-menu/src/index.ts b/packages/compass-context-menu/src/index.ts index d60a97e04b3..75d933ef767 100644 --- a/packages/compass-context-menu/src/index.ts +++ b/packages/compass-context-menu/src/index.ts @@ -1,2 +1,7 @@ export { useContextMenu } from './use-context-menu'; -export type { ContextMenuItem } from './types'; +export { ContextMenuProvider } from './context-menu-provider'; +export type { + ContextMenuItem, + ContextMenuItemGroup, + ContextMenuWrapperProps, +} from './types'; diff --git a/packages/compass-context-menu/src/types.ts b/packages/compass-context-menu/src/types.ts index f453930dcb3..91e8d65cdcc 100644 --- a/packages/compass-context-menu/src/types.ts +++ b/packages/compass-context-menu/src/types.ts @@ -1,15 +1,20 @@ -export type MenuState = - | { - isOpen: false; - } - | { - isOpen: true; - children: React.ReactNode; - position: { - x: number; - y: number; - }; - }; +export interface ContextMenuItemGroup { + items: ContextMenuItem[]; + originListener: (event: MouseEvent) => void; +} + +export type ContextMenuState = { + isOpen: boolean; + itemGroups: ContextMenuItemGroup[]; + position: { + x: number; + y: number; + }; +}; + +export type ContextMenuWrapperProps = { + menu: ContextMenuState & { close: () => void }; +}; export type ContextMenuContext = { close(): void; diff --git a/packages/compass-context-menu/src/use-context-menu.spec.tsx b/packages/compass-context-menu/src/use-context-menu.spec.tsx index eb857db9aeb..cfe0bfc7ef4 100644 --- a/packages/compass-context-menu/src/use-context-menu.spec.tsx +++ b/packages/compass-context-menu/src/use-context-menu.spec.tsx @@ -4,27 +4,29 @@ import { expect } from 'chai'; import sinon from 'sinon'; import { useContextMenu } from './use-context-menu'; import { ContextMenuProvider } from './context-menu-provider'; -import type { ContextMenuItem } from './types'; +import type { ContextMenuItem, ContextMenuWrapperProps } from './types'; describe('useContextMenu', function () { - const TestMenu: React.FC<{ items: ContextMenuItem[] }> = ({ items }) => ( + const TestMenu: React.FC = ({ menu }) => (
- {items.map((item, idx) => ( -
item.onAction?.(event)} - onKeyDown={(event) => { - if (event.key === 'Enter') { - item.onAction?.(event); - } - }} - > - {item.label} -
- ))} + {menu.itemGroups.flatMap((group, groupIdx) => + group.items.map((item, idx) => ( +
item.onAction?.(event)} + onKeyDown={(event) => { + if (event.key === 'Enter') { + item.onAction?.(event); + } + }} + > + {item.label} +
+ )) + )}
); @@ -33,9 +35,9 @@ describe('useContextMenu', function () { onAction, }: { onRegister?: (ref: any) => void; - onAction?: (id) => void; + onAction?: (id: number) => void; }) => { - const contextMenu = useContextMenu({ Menu: TestMenu }); + const contextMenu = useContextMenu(); const items: ContextMenuItem[] = [ { label: 'Test Item', @@ -55,7 +57,6 @@ describe('useContextMenu', function () { ); }; - // Add new test components for nested context menu scenario const ParentComponent = ({ onAction, children, @@ -63,7 +64,7 @@ describe('useContextMenu', function () { onAction?: (id: number) => void; children?: React.ReactNode; }) => { - const contextMenu = useContextMenu({ Menu: TestMenu }); + const contextMenu = useContextMenu(); const parentItems: ContextMenuItem[] = [ { label: 'Parent Item 1', @@ -89,7 +90,7 @@ describe('useContextMenu', function () { }: { onAction?: (id: number) => void; }) => { - const contextMenu = useContextMenu({ Menu: TestMenu }); + const contextMenu = useContextMenu(); const childItems: ContextMenuItem[] = [ { label: 'Child Item 1', @@ -135,7 +136,7 @@ describe('useContextMenu', function () { it('renders without error', function () { render( - + ); @@ -147,7 +148,7 @@ describe('useContextMenu', function () { const onRegister = sinon.spy(); render( - + ); @@ -158,7 +159,7 @@ describe('useContextMenu', function () { it('shows context menu on right click', function () { render( - + ); @@ -173,7 +174,7 @@ describe('useContextMenu', function () { describe('with nested context menus', function () { it('shows only parent items when right clicking parent area', function () { render( - + ); @@ -192,7 +193,7 @@ describe('useContextMenu', function () { it('shows both parent and child items when right clicking child area', function () { render( - + @@ -214,7 +215,7 @@ describe('useContextMenu', function () { const childOnAction = sinon.spy(); render( - + @@ -237,7 +238,7 @@ describe('useContextMenu', function () { const childOnAction = sinon.spy(); render( - + diff --git a/packages/compass-context-menu/src/use-context-menu.tsx b/packages/compass-context-menu/src/use-context-menu.tsx index fa9e20e7537..a60aeba4c69 100644 --- a/packages/compass-context-menu/src/use-context-menu.tsx +++ b/packages/compass-context-menu/src/use-context-menu.tsx @@ -1,4 +1,5 @@ -import React, { useContext, useMemo, useRef } from 'react'; +import type { RefCallback } from 'react'; +import { useContext, useMemo, useRef } from 'react'; import { Context } from './context-menu-provider'; import { appendContextMenuContent } from './context-menu-content'; import type { ContextMenuItem } from './types'; @@ -12,17 +13,12 @@ export type ContextMenuMethods = { * Register the menu items for the context menu. * @returns a callback ref to be passed onto the element responsible for triggering the menu. */ - registerItems: (items: T[]) => React.RefCallback; + registerItems: (items: T[]) => RefCallback; }; -export function useContextMenu({ - Menu, -}: { - Menu: React.ComponentType<{ - items: T[]; - }>; -}): ContextMenuMethods { - // Get the close function from the ContextProvider +export function useContextMenu< + T extends ContextMenuItem = ContextMenuItem +>(): ContextMenuMethods { const context = useContext(Context); const previous = useRef void]>( null @@ -33,32 +29,33 @@ export function useContextMenu({ throw new Error('useContextMenu called outside of the provider'); } - const register = ( - content: React.ComponentType - ): React.RefCallback => { - function listener(event: MouseEvent) { - appendContextMenuContent(event, content); - } - return (trigger: HTMLElement | null) => { - if (previous.current) { - const [previousTrigger, previousListener] = previous.current; - previousTrigger.removeEventListener('contextmenu', previousListener); - } - if (trigger) { - trigger.addEventListener('contextmenu', listener); - previous.current = [trigger, listener]; - } - }; - }; - return { close: context.close.bind(context), /** * @returns a callback ref, passed onto the element responsible for triggering the menu. */ - registerItems(items: T[]) { - return register(() => ); + registerItems(items: ContextMenuItem[]) { + function listener(event: MouseEvent): void { + appendContextMenuContent(event, { + items, + originListener: listener, + }); + } + + return (trigger: HTMLElement | null) => { + if (previous.current) { + const [previousTrigger, previousListener] = previous.current; + previousTrigger.removeEventListener( + 'contextmenu', + previousListener + ); + } + if (trigger) { + trigger.addEventListener('contextmenu', listener); + previous.current = [trigger, listener]; + } + }; }, }; - }, [context, Menu]); + }, [context]); } From ca1fb86e94310726b48829e599e2409f82ceb3ac Mon Sep 17 00:00:00 2001 From: gagik Date: Fri, 23 May 2025 17:12:31 +0200 Subject: [PATCH 23/76] fix: delete redundant context menu --- .../compass-context-menu/src/context-menu.tsx | 22 ------------------- 1 file changed, 22 deletions(-) delete mode 100644 packages/compass-context-menu/src/context-menu.tsx diff --git a/packages/compass-context-menu/src/context-menu.tsx b/packages/compass-context-menu/src/context-menu.tsx deleted file mode 100644 index b053bc4963a..00000000000 --- a/packages/compass-context-menu/src/context-menu.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { createPortal } from 'react-dom'; -import React from 'react'; - -type ContextMenuProps = React.PropsWithChildren<{ - position: { - x: number; - y: number; - }; -}>; - -export function ContextMenu({ children, position }: ContextMenuProps) { - const container = document.getElementById('context-menu-container'); - if (container === null) { - throw new Error('Expected a container for the context menu in the DOM'); - } - return createPortal( -
- {children} -
, - container - ); -} From aacdf1dc4f93b88dba7fce497ddbe8d262ee18cb Mon Sep 17 00:00:00 2001 From: gagik Date: Tue, 20 May 2025 16:59:43 +0200 Subject: [PATCH 24/76] feat(compass-context-menu): add a headless context menu package --- package-lock.json | 104 ++++++++++++++++++ packages/compass-context-menu/.depcheckrc | 8 ++ packages/compass-context-menu/.eslintignore | 2 + packages/compass-context-menu/.eslintrc.js | 9 ++ packages/compass-context-menu/.mocharc.js | 2 + packages/compass-context-menu/package.json | 72 ++++++++++++ .../src/context-menu-content.ts | 23 ++++ .../src/context-menu-provider.tsx | 68 ++++++++++++ .../compass-context-menu/src/context-menu.tsx | 22 ++++ packages/compass-context-menu/src/types.ts | 21 ++++ .../src/use-context-menu.tsx | 55 +++++++++ .../compass-context-menu/tsconfig-lint.json | 5 + packages/compass-context-menu/tsconfig.json | 8 ++ 13 files changed, 399 insertions(+) create mode 100644 packages/compass-context-menu/.depcheckrc create mode 100644 packages/compass-context-menu/.eslintignore create mode 100644 packages/compass-context-menu/.eslintrc.js create mode 100644 packages/compass-context-menu/.mocharc.js create mode 100644 packages/compass-context-menu/package.json create mode 100644 packages/compass-context-menu/src/context-menu-content.ts create mode 100644 packages/compass-context-menu/src/context-menu-provider.tsx create mode 100644 packages/compass-context-menu/src/context-menu.tsx create mode 100644 packages/compass-context-menu/src/types.ts create mode 100644 packages/compass-context-menu/src/use-context-menu.tsx create mode 100644 packages/compass-context-menu/tsconfig-lint.json create mode 100644 packages/compass-context-menu/tsconfig.json diff --git a/package-lock.json b/package-lock.json index 80e503995ba..a205c4ec913 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7864,6 +7864,10 @@ "resolved": "packages/compass-connections-navigation", "link": true }, + "node_modules/@mongodb-js/compass-context-menu": { + "resolved": "packages/compass-context-menu", + "link": true + }, "node_modules/@mongodb-js/compass-crud": { "resolved": "packages/compass-crud", "link": true @@ -43201,6 +43205,62 @@ "node": ">=0.3.1" } }, + "packages/compass-context-menu": { + "name": "@mongodb-js/compass-context-menu", + "version": "0.0.1", + "license": "SSPL", + "dependencies": { + "react": "^17.0.2" + }, + "devDependencies": { + "@mongodb-js/eslint-config-compass": "^1.3.8", + "@mongodb-js/mocha-config-compass": "^1.6.8", + "@mongodb-js/prettier-config-compass": "^1.2.8", + "@mongodb-js/tsconfig-compass": "^1.2.8", + "@types/chai": "^4.2.21", + "@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", + "gen-esm-wrapper": "^1.1.0", + "mocha": "^10.2.0", + "nyc": "^15.1.0", + "sinon": "^9.2.3", + "typescript": "^5.0.4" + } + }, + "packages/compass-context-menu/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "packages/compass-context-menu/node_modules/sinon": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.4.tgz", + "integrity": "sha512-zljcULZQsJxVra28qIAL6ow1Z9tpattkCTEJR4RBP3TGc00FcttsP5pK284Nas5WjMZU5Yzy3kAIp3B3KRf5Yg==", + "deprecated": "16.1.1", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^1.8.1", + "@sinonjs/fake-timers": "^6.0.1", + "@sinonjs/samsam": "^5.3.1", + "diff": "^4.0.2", + "nise": "^4.0.4", + "supports-color": "^7.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, "packages/compass-crud": { "name": "@mongodb-js/compass-crud", "version": "13.57.0", @@ -55646,6 +55706,50 @@ } } }, + "@mongodb-js/compass-context-menu": { + "version": "file:packages/compass-context-menu", + "requires": { + "@mongodb-js/eslint-config-compass": "^1.3.8", + "@mongodb-js/mocha-config-compass": "^1.6.8", + "@mongodb-js/prettier-config-compass": "^1.2.8", + "@mongodb-js/tsconfig-compass": "^1.2.8", + "@types/chai": "^4.2.21", + "@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", + "gen-esm-wrapper": "^1.1.0", + "mocha": "^10.2.0", + "nyc": "^15.1.0", + "react": "^17.0.2", + "sinon": "^9.2.3", + "typescript": "^5.0.4" + }, + "dependencies": { + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "sinon": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.4.tgz", + "integrity": "sha512-zljcULZQsJxVra28qIAL6ow1Z9tpattkCTEJR4RBP3TGc00FcttsP5pK284Nas5WjMZU5Yzy3kAIp3B3KRf5Yg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.8.1", + "@sinonjs/fake-timers": "^6.0.1", + "@sinonjs/samsam": "^5.3.1", + "diff": "^4.0.2", + "nise": "^4.0.4", + "supports-color": "^7.1.0" + } + } + } + }, "@mongodb-js/compass-crud": { "version": "file:packages/compass-crud", "requires": { diff --git a/packages/compass-context-menu/.depcheckrc b/packages/compass-context-menu/.depcheckrc new file mode 100644 index 00000000000..ab0ef21b740 --- /dev/null +++ b/packages/compass-context-menu/.depcheckrc @@ -0,0 +1,8 @@ +ignores: + - '@mongodb-js/prettier-config-compass' + - '@mongodb-js/tsconfig-compass' + - '@types/chai' + - '@types/sinon-chai' + - 'sinon' +ignore-patterns: + - 'dist' diff --git a/packages/compass-context-menu/.eslintignore b/packages/compass-context-menu/.eslintignore new file mode 100644 index 00000000000..85a8a75e68c --- /dev/null +++ b/packages/compass-context-menu/.eslintignore @@ -0,0 +1,2 @@ +.nyc-output +dist diff --git a/packages/compass-context-menu/.eslintrc.js b/packages/compass-context-menu/.eslintrc.js new file mode 100644 index 00000000000..9c3ab95632f --- /dev/null +++ b/packages/compass-context-menu/.eslintrc.js @@ -0,0 +1,9 @@ +'use strict'; +module.exports = { + root: true, + extends: ['@mongodb-js/eslint-config-compass'], + parserOptions: { + tsconfigRootDir: __dirname, + project: ['./tsconfig-lint.json'], + }, +}; diff --git a/packages/compass-context-menu/.mocharc.js b/packages/compass-context-menu/.mocharc.js new file mode 100644 index 00000000000..e7eaccd61fa --- /dev/null +++ b/packages/compass-context-menu/.mocharc.js @@ -0,0 +1,2 @@ +'use strict'; +module.exports = require('@mongodb-js/mocha-config-compass'); diff --git a/packages/compass-context-menu/package.json b/packages/compass-context-menu/package.json new file mode 100644 index 00000000000..3cf186e0270 --- /dev/null +++ b/packages/compass-context-menu/package.json @@ -0,0 +1,72 @@ +{ + "name": "@mongodb-js/compass-context-menu", + "author": { + "name": "MongoDB Inc", + "email": "compass@mongodb.com" + }, + "publishConfig": { + "access": "public" + }, + "bugs": { + "url": "https://jira.mongodb.org/projects/COMPASS/issues", + "email": "compass@mongodb.com" + }, + "homepage": "https://github.com/mongodb-js/compass", + "version": "0.0.1", + "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", + "prepublishOnly": "npm run compile && compass-scripts check-exports-exist", + "compile": "tsc -p tsconfig.json && gen-esm-wrapper . ./dist/.esm-wrapper.mjs", + "typecheck": "tsc -p tsconfig-lint.json --noEmit", + "eslint": "eslint-compass", + "prettier": "prettier-compass", + "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-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", + "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." + }, + "dependencies": { + "react": "^17.0.2" + }, + "devDependencies": { + "@mongodb-js/eslint-config-compass": "^1.3.8", + "@mongodb-js/mocha-config-compass": "^1.6.8", + "@mongodb-js/prettier-config-compass": "^1.2.8", + "@mongodb-js/tsconfig-compass": "^1.2.8", + "@types/chai": "^4.2.21", + "@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", + "gen-esm-wrapper": "^1.1.0", + "mocha": "^10.2.0", + "nyc": "^15.1.0", + "sinon": "^9.2.3", + "typescript": "^5.0.4" + } +} diff --git a/packages/compass-context-menu/src/context-menu-content.ts b/packages/compass-context-menu/src/context-menu-content.ts new file mode 100644 index 00000000000..6856f1fe684 --- /dev/null +++ b/packages/compass-context-menu/src/context-menu-content.ts @@ -0,0 +1,23 @@ +const CONTEXT_MENUS_SYMBOL = Symbol('context_menus'); + +export type EnhancedMouseEvent = MouseEvent & { + [CONTEXT_MENUS_SYMBOL]?: React.ComponentType[]; +}; + +export function getContextMenuContent( + event: EnhancedMouseEvent +): React.ComponentType[] { + return event[CONTEXT_MENUS_SYMBOL] ?? []; +} + +export function appendContextMenuContent( + event: EnhancedMouseEvent, + content: React.ComponentType +) { + // Initialize if not already patched + if (event[CONTEXT_MENUS_SYMBOL] === undefined) { + event[CONTEXT_MENUS_SYMBOL] = [content]; + return; + } + event[CONTEXT_MENUS_SYMBOL].push(content); +} diff --git a/packages/compass-context-menu/src/context-menu-provider.tsx b/packages/compass-context-menu/src/context-menu-provider.tsx new file mode 100644 index 00000000000..7a7b59bf16a --- /dev/null +++ b/packages/compass-context-menu/src/context-menu-provider.tsx @@ -0,0 +1,68 @@ +import React, { + useCallback, + useEffect, + useState, + useMemo, + createContext, +} from 'react'; +import type { ContextMenuContext, MenuState } from './types'; +import { ContextMenu } from './context-menu'; +import type { EnhancedMouseEvent } from './context-menu-content'; +import { getContextMenuContent } from './context-menu-content'; + +export const Context = createContext(null); + +export function ContextMenuProvider({ + children, +}: React.PropsWithChildren) { + const [menu, setMenu] = useState({ isOpen: false }); + const close = useCallback(() => setMenu({ isOpen: false }), [setMenu]); + + useEffect(() => { + function handleContextMenu(event: MouseEvent) { + event.preventDefault(); + setMenu({ + isOpen: true, + children: getContextMenuContent(event as EnhancedMouseEvent).map( + (Content, index) => + ), + position: { + // TODO: Fix handling offset while scrolling + x: event.clientX, + y: event.clientY, + }, + }); + } + document.addEventListener('contextmenu', handleContextMenu); + + function handleClosingEvent(event: Event) { + if (!event.defaultPrevented) { + setMenu({ isOpen: false }); + } + } + document.addEventListener('click', handleClosingEvent); + window.addEventListener('resize', handleClosingEvent); + + return () => { + document.removeEventListener('contextmenu', handleContextMenu); + document.removeEventListener('click', handleClosingEvent); + window.removeEventListener('resize', handleClosingEvent); + }; + }, [setMenu]); + + const value = useMemo( + () => ({ + close, + }), + [close] + ); + + return ( + <> + {children} + {menu.isOpen && ( + {menu.children} + )} + + ); +} diff --git a/packages/compass-context-menu/src/context-menu.tsx b/packages/compass-context-menu/src/context-menu.tsx new file mode 100644 index 00000000000..b053bc4963a --- /dev/null +++ b/packages/compass-context-menu/src/context-menu.tsx @@ -0,0 +1,22 @@ +import { createPortal } from 'react-dom'; +import React from 'react'; + +type ContextMenuProps = React.PropsWithChildren<{ + position: { + x: number; + y: number; + }; +}>; + +export function ContextMenu({ children, position }: ContextMenuProps) { + const container = document.getElementById('context-menu-container'); + if (container === null) { + throw new Error('Expected a container for the context menu in the DOM'); + } + return createPortal( +
+ {children} +
, + container + ); +} diff --git a/packages/compass-context-menu/src/types.ts b/packages/compass-context-menu/src/types.ts new file mode 100644 index 00000000000..07efb491ac6 --- /dev/null +++ b/packages/compass-context-menu/src/types.ts @@ -0,0 +1,21 @@ +export type MenuState = + | { + isOpen: false; + } + | { + isOpen: true; + children: React.ReactNode; + position: { + x: number; + y: number; + }; + }; + +export type ContextMenuContext = { + close(): void; +}; + +export type MenuItem = { + label: string; + onAction: (event: Event) => void; +}; diff --git a/packages/compass-context-menu/src/use-context-menu.tsx b/packages/compass-context-menu/src/use-context-menu.tsx new file mode 100644 index 00000000000..d7ccc114a90 --- /dev/null +++ b/packages/compass-context-menu/src/use-context-menu.tsx @@ -0,0 +1,55 @@ +import React, { useContext, useMemo, useRef } from 'react'; +import { Context } from './context-menu-provider'; +import { appendContextMenuContent } from './context-menu-content'; +import type { MenuItem } from './types'; + +/** + * @returns an object with methods to {@link register} content for the menu and {@link close} the menu + */ +export function useContextMenu({ + Menu, +}: { + Menu: React.ComponentType<{ + items: MenuItem[]; + }>; +}) { + // Get the close function from the ContextProvider + const context = useContext(Context); + const previous = useRef void]>( + null + ); + + return useMemo(() => { + if (!context) { + throw new Error('useContextMenu called outside of the provider'); + } + + return { + close: context.close.bind(context), + /** + * @returns a callback ref, passed onto the element responsible for triggering the menu. + */ + register(content: React.ComponentType) { + function listener(event: MouseEvent) { + appendContextMenuContent(event, content); + } + return (trigger: HTMLElement | null) => { + if (previous.current) { + const [previousTrigger, previousListener] = previous.current; + previousTrigger.removeEventListener( + 'contextmenu', + previousListener + ); + } + if (trigger) { + trigger.addEventListener('contextmenu', listener); + previous.current = [trigger, listener]; + } + }; + }, + registerItems(items: MenuItem[]) { + return this.register(() => ); + }, + }; + }, [context, Menu]); +} diff --git a/packages/compass-context-menu/tsconfig-lint.json b/packages/compass-context-menu/tsconfig-lint.json new file mode 100644 index 00000000000..6bdef84f322 --- /dev/null +++ b/packages/compass-context-menu/tsconfig-lint.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.json", + "include": ["**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/compass-context-menu/tsconfig.json b/packages/compass-context-menu/tsconfig.json new file mode 100644 index 00000000000..79bc84584ce --- /dev/null +++ b/packages/compass-context-menu/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@mongodb-js/tsconfig-compass/tsconfig.react.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": ["src/**/*"], + "exclude": ["./src/**/*.spec.*"] +} From cb99706c351a314da3f718f1e7aada9a06ce12fa Mon Sep 17 00:00:00 2001 From: gagik Date: Wed, 21 May 2025 13:41:30 +0200 Subject: [PATCH 25/76] wip --- .../src/context-menu-provider.tsx | 4 +- .../src/use-context-menu.spec.tsx | 144 ++++++++++++++++++ 2 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 packages/compass-context-menu/src/use-context-menu.spec.tsx diff --git a/packages/compass-context-menu/src/context-menu-provider.tsx b/packages/compass-context-menu/src/context-menu-provider.tsx index 7a7b59bf16a..499d9c273c1 100644 --- a/packages/compass-context-menu/src/context-menu-provider.tsx +++ b/packages/compass-context-menu/src/context-menu-provider.tsx @@ -14,7 +14,9 @@ export const Context = createContext(null); export function ContextMenuProvider({ children, -}: React.PropsWithChildren) { +}: { + children: React.ReactNode; +}) { const [menu, setMenu] = useState({ isOpen: false }); const close = useCallback(() => setMenu({ isOpen: false }), [setMenu]); diff --git a/packages/compass-context-menu/src/use-context-menu.spec.tsx b/packages/compass-context-menu/src/use-context-menu.spec.tsx new file mode 100644 index 00000000000..7337463e819 --- /dev/null +++ b/packages/compass-context-menu/src/use-context-menu.spec.tsx @@ -0,0 +1,144 @@ +import React from 'react'; +import { + render, + screen, + cleanup, + userEvent, +} from '@mongodb-js/testing-library-compass'; +import { expect } from 'chai'; +import sinon from 'sinon'; +import { useContextMenu } from './use-context-menu'; +import { ContextMenuProvider } from './context-menu-provider'; +import type { MenuItem } from './types'; + +describe('useContextMenu', function () { + const TestMenu: React.FC<{ items: MenuItem[] }> = ({ items }) => ( +
+ {items.map((item, idx) => ( +
+ {item.label} +
+ ))} +
+ ); + + const TestComponent = ({ + onRegister, + }: { + onRegister?: (ref: any) => void; + }) => { + const contextMenu = useContextMenu({ Menu: TestMenu }); + const items: MenuItem[] = [ + { + label: 'Test Item', + onAction: () => { + /* noop */ + }, + }, + ]; + const ref = contextMenu.registerItems(items); + + React.useEffect(() => { + onRegister?.(ref); + }, [ref, onRegister]); + + return ( +
+ Test Component +
+ ); + }; + + afterEach(cleanup); + + describe('when used outside provider', function () { + it('throws an error', function () { + expect(() => { + render(); + }).to.throw('useContextMenu called outside of the provider'); + }); + }); + + describe('when used inside provider', function () { + beforeEach(() => { + // Create the container for the context menu portal + const container = document.createElement('div'); + container.id = 'context-menu-container'; + document.body.appendChild(container); + }); + + afterEach(() => { + // Clean up the container + const container = document.getElementById('context-menu-container'); + if (container) { + document.body.removeChild(container); + } + }); + + it('renders without error', function () { + render( + + + + ); + + expect(screen.getByTestId('test-trigger')).to.exist; + }); + + it('registers context menu event listener', function () { + const onRegister = sinon.spy(); + + render( + + + + ); + + expect(onRegister).to.have.been.calledOnce; + expect(onRegister.firstCall.args[0]).to.be.a('function'); + }); + + it('shows context menu on right click', function () { + render( + + + + ); + + const trigger = screen.getByTestId('test-trigger'); + userEvent.click(trigger, { button: 2 }); + + // The menu should be rendered in the portal + expect(screen.getByTestId('menu-item-Test Item')).to.exist; + }); + + it('cleans up previous event listener when ref changes', function () { + const removeEventListenerSpy = sinon.spy(); + const addEventListenerSpy = sinon.spy(); + + const { rerender } = render( + + + + ); + + // Simulate ref change + const ref = screen.getByTestId('test-trigger'); + Object.defineProperty(ref, 'addEventListener', { + value: addEventListenerSpy, + }); + Object.defineProperty(ref, 'removeEventListener', { + value: removeEventListenerSpy, + }); + + rerender( + + + + ); + + expect(removeEventListenerSpy).to.have.been.calledWith('contextmenu'); + expect(addEventListenerSpy).to.have.been.calledWith('contextmenu'); + }); + }); +}); From da22b51b2bd887bb34b824eabee9c3b1cd2a59e1 Mon Sep 17 00:00:00 2001 From: gagik Date: Wed, 21 May 2025 15:16:18 +0200 Subject: [PATCH 26/76] fix: add tests --- packages/compass-context-menu/.mocharc.js | 2 +- .../src/use-context-menu.spec.tsx | 58 +++++++------------ .../src/use-context-menu.tsx | 6 +- 3 files changed, 26 insertions(+), 40 deletions(-) diff --git a/packages/compass-context-menu/.mocharc.js b/packages/compass-context-menu/.mocharc.js index e7eaccd61fa..5a33f216327 100644 --- a/packages/compass-context-menu/.mocharc.js +++ b/packages/compass-context-menu/.mocharc.js @@ -1,2 +1,2 @@ 'use strict'; -module.exports = require('@mongodb-js/mocha-config-compass'); +module.exports = require('@mongodb-js/mocha-config-compass/react'); diff --git a/packages/compass-context-menu/src/use-context-menu.spec.tsx b/packages/compass-context-menu/src/use-context-menu.spec.tsx index 7337463e819..8a5cb4b6fee 100644 --- a/packages/compass-context-menu/src/use-context-menu.spec.tsx +++ b/packages/compass-context-menu/src/use-context-menu.spec.tsx @@ -1,36 +1,37 @@ import React from 'react'; -import { - render, - screen, - cleanup, - userEvent, -} from '@mongodb-js/testing-library-compass'; +import { render, screen, userEvent } from '@mongodb-js/testing-library-compass'; import { expect } from 'chai'; import sinon from 'sinon'; import { useContextMenu } from './use-context-menu'; import { ContextMenuProvider } from './context-menu-provider'; import type { MenuItem } from './types'; +type TestMenuItem = MenuItem & { id: number }; + describe('useContextMenu', function () { - const TestMenu: React.FC<{ items: MenuItem[] }> = ({ items }) => ( + const TestMenu: React.FC<{ items: TestMenuItem[] }> = ({ items }) => (
{items.map((item, idx) => ( -
+
{item.label}
))}
); - const TestComponent = ({ - onRegister, - }: { - onRegister?: (ref: any) => void; - }) => { + const TestComponent = () => { const contextMenu = useContextMenu({ Menu: TestMenu }); - const items: MenuItem[] = [ + const items: TestMenuItem[] = [ { - label: 'Test Item', + id: 1, + label: 'Test A', + onAction: () => { + /* noop */ + }, + }, + { + id: 2, + label: 'Test B', onAction: () => { /* noop */ }, @@ -38,10 +39,6 @@ describe('useContextMenu', function () { ]; const ref = contextMenu.registerItems(items); - React.useEffect(() => { - onRegister?.(ref); - }, [ref, onRegister]); - return (
Test Component @@ -49,8 +46,6 @@ describe('useContextMenu', function () { ); }; - afterEach(cleanup); - describe('when used outside provider', function () { it('throws an error', function () { expect(() => { @@ -59,7 +54,7 @@ describe('useContextMenu', function () { }); }); - describe('when used inside provider', function () { + describe('with valid provider', function () { beforeEach(() => { // Create the container for the context menu portal const container = document.createElement('div'); @@ -85,19 +80,6 @@ describe('useContextMenu', function () { expect(screen.getByTestId('test-trigger')).to.exist; }); - it('registers context menu event listener', function () { - const onRegister = sinon.spy(); - - render( - - - - ); - - expect(onRegister).to.have.been.calledOnce; - expect(onRegister.firstCall.args[0]).to.be.a('function'); - }); - it('shows context menu on right click', function () { render( @@ -105,11 +87,15 @@ describe('useContextMenu', function () { ); + expect(screen.queryByTestId('menu-item-1')).not.to.exist; + expect(screen.queryByTestId('menu-item-2')).not.to.exist; + const trigger = screen.getByTestId('test-trigger'); userEvent.click(trigger, { button: 2 }); // The menu should be rendered in the portal - expect(screen.getByTestId('menu-item-Test Item')).to.exist; + expect(screen.getByTestId('menu-item-1')).to.exist; + expect(screen.getByTestId('menu-item-2')).to.exist; }); it('cleans up previous event listener when ref changes', function () { diff --git a/packages/compass-context-menu/src/use-context-menu.tsx b/packages/compass-context-menu/src/use-context-menu.tsx index d7ccc114a90..37993b04f15 100644 --- a/packages/compass-context-menu/src/use-context-menu.tsx +++ b/packages/compass-context-menu/src/use-context-menu.tsx @@ -6,11 +6,11 @@ import type { MenuItem } from './types'; /** * @returns an object with methods to {@link register} content for the menu and {@link close} the menu */ -export function useContextMenu({ +export function useContextMenu({ Menu, }: { Menu: React.ComponentType<{ - items: MenuItem[]; + items: T[]; }>; }) { // Get the close function from the ContextProvider @@ -47,7 +47,7 @@ export function useContextMenu({ } }; }, - registerItems(items: MenuItem[]) { + registerItems(items: T[]) { return this.register(() => ); }, }; From 78ff94cac09c3457adae201d09746c9ccadf93cb Mon Sep 17 00:00:00 2001 From: gagik Date: Wed, 21 May 2025 16:50:00 +0200 Subject: [PATCH 27/76] fix: add tests and fix types --- packages/compass-context-menu/src/types.ts | 2 +- .../src/use-context-menu.spec.tsx | 219 ++++++++++++++---- 2 files changed, 175 insertions(+), 46 deletions(-) diff --git a/packages/compass-context-menu/src/types.ts b/packages/compass-context-menu/src/types.ts index 07efb491ac6..e9ac549ba63 100644 --- a/packages/compass-context-menu/src/types.ts +++ b/packages/compass-context-menu/src/types.ts @@ -17,5 +17,5 @@ export type ContextMenuContext = { export type MenuItem = { label: string; - onAction: (event: Event) => void; + onAction: (event: React.KeyboardEvent | React.MouseEvent) => void; }; diff --git a/packages/compass-context-menu/src/use-context-menu.spec.tsx b/packages/compass-context-menu/src/use-context-menu.spec.tsx index 8a5cb4b6fee..1d099272104 100644 --- a/packages/compass-context-menu/src/use-context-menu.spec.tsx +++ b/packages/compass-context-menu/src/use-context-menu.spec.tsx @@ -6,39 +6,48 @@ import { useContextMenu } from './use-context-menu'; import { ContextMenuProvider } from './context-menu-provider'; import type { MenuItem } from './types'; -type TestMenuItem = MenuItem & { id: number }; - describe('useContextMenu', function () { - const TestMenu: React.FC<{ items: TestMenuItem[] }> = ({ items }) => ( + const TestMenu: React.FC<{ items: MenuItem[] }> = ({ items }) => (
{items.map((item, idx) => ( -
+
item.onAction?.(event)} + onKeyDown={(event) => { + if (event.key === 'Enter') { + item.onAction?.(event); + } + }} + > {item.label}
))}
); - const TestComponent = () => { + const TestComponent = ({ + onRegister, + onAction, + }: { + onRegister?: (ref: any) => void; + onAction?: (id) => void; + }) => { const contextMenu = useContextMenu({ Menu: TestMenu }); - const items: TestMenuItem[] = [ - { - id: 1, - label: 'Test A', - onAction: () => { - /* noop */ - }, - }, + const items: MenuItem[] = [ { - id: 2, - label: 'Test B', - onAction: () => { - /* noop */ - }, + label: 'Test Item', + onAction: () => onAction?.(1), }, ]; const ref = contextMenu.registerItems(items); + React.useEffect(() => { + onRegister?.(ref); + }, [ref, onRegister]); + return (
Test Component @@ -46,6 +55,60 @@ describe('useContextMenu', function () { ); }; + // Add new test components for nested context menu scenario + const ParentComponent = ({ + onAction, + children, + }: { + onAction?: (id: number) => void; + children?: React.ReactNode; + }) => { + const contextMenu = useContextMenu({ Menu: TestMenu }); + const parentItems: MenuItem[] = [ + { + label: 'Parent Item 1', + onAction: () => onAction?.(1), + }, + { + label: 'Parent Item 2', + onAction: () => onAction?.(2), + }, + ]; + const ref = contextMenu.registerItems(parentItems); + + return ( +
+
Parent Component
+ {children} +
+ ); + }; + + const ChildComponent = ({ + onAction, + }: { + onAction?: (id: number) => void; + }) => { + const contextMenu = useContextMenu({ Menu: TestMenu }); + const childItems: MenuItem[] = [ + { + label: 'Child Item 1', + onAction: () => onAction?.(1), + }, + { + label: 'Child Item 2', + onAction: () => onAction?.(2), + }, + ]; + const ref = contextMenu.registerItems(childItems); + + return ( +
+ Child Component +
+ ); + }; + describe('when used outside provider', function () { it('throws an error', function () { expect(() => { @@ -54,7 +117,7 @@ describe('useContextMenu', function () { }); }); - describe('with valid provider', function () { + describe('with a valid provider', function () { beforeEach(() => { // Create the container for the context menu portal const container = document.createElement('div'); @@ -80,6 +143,19 @@ describe('useContextMenu', function () { expect(screen.getByTestId('test-trigger')).to.exist; }); + it('registers context menu event listener', function () { + const onRegister = sinon.spy(); + + render( + + + + ); + + expect(onRegister).to.have.been.calledOnce; + expect(onRegister.firstCall.args[0]).to.be.a('function'); + }); + it('shows context menu on right click', function () { render( @@ -87,44 +163,97 @@ describe('useContextMenu', function () { ); - expect(screen.queryByTestId('menu-item-1')).not.to.exist; - expect(screen.queryByTestId('menu-item-2')).not.to.exist; - const trigger = screen.getByTestId('test-trigger'); userEvent.click(trigger, { button: 2 }); // The menu should be rendered in the portal - expect(screen.getByTestId('menu-item-1')).to.exist; - expect(screen.getByTestId('menu-item-2')).to.exist; + expect(screen.getByTestId('menu-item-Test Item')).to.exist; }); - it('cleans up previous event listener when ref changes', function () { - const removeEventListenerSpy = sinon.spy(); - const addEventListenerSpy = sinon.spy(); + describe('with nested context menus', function () { + it('shows only parent items when right clicking parent area', function () { + render( + + + + ); - const { rerender } = render( - - - - ); + const parentTrigger = screen.getByTestId('parent-trigger'); + userEvent.click(parentTrigger, { button: 2 }); + + // Should show parent items + expect(screen.getByTestId('menu-item-Parent Item 1')).to.exist; + expect(screen.getByTestId('menu-item-Parent Item 2')).to.exist; - // Simulate ref change - const ref = screen.getByTestId('test-trigger'); - Object.defineProperty(ref, 'addEventListener', { - value: addEventListenerSpy, + // Should not show child items + expect(() => screen.getByTestId('menu-item-Child Item 1')).to.throw; + expect(() => screen.getByTestId('menu-item-Child Item 2')).to.throw; }); - Object.defineProperty(ref, 'removeEventListener', { - value: removeEventListenerSpy, + + it('shows both parent and child items when right clicking child area', function () { + render( + + + + + + ); + + const childTrigger = screen.getByTestId('child-trigger'); + userEvent.click(childTrigger, { button: 2 }); + + // Should show both parent and child items + expect(screen.getByTestId('menu-item-Parent Item 1')).to.exist; + expect(screen.getByTestId('menu-item-Parent Item 2')).to.exist; + expect(screen.getByTestId('menu-item-Child Item 1')).to.exist; + expect(screen.getByTestId('menu-item-Child Item 2')).to.exist; }); - rerender( - - - - ); + it('triggers only the child action when clicking child menu item', function () { + const parentOnAction = sinon.spy(); + const childOnAction = sinon.spy(); + + render( + + + + + + ); + + const childTrigger = screen.getByTestId('child-trigger'); + userEvent.click(childTrigger, { button: 2 }); + + const childItem1 = screen.getByTestId('menu-item-Child Item 1'); + userEvent.click(childItem1); - expect(removeEventListenerSpy).to.have.been.calledWith('contextmenu'); - expect(addEventListenerSpy).to.have.been.calledWith('contextmenu'); + expect(childOnAction).to.have.been.calledOnceWithExactly(1); + expect(parentOnAction).to.not.have.been.called; + expect(() => screen.getByTestId('test-menu')).to.throw; + }); + + it('triggers only the parent action when clicking a parent menu item from child context', function () { + const parentOnAction = sinon.spy(); + const childOnAction = sinon.spy(); + + render( + + + + + + ); + + const childTrigger = screen.getByTestId('child-trigger'); + userEvent.click(childTrigger, { button: 2 }); + + const parentItem1 = screen.getByTestId('menu-item-Parent Item 1'); + userEvent.click(parentItem1); + + expect(parentOnAction).to.have.been.calledOnceWithExactly(1); + expect(childOnAction).to.not.have.been.called; + expect(() => screen.getByTestId('test-menu')).to.throw; + }); }); }); }); From a24e89cf1206972ac7d4a799e8837d21ae10250a Mon Sep 17 00:00:00 2001 From: gagik Date: Wed, 21 May 2025 17:07:53 +0200 Subject: [PATCH 28/76] refactor: minor stylistic changes --- .../src/context-menu-provider.tsx | 3 +- .../src/use-context-menu.tsx | 53 +++++++++++-------- 2 files changed, 32 insertions(+), 24 deletions(-) diff --git a/packages/compass-context-menu/src/context-menu-provider.tsx b/packages/compass-context-menu/src/context-menu-provider.tsx index 499d9c273c1..43d9e893367 100644 --- a/packages/compass-context-menu/src/context-menu-provider.tsx +++ b/packages/compass-context-menu/src/context-menu-provider.tsx @@ -35,13 +35,14 @@ export function ContextMenuProvider({ }, }); } - document.addEventListener('contextmenu', handleContextMenu); function handleClosingEvent(event: Event) { if (!event.defaultPrevented) { setMenu({ isOpen: false }); } } + + document.addEventListener('contextmenu', handleContextMenu); document.addEventListener('click', handleClosingEvent); window.addEventListener('resize', handleClosingEvent); diff --git a/packages/compass-context-menu/src/use-context-menu.tsx b/packages/compass-context-menu/src/use-context-menu.tsx index 37993b04f15..91f51c2f849 100644 --- a/packages/compass-context-menu/src/use-context-menu.tsx +++ b/packages/compass-context-menu/src/use-context-menu.tsx @@ -3,16 +3,25 @@ import { Context } from './context-menu-provider'; import { appendContextMenuContent } from './context-menu-content'; import type { MenuItem } from './types'; -/** - * @returns an object with methods to {@link register} content for the menu and {@link close} the menu - */ +export type ContextMenuMethods = { + /** + * Close the context menu. + */ + close: () => void; + /** + * Register the menu items for the context menu. + * @returns a callback ref to be passed onto the element responsible for triggering the menu. + */ + registerItems: (items: T[]) => (trigger: HTMLElement | null) => void; +}; + export function useContextMenu({ Menu, }: { Menu: React.ComponentType<{ items: T[]; }>; -}) { +}): ContextMenuMethods { // Get the close function from the ContextProvider const context = useContext(Context); const previous = useRef void]>( @@ -24,31 +33,29 @@ export function useContextMenu({ throw new Error('useContextMenu called outside of the provider'); } + const register = (content: React.ComponentType) => { + function listener(event: MouseEvent) { + appendContextMenuContent(event, content); + } + return (trigger: HTMLElement | null) => { + if (previous.current) { + const [previousTrigger, previousListener] = previous.current; + previousTrigger.removeEventListener('contextmenu', previousListener); + } + if (trigger) { + trigger.addEventListener('contextmenu', listener); + previous.current = [trigger, listener]; + } + }; + }; + return { close: context.close.bind(context), /** * @returns a callback ref, passed onto the element responsible for triggering the menu. */ - register(content: React.ComponentType) { - function listener(event: MouseEvent) { - appendContextMenuContent(event, content); - } - return (trigger: HTMLElement | null) => { - if (previous.current) { - const [previousTrigger, previousListener] = previous.current; - previousTrigger.removeEventListener( - 'contextmenu', - previousListener - ); - } - if (trigger) { - trigger.addEventListener('contextmenu', listener); - previous.current = [trigger, listener]; - } - }; - }, registerItems(items: T[]) { - return this.register(() => ); + return register(() => ); }, }; }, [context, Menu]); From f3869eac0cab0fa2e0ddd1dd494cc418109b5bf0 Mon Sep 17 00:00:00 2001 From: gagik Date: Thu, 22 May 2025 11:59:55 +0200 Subject: [PATCH 29/76] fix: export types and rename MenuItem --- packages/compass-context-menu/src/index.ts | 2 ++ packages/compass-context-menu/src/types.ts | 2 +- .../compass-context-menu/src/use-context-menu.spec.tsx | 10 +++++----- packages/compass-context-menu/src/use-context-menu.tsx | 6 +++--- 4 files changed, 11 insertions(+), 9 deletions(-) create mode 100644 packages/compass-context-menu/src/index.ts diff --git a/packages/compass-context-menu/src/index.ts b/packages/compass-context-menu/src/index.ts new file mode 100644 index 00000000000..d60a97e04b3 --- /dev/null +++ b/packages/compass-context-menu/src/index.ts @@ -0,0 +1,2 @@ +export { useContextMenu } from './use-context-menu'; +export type { ContextMenuItem } from './types'; diff --git a/packages/compass-context-menu/src/types.ts b/packages/compass-context-menu/src/types.ts index e9ac549ba63..f453930dcb3 100644 --- a/packages/compass-context-menu/src/types.ts +++ b/packages/compass-context-menu/src/types.ts @@ -15,7 +15,7 @@ export type ContextMenuContext = { close(): void; }; -export type MenuItem = { +export type ContextMenuItem = { label: string; onAction: (event: React.KeyboardEvent | React.MouseEvent) => void; }; diff --git a/packages/compass-context-menu/src/use-context-menu.spec.tsx b/packages/compass-context-menu/src/use-context-menu.spec.tsx index 1d099272104..eb857db9aeb 100644 --- a/packages/compass-context-menu/src/use-context-menu.spec.tsx +++ b/packages/compass-context-menu/src/use-context-menu.spec.tsx @@ -4,10 +4,10 @@ import { expect } from 'chai'; import sinon from 'sinon'; import { useContextMenu } from './use-context-menu'; import { ContextMenuProvider } from './context-menu-provider'; -import type { MenuItem } from './types'; +import type { ContextMenuItem } from './types'; describe('useContextMenu', function () { - const TestMenu: React.FC<{ items: MenuItem[] }> = ({ items }) => ( + const TestMenu: React.FC<{ items: ContextMenuItem[] }> = ({ items }) => (
{items.map((item, idx) => (
void; }) => { const contextMenu = useContextMenu({ Menu: TestMenu }); - const items: MenuItem[] = [ + const items: ContextMenuItem[] = [ { label: 'Test Item', onAction: () => onAction?.(1), @@ -64,7 +64,7 @@ describe('useContextMenu', function () { children?: React.ReactNode; }) => { const contextMenu = useContextMenu({ Menu: TestMenu }); - const parentItems: MenuItem[] = [ + const parentItems: ContextMenuItem[] = [ { label: 'Parent Item 1', onAction: () => onAction?.(1), @@ -90,7 +90,7 @@ describe('useContextMenu', function () { onAction?: (id: number) => void; }) => { const contextMenu = useContextMenu({ Menu: TestMenu }); - const childItems: MenuItem[] = [ + const childItems: ContextMenuItem[] = [ { label: 'Child Item 1', onAction: () => onAction?.(1), diff --git a/packages/compass-context-menu/src/use-context-menu.tsx b/packages/compass-context-menu/src/use-context-menu.tsx index 91f51c2f849..a0c97dead9b 100644 --- a/packages/compass-context-menu/src/use-context-menu.tsx +++ b/packages/compass-context-menu/src/use-context-menu.tsx @@ -1,9 +1,9 @@ import React, { useContext, useMemo, useRef } from 'react'; import { Context } from './context-menu-provider'; import { appendContextMenuContent } from './context-menu-content'; -import type { MenuItem } from './types'; +import type { ContextMenuItem } from './types'; -export type ContextMenuMethods = { +export type ContextMenuMethods = { /** * Close the context menu. */ @@ -15,7 +15,7 @@ export type ContextMenuMethods = { registerItems: (items: T[]) => (trigger: HTMLElement | null) => void; }; -export function useContextMenu({ +export function useContextMenu({ Menu, }: { Menu: React.ComponentType<{ From c2d9ac141d35f3b6b945d71beb79795072989d79 Mon Sep 17 00:00:00 2001 From: gagik Date: Thu, 22 May 2025 12:14:37 +0200 Subject: [PATCH 30/76] fix: use React.RefCallback --- packages/compass-context-menu/src/use-context-menu.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/compass-context-menu/src/use-context-menu.tsx b/packages/compass-context-menu/src/use-context-menu.tsx index a0c97dead9b..fa9e20e7537 100644 --- a/packages/compass-context-menu/src/use-context-menu.tsx +++ b/packages/compass-context-menu/src/use-context-menu.tsx @@ -12,7 +12,7 @@ export type ContextMenuMethods = { * Register the menu items for the context menu. * @returns a callback ref to be passed onto the element responsible for triggering the menu. */ - registerItems: (items: T[]) => (trigger: HTMLElement | null) => void; + registerItems: (items: T[]) => React.RefCallback; }; export function useContextMenu({ @@ -33,7 +33,9 @@ export function useContextMenu({ throw new Error('useContextMenu called outside of the provider'); } - const register = (content: React.ComponentType) => { + const register = ( + content: React.ComponentType + ): React.RefCallback => { function listener(event: MouseEvent) { appendContextMenuContent(event, content); } From 1c6ef03024612cf493f60fe3a124748a39e8dd47 Mon Sep 17 00:00:00 2001 From: gagik Date: Fri, 23 May 2025 13:50:50 +0200 Subject: [PATCH 31/76] refactor: use item groups instead of React elements, use wrapper, keep menu state consistent The refactor is meant to make the Leafygreen integration more straightforward: 1. We stick to item groups and instead have a single wrapper to handle any rendering differences between groups. This allows the wrapper to always have context of all items when rendering which is useful when inserting menu seperators in Leafygreen. Also encourages consistent UI (while allowing per-case customization if needed at wrapper-level). We could introduce itemWrappers instead of itemGroups but having one wrapper handling all seems cleaner to me. 2. More of the responsibility is moved to a parent wrapper component that will house the context menu. This allows us to standardize the right click menu and make better use of Leafygreen's menu component including its click handling (which has been removed from the context menu library). 3. Menu state (i.e. position) is now preserved even closed; this is useful for leafygreen's menu to animate in the same position instead of losing the position all together. --- .../src/context-menu-content.ts | 13 ++-- .../src/context-menu-provider.tsx | 57 ++++++++++------- packages/compass-context-menu/src/index.ts | 7 ++- packages/compass-context-menu/src/types.ts | 29 +++++---- .../src/use-context-menu.spec.tsx | 61 ++++++++++--------- .../src/use-context-menu.tsx | 59 +++++++++--------- 6 files changed, 124 insertions(+), 102 deletions(-) diff --git a/packages/compass-context-menu/src/context-menu-content.ts b/packages/compass-context-menu/src/context-menu-content.ts index 6856f1fe684..c301983a679 100644 --- a/packages/compass-context-menu/src/context-menu-content.ts +++ b/packages/compass-context-menu/src/context-menu-content.ts @@ -1,23 +1,24 @@ +import type { ContextMenuItemGroup } from './types'; + const CONTEXT_MENUS_SYMBOL = Symbol('context_menus'); export type EnhancedMouseEvent = MouseEvent & { - [CONTEXT_MENUS_SYMBOL]?: React.ComponentType[]; + [CONTEXT_MENUS_SYMBOL]?: ContextMenuItemGroup[]; }; export function getContextMenuContent( event: EnhancedMouseEvent -): React.ComponentType[] { +): ContextMenuItemGroup[] { return event[CONTEXT_MENUS_SYMBOL] ?? []; } export function appendContextMenuContent( event: EnhancedMouseEvent, - content: React.ComponentType + content: ContextMenuItemGroup ) { // Initialize if not already patched - if (event[CONTEXT_MENUS_SYMBOL] === undefined) { - event[CONTEXT_MENUS_SYMBOL] = [content]; - return; + if (!event[CONTEXT_MENUS_SYMBOL]) { + event[CONTEXT_MENUS_SYMBOL] = []; } event[CONTEXT_MENUS_SYMBOL].push(content); } diff --git a/packages/compass-context-menu/src/context-menu-provider.tsx b/packages/compass-context-menu/src/context-menu-provider.tsx index 43d9e893367..5dd1cba2cff 100644 --- a/packages/compass-context-menu/src/context-menu-provider.tsx +++ b/packages/compass-context-menu/src/context-menu-provider.tsx @@ -5,8 +5,7 @@ import React, { useMemo, createContext, } from 'react'; -import type { ContextMenuContext, MenuState } from './types'; -import { ContextMenu } from './context-menu'; +import type { ContextMenuContext, ContextMenuState } from './types'; import type { EnhancedMouseEvent } from './context-menu-content'; import { getContextMenuContent } from './context-menu-content'; @@ -14,20 +13,42 @@ export const Context = createContext(null); export function ContextMenuProvider({ children, + wrapper, }: { children: React.ReactNode; + wrapper: React.ComponentType<{ + menu: ContextMenuState & { close: () => void }; + }>; }) { - const [menu, setMenu] = useState({ isOpen: false }); - const close = useCallback(() => setMenu({ isOpen: false }), [setMenu]); + const [menu, setMenu] = useState({ + isOpen: false, + itemGroups: [], + position: { x: 0, y: 0 }, + }); + const close = useCallback(() => setMenu({ ...menu, isOpen: false }), [menu]); + + const handleClosingEvent = useCallback( + (event: Event) => { + if (!event.defaultPrevented) { + setMenu({ ...menu, isOpen: false }); + } + }, + [menu] + ); useEffect(() => { function handleContextMenu(event: MouseEvent) { event.preventDefault(); + + const itemGroups = getContextMenuContent(event as EnhancedMouseEvent); + + if (itemGroups.length === 0) { + return; + } + setMenu({ isOpen: true, - children: getContextMenuContent(event as EnhancedMouseEvent).map( - (Content, index) => - ), + itemGroups, position: { // TODO: Fix handling offset while scrolling x: event.clientX, @@ -36,22 +57,14 @@ export function ContextMenuProvider({ }); } - function handleClosingEvent(event: Event) { - if (!event.defaultPrevented) { - setMenu({ isOpen: false }); - } - } - document.addEventListener('contextmenu', handleContextMenu); - document.addEventListener('click', handleClosingEvent); window.addEventListener('resize', handleClosingEvent); return () => { document.removeEventListener('contextmenu', handleContextMenu); - document.removeEventListener('click', handleClosingEvent); window.removeEventListener('resize', handleClosingEvent); }; - }, [setMenu]); + }, [handleClosingEvent]); const value = useMemo( () => ({ @@ -60,12 +73,12 @@ export function ContextMenuProvider({ [close] ); + const Wrapper = wrapper ?? React.Fragment; + return ( - <> - {children} - {menu.isOpen && ( - {menu.children} - )} - + + {children} + + ); } diff --git a/packages/compass-context-menu/src/index.ts b/packages/compass-context-menu/src/index.ts index d60a97e04b3..75d933ef767 100644 --- a/packages/compass-context-menu/src/index.ts +++ b/packages/compass-context-menu/src/index.ts @@ -1,2 +1,7 @@ export { useContextMenu } from './use-context-menu'; -export type { ContextMenuItem } from './types'; +export { ContextMenuProvider } from './context-menu-provider'; +export type { + ContextMenuItem, + ContextMenuItemGroup, + ContextMenuWrapperProps, +} from './types'; diff --git a/packages/compass-context-menu/src/types.ts b/packages/compass-context-menu/src/types.ts index f453930dcb3..91e8d65cdcc 100644 --- a/packages/compass-context-menu/src/types.ts +++ b/packages/compass-context-menu/src/types.ts @@ -1,15 +1,20 @@ -export type MenuState = - | { - isOpen: false; - } - | { - isOpen: true; - children: React.ReactNode; - position: { - x: number; - y: number; - }; - }; +export interface ContextMenuItemGroup { + items: ContextMenuItem[]; + originListener: (event: MouseEvent) => void; +} + +export type ContextMenuState = { + isOpen: boolean; + itemGroups: ContextMenuItemGroup[]; + position: { + x: number; + y: number; + }; +}; + +export type ContextMenuWrapperProps = { + menu: ContextMenuState & { close: () => void }; +}; export type ContextMenuContext = { close(): void; diff --git a/packages/compass-context-menu/src/use-context-menu.spec.tsx b/packages/compass-context-menu/src/use-context-menu.spec.tsx index eb857db9aeb..cfe0bfc7ef4 100644 --- a/packages/compass-context-menu/src/use-context-menu.spec.tsx +++ b/packages/compass-context-menu/src/use-context-menu.spec.tsx @@ -4,27 +4,29 @@ import { expect } from 'chai'; import sinon from 'sinon'; import { useContextMenu } from './use-context-menu'; import { ContextMenuProvider } from './context-menu-provider'; -import type { ContextMenuItem } from './types'; +import type { ContextMenuItem, ContextMenuWrapperProps } from './types'; describe('useContextMenu', function () { - const TestMenu: React.FC<{ items: ContextMenuItem[] }> = ({ items }) => ( + const TestMenu: React.FC = ({ menu }) => (
- {items.map((item, idx) => ( -
item.onAction?.(event)} - onKeyDown={(event) => { - if (event.key === 'Enter') { - item.onAction?.(event); - } - }} - > - {item.label} -
- ))} + {menu.itemGroups.flatMap((group, groupIdx) => + group.items.map((item, idx) => ( +
item.onAction?.(event)} + onKeyDown={(event) => { + if (event.key === 'Enter') { + item.onAction?.(event); + } + }} + > + {item.label} +
+ )) + )}
); @@ -33,9 +35,9 @@ describe('useContextMenu', function () { onAction, }: { onRegister?: (ref: any) => void; - onAction?: (id) => void; + onAction?: (id: number) => void; }) => { - const contextMenu = useContextMenu({ Menu: TestMenu }); + const contextMenu = useContextMenu(); const items: ContextMenuItem[] = [ { label: 'Test Item', @@ -55,7 +57,6 @@ describe('useContextMenu', function () { ); }; - // Add new test components for nested context menu scenario const ParentComponent = ({ onAction, children, @@ -63,7 +64,7 @@ describe('useContextMenu', function () { onAction?: (id: number) => void; children?: React.ReactNode; }) => { - const contextMenu = useContextMenu({ Menu: TestMenu }); + const contextMenu = useContextMenu(); const parentItems: ContextMenuItem[] = [ { label: 'Parent Item 1', @@ -89,7 +90,7 @@ describe('useContextMenu', function () { }: { onAction?: (id: number) => void; }) => { - const contextMenu = useContextMenu({ Menu: TestMenu }); + const contextMenu = useContextMenu(); const childItems: ContextMenuItem[] = [ { label: 'Child Item 1', @@ -135,7 +136,7 @@ describe('useContextMenu', function () { it('renders without error', function () { render( - + ); @@ -147,7 +148,7 @@ describe('useContextMenu', function () { const onRegister = sinon.spy(); render( - + ); @@ -158,7 +159,7 @@ describe('useContextMenu', function () { it('shows context menu on right click', function () { render( - + ); @@ -173,7 +174,7 @@ describe('useContextMenu', function () { describe('with nested context menus', function () { it('shows only parent items when right clicking parent area', function () { render( - + ); @@ -192,7 +193,7 @@ describe('useContextMenu', function () { it('shows both parent and child items when right clicking child area', function () { render( - + @@ -214,7 +215,7 @@ describe('useContextMenu', function () { const childOnAction = sinon.spy(); render( - + @@ -237,7 +238,7 @@ describe('useContextMenu', function () { const childOnAction = sinon.spy(); render( - + diff --git a/packages/compass-context-menu/src/use-context-menu.tsx b/packages/compass-context-menu/src/use-context-menu.tsx index fa9e20e7537..a60aeba4c69 100644 --- a/packages/compass-context-menu/src/use-context-menu.tsx +++ b/packages/compass-context-menu/src/use-context-menu.tsx @@ -1,4 +1,5 @@ -import React, { useContext, useMemo, useRef } from 'react'; +import type { RefCallback } from 'react'; +import { useContext, useMemo, useRef } from 'react'; import { Context } from './context-menu-provider'; import { appendContextMenuContent } from './context-menu-content'; import type { ContextMenuItem } from './types'; @@ -12,17 +13,12 @@ export type ContextMenuMethods = { * Register the menu items for the context menu. * @returns a callback ref to be passed onto the element responsible for triggering the menu. */ - registerItems: (items: T[]) => React.RefCallback; + registerItems: (items: T[]) => RefCallback; }; -export function useContextMenu({ - Menu, -}: { - Menu: React.ComponentType<{ - items: T[]; - }>; -}): ContextMenuMethods { - // Get the close function from the ContextProvider +export function useContextMenu< + T extends ContextMenuItem = ContextMenuItem +>(): ContextMenuMethods { const context = useContext(Context); const previous = useRef void]>( null @@ -33,32 +29,33 @@ export function useContextMenu({ throw new Error('useContextMenu called outside of the provider'); } - const register = ( - content: React.ComponentType - ): React.RefCallback => { - function listener(event: MouseEvent) { - appendContextMenuContent(event, content); - } - return (trigger: HTMLElement | null) => { - if (previous.current) { - const [previousTrigger, previousListener] = previous.current; - previousTrigger.removeEventListener('contextmenu', previousListener); - } - if (trigger) { - trigger.addEventListener('contextmenu', listener); - previous.current = [trigger, listener]; - } - }; - }; - return { close: context.close.bind(context), /** * @returns a callback ref, passed onto the element responsible for triggering the menu. */ - registerItems(items: T[]) { - return register(() => ); + registerItems(items: ContextMenuItem[]) { + function listener(event: MouseEvent): void { + appendContextMenuContent(event, { + items, + originListener: listener, + }); + } + + return (trigger: HTMLElement | null) => { + if (previous.current) { + const [previousTrigger, previousListener] = previous.current; + previousTrigger.removeEventListener( + 'contextmenu', + previousListener + ); + } + if (trigger) { + trigger.addEventListener('contextmenu', listener); + previous.current = [trigger, listener]; + } + }; }, }; - }, [context, Menu]); + }, [context]); } From 3d14d6df00a8c1909853f878bf086fb6bcda7323 Mon Sep 17 00:00:00 2001 From: gagik Date: Fri, 23 May 2025 17:12:31 +0200 Subject: [PATCH 32/76] fix: delete redundant context menu --- .../compass-context-menu/src/context-menu.tsx | 22 ------------------- 1 file changed, 22 deletions(-) delete mode 100644 packages/compass-context-menu/src/context-menu.tsx diff --git a/packages/compass-context-menu/src/context-menu.tsx b/packages/compass-context-menu/src/context-menu.tsx deleted file mode 100644 index b053bc4963a..00000000000 --- a/packages/compass-context-menu/src/context-menu.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { createPortal } from 'react-dom'; -import React from 'react'; - -type ContextMenuProps = React.PropsWithChildren<{ - position: { - x: number; - y: number; - }; -}>; - -export function ContextMenu({ children, position }: ContextMenuProps) { - const container = document.getElementById('context-menu-container'); - if (container === null) { - throw new Error('Expected a container for the context menu in the DOM'); - } - return createPortal( -
- {children} -
, - container - ); -} From ee658e1f394f49713edb63647193dc0641bcaaeb Mon Sep 17 00:00:00 2001 From: gagik Date: Mon, 2 Jun 2025 13:51:48 +0200 Subject: [PATCH 33/76] fix: remove unused dep --- package-lock.json | 4 ++-- packages/compass-context-menu/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index a205c4ec913..57ab1929a9d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43216,11 +43216,11 @@ "@mongodb-js/eslint-config-compass": "^1.3.8", "@mongodb-js/mocha-config-compass": "^1.6.8", "@mongodb-js/prettier-config-compass": "^1.2.8", + "@mongodb-js/testing-library-compass": "^1.3.1", "@mongodb-js/tsconfig-compass": "^1.2.8", "@types/chai": "^4.2.21", "@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", @@ -55712,11 +55712,11 @@ "@mongodb-js/eslint-config-compass": "^1.3.8", "@mongodb-js/mocha-config-compass": "^1.6.8", "@mongodb-js/prettier-config-compass": "^1.2.8", + "@mongodb-js/testing-library-compass": "^1.3.1", "@mongodb-js/tsconfig-compass": "^1.2.8", "@types/chai": "^4.2.21", "@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", diff --git a/packages/compass-context-menu/package.json b/packages/compass-context-menu/package.json index 3cf186e0270..67099115f92 100644 --- a/packages/compass-context-menu/package.json +++ b/packages/compass-context-menu/package.json @@ -55,11 +55,11 @@ "@mongodb-js/eslint-config-compass": "^1.3.8", "@mongodb-js/mocha-config-compass": "^1.6.8", "@mongodb-js/prettier-config-compass": "^1.2.8", + "@mongodb-js/testing-library-compass": "^1.3.1", "@mongodb-js/tsconfig-compass": "^1.2.8", "@types/chai": "^4.2.21", "@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", From 59d82d6365740cb48029b607d077d8a48687f48a Mon Sep 17 00:00:00 2001 From: gagik Date: Fri, 6 Jun 2025 10:06:20 +0200 Subject: [PATCH 34/76] test: compas crud --- package-lock.json | 169 +++++++++++++- .../src/components/context-menu.tsx | 2 +- .../components/document-list/element.spec.tsx | 126 +++++++++++ .../src/components/document-list/element.tsx | 37 +++ packages/compass-crud/package.json | 2 +- .../components/document-json-view-item.tsx | 86 +++++++ .../document-list-view-item.spec.tsx | 213 ++++++++++++++++++ .../components/document-list-view-item.tsx | 100 ++++++++ .../components/document-list-view.spec.tsx | 10 +- .../virtualized-document-json-view.tsx | 44 ++-- .../virtualized-document-list-view.spec.tsx | 27 ++- .../virtualized-document-list-view.tsx | 37 +-- 12 files changed, 794 insertions(+), 59 deletions(-) create mode 100644 packages/compass-components/src/components/document-list/element.spec.tsx create mode 100644 packages/compass-crud/src/components/document-json-view-item.tsx create mode 100644 packages/compass-crud/src/components/document-list-view-item.spec.tsx create mode 100644 packages/compass-crud/src/components/document-list-view-item.tsx diff --git a/package-lock.json b/package-lock.json index 57ab1929a9d..24151b8e443 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43318,7 +43318,7 @@ "mongodb-instance-model": "^12.31.0", "nyc": "^15.1.0", "react-dom": "^17.0.2", - "sinon": "^8.1.1", + "sinon": "^17.0.1", "typescript": "^5.0.4" } }, @@ -43334,6 +43334,55 @@ "bson": "^4.6.3 || ^5 || ^6" } }, + "packages/compass-crud/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, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "packages/compass-crud/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, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, + "packages/compass-crud/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, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1", + "lodash.get": "^4.4.2", + "type-detect": "^4.1.0" + } + }, + "packages/compass-crud/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, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "packages/compass-crud/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, + "license": "MIT" + }, "packages/compass-crud/node_modules/mongodb-query-parser": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/mongodb-query-parser/-/mongodb-query-parser-4.3.0.tgz", @@ -43349,6 +43398,20 @@ "bson": "^4.6.3 || ^5 || ^6" } }, + "packages/compass-crud/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, + "license": "BSD-3-Clause", + "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-crud/node_modules/numeral": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/numeral/-/numeral-2.0.6.tgz", @@ -43357,6 +43420,32 @@ "node": "*" } }, + "packages/compass-crud/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, + "license": "MIT" + }, + "packages/compass-crud/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, + "license": "BSD-3-Clause", + "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-data-modeling": { "name": "@mongodb-js/compass-data-modeling", "version": "1.8.0", @@ -55803,7 +55892,7 @@ "react-dom": "^17.0.2", "reflux": "^0.4.1", "semver": "^7.6.2", - "sinon": "^8.1.1", + "sinon": "^17.0.1", "typescript": "^5.0.4" }, "dependencies": { @@ -55815,6 +55904,49 @@ "acorn": "^8.1.0" } }, + "@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 + }, "mongodb-query-parser": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/mongodb-query-parser/-/mongodb-query-parser-4.3.0.tgz", @@ -55826,10 +55958,43 @@ "lodash": "^4.17.21" } }, + "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" + } + }, "numeral": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/numeral/-/numeral-2.0.6.tgz", "integrity": "sha512-qaKRmtYPZ5qdw4jWJD6bxEf1FJEqllJrwxCLIm0sQU/A7v2/czigzOb+C2uSiFsa9lBUzeH7M1oK+Q+OLxL3kA==" + }, + "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" + } } } }, diff --git a/packages/compass-components/src/components/context-menu.tsx b/packages/compass-components/src/components/context-menu.tsx index c78519e399c..288db7393a5 100644 --- a/packages/compass-components/src/components/context-menu.tsx +++ b/packages/compass-components/src/components/context-menu.tsx @@ -28,7 +28,7 @@ export function ContextMenu({ menu }: ContextMenuWrapperProps) { if (!menu.isOpen) { menu.close(); } - }, [menu, menu.isOpen]); + }, [menu.isOpen]); return (
{ + return render({element}); + }; + + it('copies field and value when "Copy field & value" is clicked', function () { + renderWithContextMenu( + {}} + /> + ); + + // Open context menu and click the copy option + const elementNode = screen.getByTestId('hadron-document-element'); + userEvent.click(elementNode, { button: 2 }); + userEvent.click(screen.getByText('Copy field & value'), undefined, { + skipPointerEventsCheck: true, + }); + + expect(clipboardWriteTextStub).to.have.been.calledWith('field: "value"'); + }); + + it('shows "Open URL in browser" for URL string values', function () { + const urlDoc = new HadronDocument({ link: 'https://mongodb.com' }); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const urlElement = urlDoc.elements.at(0)!; + + renderWithContextMenu( + {}} + /> + ); + + // Open context menu + const elementNode = screen.getByTestId('hadron-document-element'); + userEvent.click(elementNode, { button: 2 }); + + // Check if the menu item exists + expect(screen.getByText('Open URL in browser')).to.exist; + }); + + it('opens URL in new tab when "Open URL in browser" is clicked', function () { + const urlDoc = new HadronDocument({ link: 'https://mongodb.com' }); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const urlElement = urlDoc.elements.at(0)!; + + renderWithContextMenu( + {}} + /> + ); + + // Open context menu and click the open URL option + const elementNode = screen.getByTestId('hadron-document-element'); + userEvent.click(elementNode, { button: 2 }); + userEvent.click(screen.getByText('Open URL in browser'), undefined, { + skipPointerEventsCheck: true, + }); + + expect(windowOpenStub).to.have.been.calledWith( + 'https://mongodb.com', + '_blank', + 'noopener' + ); + }); + + it('does not show "Open URL in browser" for non-URL string values', function () { + renderWithContextMenu( + {}} + /> + ); + + // Open context menu + const elementNode = screen.getByTestId('hadron-document-element'); + userEvent.click(elementNode, { button: 2 }); + + // Check that the menu item doesn't exist + expect(screen.queryByText('Open URL in browser')).to.not.exist; + }); + }); +}); diff --git a/packages/compass-components/src/components/document-list/element.tsx b/packages/compass-components/src/components/document-list/element.tsx index f41a90a0bf3..39ca9cfd86a 100644 --- a/packages/compass-components/src/components/document-list/element.tsx +++ b/packages/compass-components/src/components/document-list/element.tsx @@ -15,6 +15,7 @@ import { ElementEvents, ElementEditor, DEFAULT_VISIBLE_ELEMENTS, + objectToIdiomaticEJSON, } from 'hadron-document'; import BSONValue from '../bson-value'; import { spacing } from '@leafygreen-ui/tokens'; @@ -28,6 +29,7 @@ import { palette } from '@leafygreen-ui/palette'; import { Icon } from '../leafygreen'; import { useDarkMode } from '../../hooks/use-theme'; import VisibleFieldsToggle from './visible-field-toggle'; +import { useContextMenuItems } from '../context-menu'; function getEditorByType(type: HadronElementType['type']) { switch (type) { @@ -409,6 +411,16 @@ export const calculateShowMoreToggleOffset = ({ return spacerWidth + editableOffset + expandIconSize; }; +// Helper function to check if a string is a URL +const isValidUrl = (str: string): boolean => { + try { + const url = new URL(str); + return url.protocol === 'http:' || url.protocol === 'https:'; + } catch { + return false; + } +}; + export const HadronElement: React.FunctionComponent<{ value: HadronElementType; editable: boolean; @@ -447,6 +459,29 @@ export const HadronElement: React.FunctionComponent<{ collapse, } = useHadronElement(element); + // Add context menu hook for the field + const fieldContextMenuRef = useContextMenuItems([ + { + label: 'Copy field & value', + onAction: () => { + const fieldStr = `${key.value}: ${objectToIdiomaticEJSON( + value.originalValue + )}`; + void navigator.clipboard.writeText(fieldStr); + }, + }, + ...(type.value === 'String' && isValidUrl(value.value) + ? [ + { + label: 'Open URL in browser', + onAction: () => { + window.open(value.value, '_blank', 'noopener'); + }, + }, + ] + : []), + ]); + const toggleExpanded = () => { expanded ? collapse() : expand(); }; @@ -489,6 +524,7 @@ export const HadronElement: React.FunctionComponent<{ : elementInvalidLightMode; const elementProps = { + ref: fieldContextMenuRef, className: cx( hadronElement, darkMode ? hadronElementDarkMode : hadronElementLightMode, @@ -527,6 +563,7 @@ export const HadronElement: React.FunctionComponent<{ data-field={key.value} data-id={element.uuid} {...elementProps} + ref={fieldContextMenuRef} > {editable && (
diff --git a/packages/compass-crud/package.json b/packages/compass-crud/package.json index 70194456a54..d5ee9a25892 100644 --- a/packages/compass-crud/package.json +++ b/packages/compass-crud/package.json @@ -66,7 +66,7 @@ "mongodb-instance-model": "^12.31.0", "nyc": "^15.1.0", "react-dom": "^17.0.2", - "sinon": "^8.1.1", + "sinon": "^17.0.1", "typescript": "^5.0.4" }, "dependencies": { diff --git a/packages/compass-crud/src/components/document-json-view-item.tsx b/packages/compass-crud/src/components/document-json-view-item.tsx new file mode 100644 index 00000000000..f368b476f09 --- /dev/null +++ b/packages/compass-crud/src/components/document-json-view-item.tsx @@ -0,0 +1,86 @@ +import React from 'react'; +import type HadronDocument from 'hadron-document'; +import { css, KeylineCard, spacing } from '@mongodb-js/compass-components'; + +import JSONEditor, { type JSONEditorProps } from './json-editor'; +import { useContextMenuItems } from '@mongodb-js/compass-components'; + +const keylineCardStyles = css({ + overflow: 'hidden', + position: 'relative', +}); + +export type DocumentJsonViewItemProps = { + doc: HadronDocument; + docRef: React.Ref; + docIndex: number; + namespace: string; + isEditable: boolean; + isTimeSeries?: boolean; + scrollTriggerRef?: React.Ref; +} & Pick< + JSONEditorProps, + | 'copyToClipboard' + | 'removeDocument' + | 'replaceDocument' + | 'updateDocument' + | 'openInsertDocumentDialog' +>; + +const DocumentJsonViewItem: React.FC = ({ + doc, + docRef, + docIndex, + namespace, + isEditable, + isTimeSeries, + scrollTriggerRef, + copyToClipboard, + removeDocument, + replaceDocument, + updateDocument, + openInsertDocumentDialog, +}) => { + const ref = useContextMenuItems([ + { + label: 'Update document', + onAction: () => { + updateDocument?.(doc); + }, + }, + { + label: 'Copy document', + onAction: () => { + copyToClipboard?.(doc); + }, + }, + { + label: 'Delete document', + onAction: () => { + removeDocument?.(doc); + }, + }, + ]); + + return ( +
+ + {scrollTriggerRef && docIndex === 0 &&
} + + +
+ ); +}; + +export { DocumentJsonViewItem }; diff --git a/packages/compass-crud/src/components/document-list-view-item.spec.tsx b/packages/compass-crud/src/components/document-list-view-item.spec.tsx new file mode 100644 index 00000000000..e00046b478e --- /dev/null +++ b/packages/compass-crud/src/components/document-list-view-item.spec.tsx @@ -0,0 +1,213 @@ +import React from 'react'; +import { render, screen, userEvent } from '@mongodb-js/testing-library-compass'; +import { expect } from 'chai'; +import sinon from 'sinon'; +import HadronDocument from 'hadron-document'; +import { DocumentListViewItem } from './document-list-view-item'; +import { ContextMenuProvider } from '@mongodb-js/compass-components'; + +describe('DocumentListViewItem', function () { + describe('document context menu', function () { + let doc: HadronDocument; + let copyToClipboardStub: sinon.SinonStub; + let openInsertDocumentDialogStub: sinon.SinonStub; + let collapseStub: sinon.SinonStub; + let expandStub: sinon.SinonStub; + let startEditingStub: sinon.SinonStub; + let markForDeletionStub: sinon.SinonStub; + let generateObjectStub: sinon.SinonStub; + + beforeEach(function () { + doc = new HadronDocument({ + _id: 1, + name: 'test', + url: 'https://mongodb.com', + nested: { field: 'value' }, + }); + + copyToClipboardStub = sinon.stub(); + openInsertDocumentDialogStub = sinon.stub(); + + // Set up document methods as stubs + collapseStub = sinon.stub(doc, 'collapse'); + expandStub = sinon.stub(doc, 'expand'); + startEditingStub = sinon.stub(doc, 'startEditing'); + markForDeletionStub = sinon.stub(doc, 'markForDeletion'); + generateObjectStub = sinon.stub(doc, 'generateObject').returns({ + _id: 1, + name: 'test', + url: 'https://mongodb.com', + nested: { field: 'value' }, + }); + }); + + /** + * Renders the element with the context menu provider and returns a reference to the first child of the container. + */ + const renderWithContextMenu = (doc: HadronDocument): HTMLElement => { + const { container } = render( + + + + ); + return container.firstChild as HTMLElement; + }; + + afterEach(function () { + sinon.restore(); + }); + + it('shows "Expand all fields" when document is collapsed', function () { + doc.expanded = false; + + const container = renderWithContextMenu(doc); + + // Right-click to open context menu + userEvent.click(container, { button: 2 }); + + expect(screen.getByText('Expand all fields')).to.exist; + }); + + it('shows "Collapse all fields" when document is expanded', function () { + doc.expanded = true; + + const container = renderWithContextMenu(doc); + + // Right-click to open context menu + userEvent.click(container, { button: 2 }); + + expect(screen.getByText('Collapse all fields')).to.exist; + }); + + it('expands document when "Expand all fields" is clicked', function () { + doc.expanded = false; + + const container = renderWithContextMenu(doc); + + // Right-click to open context menu + userEvent.click(container, { button: 2 }); + + // Click expand option + userEvent.click(screen.getByText('Expand all fields')); + + expect(expandStub).to.have.been.calledOnce; + }); + + it('collapses document when "Collapse all fields" is clicked', function () { + doc.expanded = true; + + const container = renderWithContextMenu(doc); + + // Right-click to open context menu + userEvent.click(container, { button: 2 }); + + // Click collapse option + userEvent.click(screen.getByText('Collapse all fields'), undefined, { + skipPointerEventsCheck: true, + }); + + expect(collapseStub).to.have.been.calledOnce; + }); + + it('shows "Edit document" when document is not in editing mode', function () { + doc.editing = false; + + const container = renderWithContextMenu(doc); + + // Right-click to open context menu + userEvent.click(container, { button: 2 }); + + expect(screen.getByText('Edit document')).to.exist; + }); + + it('does not show "Edit document" when document is in editing mode', function () { + doc.editing = true; + + const container = renderWithContextMenu(doc); + + // Right-click to open context menu + userEvent.click(container, { button: 2 }); + + expect(screen.queryByText('Edit document')).to.not.exist; + }); + + it('starts editing when "Edit document" is clicked', function () { + doc.editing = false; + + const container = renderWithContextMenu(doc); + + // Right-click to open context menu + userEvent.click(container, { button: 2 }); + + // Click edit option + userEvent.click(screen.getByText('Edit document'), undefined, { + skipPointerEventsCheck: true, + }); + + expect(startEditingStub).to.have.been.calledOnce; + }); + + it('calls copyToClipboard when "Copy document" is clicked', function () { + const container = renderWithContextMenu(doc); + + // Right-click to open context menu + userEvent.click(container, { button: 2 }); + + // Click copy option + userEvent.click(screen.getByText('Copy document'), undefined, { + skipPointerEventsCheck: true, + }); + + expect(copyToClipboardStub).to.have.been.calledWith(doc); + }); + + it('opens insert dialog with cloned document when "Clone document..." is clicked', async function () { + const container = renderWithContextMenu(doc); + + // Right-click to open context menu + userEvent.click(container, { button: 2 }); + + // Click clone option + userEvent.click(screen.getByText('Clone document...'), undefined, { + skipPointerEventsCheck: true, + }); + + await new Promise((resolve) => setTimeout(resolve, 1000)); + + expect(generateObjectStub).to.have.been.calledWith({ + excludeInternalFields: true, + }); + + expect(openInsertDocumentDialogStub).to.have.been.calledWith( + { + _id: 1, + name: 'test', + url: 'https://mongodb.com', + nested: { field: 'value' }, + }, + true + ); + }); + + it('marks document for deletion when "Delete document" is clicked', function () { + const container = renderWithContextMenu(doc); + + // Right-click to open context menu + userEvent.click(container, { button: 2 }); + + // Click delete option + userEvent.click(screen.getByText('Delete document'), undefined, { + skipPointerEventsCheck: true, + }); + + expect(markForDeletionStub).to.have.been.calledOnce; + }); + }); +}); diff --git a/packages/compass-crud/src/components/document-list-view-item.tsx b/packages/compass-crud/src/components/document-list-view-item.tsx new file mode 100644 index 00000000000..493f7bc52c4 --- /dev/null +++ b/packages/compass-crud/src/components/document-list-view-item.tsx @@ -0,0 +1,100 @@ +import React from 'react'; +import type HadronDocument from 'hadron-document'; +import { KeylineCard } from '@mongodb-js/compass-components'; +import Document, { type DocumentProps } from './document'; +import { useContextMenuItems } from '@mongodb-js/compass-components'; + +export type DocumentListViewItemProps = { + doc: HadronDocument; + docRef: React.Ref; + docIndex: number; + isEditable: boolean; + isTimeSeries?: boolean; + scrollTriggerRef?: React.Ref; +} & Pick< + DocumentProps, + | 'copyToClipboard' + | 'removeDocument' + | 'replaceDocument' + | 'updateDocument' + | 'openInsertDocumentDialog' +>; + +const DocumentListViewItem: React.FC = ({ + doc, + docRef, + docIndex, + isEditable, + isTimeSeries, + scrollTriggerRef, + copyToClipboard, + removeDocument, + replaceDocument, + updateDocument, + openInsertDocumentDialog, +}) => { + const ref = useContextMenuItems([ + { + label: doc.expanded ? 'Collapse all fields' : 'Expand all fields', + onAction: () => { + if (doc.expanded) { + doc.collapse(); + } else { + doc.expand(); + } + }, + }, + ...(!doc.editing + ? [ + { + label: 'Edit document', + onAction: () => { + doc.startEditing(); + }, + }, + ] + : []), + { + label: 'Copy document', + onAction: () => { + copyToClipboard?.(doc); + }, + }, + { + label: 'Clone document...', + onAction: () => { + const clonedDoc = doc.generateObject({ + excludeInternalFields: true, + }); + openInsertDocumentDialog?.(clonedDoc, true); + }, + }, + { + label: 'Delete document', + onAction: () => { + doc.markForDeletion(); + }, + }, + ]); + + return ( +
+ + {scrollTriggerRef && docIndex === 0 &&
} + + +
+ ); +}; + +export { DocumentListViewItem }; diff --git a/packages/compass-crud/src/components/document-list-view.spec.tsx b/packages/compass-crud/src/components/document-list-view.spec.tsx index 536f48e66c2..0465df67545 100644 --- a/packages/compass-crud/src/components/document-list-view.spec.tsx +++ b/packages/compass-crud/src/components/document-list-view.spec.tsx @@ -2,9 +2,9 @@ import React from 'react'; import { mount } from 'enzyme'; import HadronDocument from 'hadron-document'; import { expect } from 'chai'; -import sinon from 'sinon'; import DocumentListView from './document-list-view'; +import { ContextMenuProvider } from '@mongodb-js/compass-components'; describe('', function () { describe('#render', function () { @@ -16,12 +16,8 @@ describe('', function () { docs={hadronDocs} isEditable={false} isTimeSeries={false} - copyToClipboard={sinon.spy()} - removeDocument={sinon.spy()} - replaceDocument={sinon.spy()} - updateDocument={sinon.spy()} - openInsertDocumentDialog={sinon.spy()} - /> + />, + { wrappingComponent: ContextMenuProvider } ); it('renders all the documents', function () { diff --git a/packages/compass-crud/src/components/virtualized-document-json-view.tsx b/packages/compass-crud/src/components/virtualized-document-json-view.tsx index 8cffcf8f26f..609bb657836 100644 --- a/packages/compass-crud/src/components/virtualized-document-json-view.tsx +++ b/packages/compass-crud/src/components/virtualized-document-json-view.tsx @@ -2,19 +2,14 @@ import React, { useCallback } from 'react'; import type HadronDocument from 'hadron-document'; import { css, - KeylineCard, spacing, VirtualList, type VirtualListRef, type VirtualListItemRenderer, } from '@mongodb-js/compass-components'; -import JSONEditor, { type JSONEditorProps } from './json-editor'; - -const keylineCardStyles = css({ - overflow: 'hidden', - position: 'relative', -}); +import type { JSONEditorProps } from './json-editor'; +import { DocumentJsonViewItem } from './document-json-view-item'; const spacingStyles = css({ padding: spacing[400], @@ -75,23 +70,26 @@ const VirtualizedDocumentJsonView: React.FC< listRef, }) => { const renderItem: VirtualListItemRenderer = useCallback( - (doc, docRef, docIndex) => { + ( + doc: HadronDocument, + docRef: React.Ref, + docIndex: number + ) => { return ( - - {scrollTriggerRef && docIndex === 0 &&
} - - + ); }, [ diff --git a/packages/compass-crud/src/components/virtualized-document-list-view.spec.tsx b/packages/compass-crud/src/components/virtualized-document-list-view.spec.tsx index a9d291f6479..9f9ecb44e86 100644 --- a/packages/compass-crud/src/components/virtualized-document-list-view.spec.tsx +++ b/packages/compass-crud/src/components/virtualized-document-list-view.spec.tsx @@ -2,14 +2,17 @@ import React from 'react'; import { expect } from 'chai'; import HadronDocument from 'hadron-document'; import { - render, screen, cleanup, within, act, userEvent, + render, } from '@mongodb-js/testing-library-compass'; -import { type VirtualListRef } from '@mongodb-js/compass-components'; +import { + ContextMenuProvider, + type VirtualListRef, +} from '@mongodb-js/compass-components'; import VirtualizedDocumentListView from './virtualized-document-list-view'; @@ -36,12 +39,20 @@ const getDocs = () => [ ]; describe('VirtualizedDocumentListView', function () { + const renderWithContextMenu = (element: React.ReactElement) => { + return render(element, { + wrapper: ContextMenuProvider, + }); + }; + afterEach(function () { cleanup(); }); it('renders the list of provided BSON objects', function () { - render(); + renderWithContextMenu( + + ); expect(screen.getByTitle('1')).to.be.visible; expect(screen.getByTitle('Doc1')).to.be.visible; @@ -50,7 +61,7 @@ describe('VirtualizedDocumentListView', function () { }); it('renders the list of provided HadronDocuments', function () { - render( + renderWithContextMenu( new HadronDocument(doc))} isEditable={false} @@ -64,7 +75,7 @@ describe('VirtualizedDocumentListView', function () { }); it('renders a readonly list when isEditable is false', function () { - render( + renderWithContextMenu( new HadronDocument(doc))} isEditable={false} @@ -75,7 +86,7 @@ describe('VirtualizedDocumentListView', function () { }); it('renders an editable list when isEditable is true', function () { - render( + renderWithContextMenu( new HadronDocument(doc))} isEditable={true} @@ -91,7 +102,7 @@ describe('VirtualizedDocumentListView', function () { (_, idx) => new HadronDocument(createBigDocument(idx)) ); const listRef: VirtualListRef = React.createRef(); - render( + renderWithContextMenu( = useCallback( - (doc, docRef, docIndex) => { + ( + doc: HadronDocument, + docRef: React.Ref, + docIndex: number + ) => { return ( - - {scrollTriggerRef && docIndex === 0 &&
} - - + ); }, [ From 1ad46a7b47029ace184bfe1e8c993011578e934e Mon Sep 17 00:00:00 2001 From: gagik Date: Tue, 10 Jun 2025 17:13:08 +0200 Subject: [PATCH 35/76] fix: enforce no nesting, adjsut enzyme test and move setup to testing-library --- configs/testing-library-compass/src/index.tsx | 77 ++++++++++--------- .../src/context-menu-provider.spec.tsx | 45 +++++++++++ .../src/context-menu-provider.tsx | 18 ++++- .../components/document-list-view.spec.tsx | 25 +++--- 4 files changed, 119 insertions(+), 46 deletions(-) create mode 100644 packages/compass-context-menu/src/context-menu-provider.spec.tsx diff --git a/configs/testing-library-compass/src/index.tsx b/configs/testing-library-compass/src/index.tsx index 6f3bf3bb79d..d01c55bba42 100644 --- a/configs/testing-library-compass/src/index.tsx +++ b/configs/testing-library-compass/src/index.tsx @@ -45,7 +45,10 @@ import { ReadOnlyPreferenceAccess, } from 'compass-preferences-model/provider'; import { TelemetryProvider } from '@mongodb-js/compass-telemetry/provider'; -import { CompassComponentsProvider } from '@mongodb-js/compass-components'; +import { + CompassComponentsProvider, + ContextMenuProvider, +} from '@mongodb-js/compass-components'; import { TestEnvCurrentConnectionContext, ConnectionInfoProvider, @@ -349,41 +352,43 @@ function createWrapper( - - - { - // noop - }) - } - onExtraConnectionDataRequest={ - options.onExtraConnectionDataRequest ?? - (() => { - return Promise.resolve([{}, null] as [any, null]); - }) - } - onAutoconnectInfoRequest={ - options.onAutoconnectInfoRequest - } - preloadStorageConnectionInfos={connections} - > - - - - {children} - - - - - - + + + + { + // noop + }) + } + onExtraConnectionDataRequest={ + options.onExtraConnectionDataRequest ?? + (() => { + return Promise.resolve([{}, null] as [any, null]); + }) + } + onAutoconnectInfoRequest={ + options.onAutoconnectInfoRequest + } + preloadStorageConnectionInfos={connections} + > + + + + {children} + + + + + + + diff --git a/packages/compass-context-menu/src/context-menu-provider.spec.tsx b/packages/compass-context-menu/src/context-menu-provider.spec.tsx new file mode 100644 index 00000000000..7e39fdee800 --- /dev/null +++ b/packages/compass-context-menu/src/context-menu-provider.spec.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { render } from '@mongodb-js/testing-library-compass'; +import { expect } from 'chai'; +import { ContextMenuProvider } from './context-menu-provider'; +import type { ContextMenuWrapperProps } from './types'; + +describe('ContextMenuProvider', function () { + const TestMenu: React.FC = () => ( +
Test Menu
+ ); + + const TestComponent = () => ( +
Test Content
+ ); + + describe('when nested', function () { + it('throws an error when providers are nested', function () { + expect(() => { + render( + +
+ + + +
+
+ ); + }).to.throw( + 'Duplicated ContextMenuProvider found. Please remove the nested provider.' + ); + }); + }); + + describe('when not nested', function () { + it('renders without error', function () { + render( + + + + ); + + expect(document.querySelector('[data-testid="test-content"]')).to.exist; + }); + }); +}); diff --git a/packages/compass-context-menu/src/context-menu-provider.tsx b/packages/compass-context-menu/src/context-menu-provider.tsx index 5dd1cba2cff..0ae7c5f0e06 100644 --- a/packages/compass-context-menu/src/context-menu-provider.tsx +++ b/packages/compass-context-menu/src/context-menu-provider.tsx @@ -4,6 +4,7 @@ import React, { useState, useMemo, createContext, + useContext, } from 'react'; import type { ContextMenuContext, ContextMenuState } from './types'; import type { EnhancedMouseEvent } from './context-menu-content'; @@ -20,6 +21,9 @@ export function ContextMenuProvider({ menu: ContextMenuState & { close: () => void }; }>; }) { + // Check if there's already a parent context menu provider + const parentContext = useContext(Context); + const [menu, setMenu] = useState({ isOpen: false, itemGroups: [], @@ -37,6 +41,11 @@ export function ContextMenuProvider({ ); useEffect(() => { + // If there's a parent provider, don't add event listeners + if (parentContext) { + return; + } + function handleContextMenu(event: MouseEvent) { event.preventDefault(); @@ -64,7 +73,7 @@ export function ContextMenuProvider({ document.removeEventListener('contextmenu', handleContextMenu); window.removeEventListener('resize', handleClosingEvent); }; - }, [handleClosingEvent]); + }, [handleClosingEvent, parentContext]); const value = useMemo( () => ({ @@ -73,6 +82,13 @@ export function ContextMenuProvider({ [close] ); + // Prevent accidental nested providers + if (parentContext) { + throw new Error( + 'Duplicated ContextMenuProvider found. Please remove the nested provider.' + ); + } + const Wrapper = wrapper ?? React.Fragment; return ( diff --git a/packages/compass-crud/src/components/document-list-view.spec.tsx b/packages/compass-crud/src/components/document-list-view.spec.tsx index 0465df67545..01710d92cf5 100644 --- a/packages/compass-crud/src/components/document-list-view.spec.tsx +++ b/packages/compass-crud/src/components/document-list-view.spec.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { mount } from 'enzyme'; +import { ReactWrapper, mount } from 'enzyme'; import HadronDocument from 'hadron-document'; import { expect } from 'chai'; @@ -11,14 +11,21 @@ describe('', function () { context('when the documents have objects for ids', function () { const docs = [{ _id: { name: 'test-1' } }, { _id: { name: 'test-2' } }]; const hadronDocs = docs.map((doc) => new HadronDocument(doc)); - const component = mount( - , - { wrappingComponent: ContextMenuProvider } - ); + let component: ReactWrapper; + beforeEach(function () { + component = mount( + , + { wrappingComponent: ContextMenuProvider } + ); + }); + + afterEach(function () { + component?.unmount(); + }); it('renders all the documents', function () { const wrapper = component.find('[data-testid="readonly-document"]'); From 4730c189e9b0ba768e7b772d4d3ffd7d5bfe5818 Mon Sep 17 00:00:00 2001 From: gagik Date: Tue, 10 Jun 2025 17:15:25 +0200 Subject: [PATCH 36/76] fix: add tests and fix bug with menu auto-closing --- .../src/components/context-menu.tsx | 2 +- .../src/context-menu-provider.spec.tsx | 45 +++++++++++++++++++ .../src/context-menu-provider.tsx | 18 +++++++- 3 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 packages/compass-context-menu/src/context-menu-provider.spec.tsx diff --git a/packages/compass-components/src/components/context-menu.tsx b/packages/compass-components/src/components/context-menu.tsx index c78519e399c..288db7393a5 100644 --- a/packages/compass-components/src/components/context-menu.tsx +++ b/packages/compass-components/src/components/context-menu.tsx @@ -28,7 +28,7 @@ export function ContextMenu({ menu }: ContextMenuWrapperProps) { if (!menu.isOpen) { menu.close(); } - }, [menu, menu.isOpen]); + }, [menu.isOpen]); return (
= () => ( +
Test Menu
+ ); + + const TestComponent = () => ( +
Test Content
+ ); + + describe('when nested', function () { + it('throws an error when providers are nested', function () { + expect(() => { + render( + +
+ + + +
+
+ ); + }).to.throw( + 'Duplicated ContextMenuProvider found. Please remove the nested provider.' + ); + }); + }); + + describe('when not nested', function () { + it('renders without error', function () { + render( + + + + ); + + expect(document.querySelector('[data-testid="test-content"]')).to.exist; + }); + }); +}); diff --git a/packages/compass-context-menu/src/context-menu-provider.tsx b/packages/compass-context-menu/src/context-menu-provider.tsx index 5dd1cba2cff..0ae7c5f0e06 100644 --- a/packages/compass-context-menu/src/context-menu-provider.tsx +++ b/packages/compass-context-menu/src/context-menu-provider.tsx @@ -4,6 +4,7 @@ import React, { useState, useMemo, createContext, + useContext, } from 'react'; import type { ContextMenuContext, ContextMenuState } from './types'; import type { EnhancedMouseEvent } from './context-menu-content'; @@ -20,6 +21,9 @@ export function ContextMenuProvider({ menu: ContextMenuState & { close: () => void }; }>; }) { + // Check if there's already a parent context menu provider + const parentContext = useContext(Context); + const [menu, setMenu] = useState({ isOpen: false, itemGroups: [], @@ -37,6 +41,11 @@ export function ContextMenuProvider({ ); useEffect(() => { + // If there's a parent provider, don't add event listeners + if (parentContext) { + return; + } + function handleContextMenu(event: MouseEvent) { event.preventDefault(); @@ -64,7 +73,7 @@ export function ContextMenuProvider({ document.removeEventListener('contextmenu', handleContextMenu); window.removeEventListener('resize', handleClosingEvent); }; - }, [handleClosingEvent]); + }, [handleClosingEvent, parentContext]); const value = useMemo( () => ({ @@ -73,6 +82,13 @@ export function ContextMenuProvider({ [close] ); + // Prevent accidental nested providers + if (parentContext) { + throw new Error( + 'Duplicated ContextMenuProvider found. Please remove the nested provider.' + ); + } + const Wrapper = wrapper ?? React.Fragment; return ( From eb657699844edde0af7ba583f8fa153ef08f6fb2 Mon Sep 17 00:00:00 2001 From: gagik Date: Tue, 10 Jun 2025 17:13:08 +0200 Subject: [PATCH 37/76] fix: enforce no nesting, adjsut enzyme test and move setup to testing-library --- configs/testing-library-compass/src/index.tsx | 77 ++++++++++--------- .../components/document-list-view.spec.tsx | 30 ++++---- 2 files changed, 58 insertions(+), 49 deletions(-) diff --git a/configs/testing-library-compass/src/index.tsx b/configs/testing-library-compass/src/index.tsx index 6f3bf3bb79d..d01c55bba42 100644 --- a/configs/testing-library-compass/src/index.tsx +++ b/configs/testing-library-compass/src/index.tsx @@ -45,7 +45,10 @@ import { ReadOnlyPreferenceAccess, } from 'compass-preferences-model/provider'; import { TelemetryProvider } from '@mongodb-js/compass-telemetry/provider'; -import { CompassComponentsProvider } from '@mongodb-js/compass-components'; +import { + CompassComponentsProvider, + ContextMenuProvider, +} from '@mongodb-js/compass-components'; import { TestEnvCurrentConnectionContext, ConnectionInfoProvider, @@ -349,41 +352,43 @@ function createWrapper( - - - { - // noop - }) - } - onExtraConnectionDataRequest={ - options.onExtraConnectionDataRequest ?? - (() => { - return Promise.resolve([{}, null] as [any, null]); - }) - } - onAutoconnectInfoRequest={ - options.onAutoconnectInfoRequest - } - preloadStorageConnectionInfos={connections} - > - - - - {children} - - - - - - + + + + { + // noop + }) + } + onExtraConnectionDataRequest={ + options.onExtraConnectionDataRequest ?? + (() => { + return Promise.resolve([{}, null] as [any, null]); + }) + } + onAutoconnectInfoRequest={ + options.onAutoconnectInfoRequest + } + preloadStorageConnectionInfos={connections} + > + + + + {children} + + + + + + + diff --git a/packages/compass-crud/src/components/document-list-view.spec.tsx b/packages/compass-crud/src/components/document-list-view.spec.tsx index 536f48e66c2..ed257306567 100644 --- a/packages/compass-crud/src/components/document-list-view.spec.tsx +++ b/packages/compass-crud/src/components/document-list-view.spec.tsx @@ -1,28 +1,32 @@ import React from 'react'; import { mount } from 'enzyme'; +import type { ReactWrapper } from 'enzyme'; import HadronDocument from 'hadron-document'; import { expect } from 'chai'; -import sinon from 'sinon'; import DocumentListView from './document-list-view'; +import { ContextMenuProvider } from '@mongodb-js/compass-components'; describe('', function () { describe('#render', function () { context('when the documents have objects for ids', function () { const docs = [{ _id: { name: 'test-1' } }, { _id: { name: 'test-2' } }]; const hadronDocs = docs.map((doc) => new HadronDocument(doc)); - const component = mount( - - ); + let component: ReactWrapper; + beforeEach(function () { + component = mount( + , + { wrappingComponent: ContextMenuProvider } + ); + }); + + afterEach(function () { + component?.unmount(); + }); it('renders all the documents', function () { const wrapper = component.find('[data-testid="readonly-document"]'); From 37dff3a43d9f6baca341d210a2b43d81a9a2c08f Mon Sep 17 00:00:00 2001 From: gagik Date: Tue, 10 Jun 2025 17:16:49 +0200 Subject: [PATCH 38/76] fix: separate type import --- .../compass-crud/src/components/document-list-view.spec.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/compass-crud/src/components/document-list-view.spec.tsx b/packages/compass-crud/src/components/document-list-view.spec.tsx index 01710d92cf5..ed257306567 100644 --- a/packages/compass-crud/src/components/document-list-view.spec.tsx +++ b/packages/compass-crud/src/components/document-list-view.spec.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { ReactWrapper, mount } from 'enzyme'; +import { mount } from 'enzyme'; +import type { ReactWrapper } from 'enzyme'; import HadronDocument from 'hadron-document'; import { expect } from 'chai'; From 61e39c39dd8ba31b92dfaac739a195afa181556b Mon Sep 17 00:00:00 2001 From: gagik Date: Tue, 10 Jun 2025 17:27:10 +0200 Subject: [PATCH 39/76] fix: remove redundant providers --- .../components/document-list/element.spec.tsx | 13 ++---- .../document-list-view-item.spec.tsx | 43 +++++++++---------- .../src/components/editable-document.spec.tsx | 4 +- .../virtualized-document-list-view.spec.tsx | 25 +++-------- 4 files changed, 34 insertions(+), 51 deletions(-) diff --git a/packages/compass-components/src/components/document-list/element.spec.tsx b/packages/compass-components/src/components/document-list/element.spec.tsx index 15a31845e3f..88fa527f257 100644 --- a/packages/compass-components/src/components/document-list/element.spec.tsx +++ b/packages/compass-components/src/components/document-list/element.spec.tsx @@ -5,7 +5,6 @@ import sinon from 'sinon'; import HadronDocument from 'hadron-document'; import { HadronElement } from './element'; import type { Element } from 'hadron-document'; -import { ContextMenuProvider } from '@mongodb-js/compass-components'; describe('HadronElement', function () { describe('context menu', function () { @@ -27,12 +26,8 @@ describe('HadronElement', function () { clipboardWriteTextStub.restore(); }); - const renderWithContextMenu = (element: JSX.Element) => { - return render({element}); - }; - it('copies field and value when "Copy field & value" is clicked', function () { - renderWithContextMenu( + render( { + const renderDocumentListViewItem = (doc: HadronDocument): HTMLElement => { const { container } = render( - - - + ); return container.firstChild as HTMLElement; }; @@ -67,7 +64,7 @@ describe('DocumentListViewItem', function () { it('shows "Expand all fields" when document is collapsed', function () { doc.expanded = false; - const container = renderWithContextMenu(doc); + const container = renderDocumentListViewItem(doc); // Right-click to open context menu userEvent.click(container, { button: 2 }); @@ -78,7 +75,7 @@ describe('DocumentListViewItem', function () { it('shows "Collapse all fields" when document is expanded', function () { doc.expanded = true; - const container = renderWithContextMenu(doc); + const container = renderDocumentListViewItem(doc); // Right-click to open context menu userEvent.click(container, { button: 2 }); @@ -89,7 +86,7 @@ describe('DocumentListViewItem', function () { it('expands document when "Expand all fields" is clicked', function () { doc.expanded = false; - const container = renderWithContextMenu(doc); + const container = renderDocumentListViewItem(doc); // Right-click to open context menu userEvent.click(container, { button: 2 }); @@ -103,7 +100,7 @@ describe('DocumentListViewItem', function () { it('collapses document when "Collapse all fields" is clicked', function () { doc.expanded = true; - const container = renderWithContextMenu(doc); + const container = renderDocumentListViewItem(doc); // Right-click to open context menu userEvent.click(container, { button: 2 }); @@ -119,7 +116,7 @@ describe('DocumentListViewItem', function () { it('shows "Edit document" when document is not in editing mode', function () { doc.editing = false; - const container = renderWithContextMenu(doc); + const container = renderDocumentListViewItem(doc); // Right-click to open context menu userEvent.click(container, { button: 2 }); @@ -130,7 +127,7 @@ describe('DocumentListViewItem', function () { it('does not show "Edit document" when document is in editing mode', function () { doc.editing = true; - const container = renderWithContextMenu(doc); + const container = renderDocumentListViewItem(doc); // Right-click to open context menu userEvent.click(container, { button: 2 }); @@ -141,7 +138,7 @@ describe('DocumentListViewItem', function () { it('starts editing when "Edit document" is clicked', function () { doc.editing = false; - const container = renderWithContextMenu(doc); + const container = renderDocumentListViewItem(doc); // Right-click to open context menu userEvent.click(container, { button: 2 }); @@ -155,7 +152,7 @@ describe('DocumentListViewItem', function () { }); it('calls copyToClipboard when "Copy document" is clicked', function () { - const container = renderWithContextMenu(doc); + const container = renderDocumentListViewItem(doc); // Right-click to open context menu userEvent.click(container, { button: 2 }); @@ -169,7 +166,7 @@ describe('DocumentListViewItem', function () { }); it('opens insert dialog with cloned document when "Clone document..." is clicked', async function () { - const container = renderWithContextMenu(doc); + const container = renderDocumentListViewItem(doc); // Right-click to open context menu userEvent.click(container, { button: 2 }); @@ -197,7 +194,7 @@ describe('DocumentListViewItem', function () { }); it('marks document for deletion when "Delete document" is clicked', function () { - const container = renderWithContextMenu(doc); + const container = renderDocumentListViewItem(doc); // Right-click to open context menu userEvent.click(container, { button: 2 }); diff --git a/packages/compass-crud/src/components/editable-document.spec.tsx b/packages/compass-crud/src/components/editable-document.spec.tsx index 19667f66d2e..25643935ce4 100644 --- a/packages/compass-crud/src/components/editable-document.spec.tsx +++ b/packages/compass-crud/src/components/editable-document.spec.tsx @@ -6,6 +6,7 @@ import { expect } from 'chai'; import sinon from 'sinon'; import EditableDocument from './editable-document'; +import { ContextMenuProvider } from '@mongodb-js/compass-context-menu'; describe('', function () { describe('#render', function () { @@ -22,7 +23,8 @@ describe('', function () { updateDocument={sinon.spy(action)} copyToClipboard={sinon.spy(action)} openInsertDocumentDialog={sinon.spy(action)} - /> + />, + { wrappingComponent: ContextMenuProvider } ); }); diff --git a/packages/compass-crud/src/components/virtualized-document-list-view.spec.tsx b/packages/compass-crud/src/components/virtualized-document-list-view.spec.tsx index 9f9ecb44e86..aa2a03acba6 100644 --- a/packages/compass-crud/src/components/virtualized-document-list-view.spec.tsx +++ b/packages/compass-crud/src/components/virtualized-document-list-view.spec.tsx @@ -9,10 +9,7 @@ import { userEvent, render, } from '@mongodb-js/testing-library-compass'; -import { - ContextMenuProvider, - type VirtualListRef, -} from '@mongodb-js/compass-components'; +import { type VirtualListRef } from '@mongodb-js/compass-components'; import VirtualizedDocumentListView from './virtualized-document-list-view'; @@ -39,20 +36,12 @@ const getDocs = () => [ ]; describe('VirtualizedDocumentListView', function () { - const renderWithContextMenu = (element: React.ReactElement) => { - return render(element, { - wrapper: ContextMenuProvider, - }); - }; - afterEach(function () { cleanup(); }); it('renders the list of provided BSON objects', function () { - renderWithContextMenu( - - ); + render(); expect(screen.getByTitle('1')).to.be.visible; expect(screen.getByTitle('Doc1')).to.be.visible; @@ -61,7 +50,7 @@ describe('VirtualizedDocumentListView', function () { }); it('renders the list of provided HadronDocuments', function () { - renderWithContextMenu( + render( new HadronDocument(doc))} isEditable={false} @@ -75,7 +64,7 @@ describe('VirtualizedDocumentListView', function () { }); it('renders a readonly list when isEditable is false', function () { - renderWithContextMenu( + render( new HadronDocument(doc))} isEditable={false} @@ -86,7 +75,7 @@ describe('VirtualizedDocumentListView', function () { }); it('renders an editable list when isEditable is true', function () { - renderWithContextMenu( + render( new HadronDocument(doc))} isEditable={true} @@ -102,7 +91,7 @@ describe('VirtualizedDocumentListView', function () { (_, idx) => new HadronDocument(createBigDocument(idx)) ); const listRef: VirtualListRef = React.createRef(); - renderWithContextMenu( + render( Date: Tue, 10 Jun 2025 17:49:43 +0200 Subject: [PATCH 40/76] fix: remove unnecesary order change --- .../src/components/virtualized-document-list-view.spec.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/compass-crud/src/components/virtualized-document-list-view.spec.tsx b/packages/compass-crud/src/components/virtualized-document-list-view.spec.tsx index aa2a03acba6..a9d291f6479 100644 --- a/packages/compass-crud/src/components/virtualized-document-list-view.spec.tsx +++ b/packages/compass-crud/src/components/virtualized-document-list-view.spec.tsx @@ -2,12 +2,12 @@ import React from 'react'; import { expect } from 'chai'; import HadronDocument from 'hadron-document'; import { + render, screen, cleanup, within, act, userEvent, - render, } from '@mongodb-js/testing-library-compass'; import { type VirtualListRef } from '@mongodb-js/compass-components'; From 220a300c065917a7ee4ef4f289dedda782e9987e Mon Sep 17 00:00:00 2001 From: gagik Date: Tue, 10 Jun 2025 18:04:57 +0200 Subject: [PATCH 41/76] fix: move to compass components provider --- configs/testing-library-compass/src/index.tsx | 82 ++++++++----------- .../compass-components-provider.tsx | 21 +++-- packages/compass-components/src/index.ts | 5 +- .../compass/src/app/components/entrypoint.tsx | 4 +- 4 files changed, 50 insertions(+), 62 deletions(-) diff --git a/configs/testing-library-compass/src/index.tsx b/configs/testing-library-compass/src/index.tsx index d01c55bba42..3739137c602 100644 --- a/configs/testing-library-compass/src/index.tsx +++ b/configs/testing-library-compass/src/index.tsx @@ -45,10 +45,7 @@ import { ReadOnlyPreferenceAccess, } from 'compass-preferences-model/provider'; import { TelemetryProvider } from '@mongodb-js/compass-telemetry/provider'; -import { - CompassComponentsProvider, - ContextMenuProvider, -} from '@mongodb-js/compass-components'; +import { CompassComponentsProvider } from '@mongodb-js/compass-components'; import { TestEnvCurrentConnectionContext, ConnectionInfoProvider, @@ -352,43 +349,41 @@ function createWrapper( - - - - { - // noop - }) - } - onExtraConnectionDataRequest={ - options.onExtraConnectionDataRequest ?? - (() => { - return Promise.resolve([{}, null] as [any, null]); - }) - } - onAutoconnectInfoRequest={ - options.onAutoconnectInfoRequest - } - preloadStorageConnectionInfos={connections} - > - - - - {children} - - - - - - - + + + { + // noop + }) + } + onExtraConnectionDataRequest={ + options.onExtraConnectionDataRequest ?? + (() => { + return Promise.resolve([{}, null] as [any, null]); + }) + } + onAutoconnectInfoRequest={ + options.onAutoconnectInfoRequest + } + preloadStorageConnectionInfos={connections} + > + + + + {children} + + + + + + @@ -756,11 +751,6 @@ function createPluginTestHelpers< */ const fireEvent = testingLibraryFireEvent; -/** - * @deprecated @testing-library/react installs these hooks automatically - */ -const cleanup = rtlCleanup; - /** * @deprecated @testing-library/react-hooks installs these hooks automatically */ diff --git a/packages/compass-components/src/components/compass-components-provider.tsx b/packages/compass-components/src/components/compass-components-provider.tsx index 18bac239d30..7a2fdf20117 100644 --- a/packages/compass-components/src/components/compass-components-provider.tsx +++ b/packages/compass-components/src/components/compass-components-provider.tsx @@ -6,6 +6,7 @@ import { GuideCueProvider } from './guide-cue/guide-cue'; import { SignalHooksProvider } from './signal-popover'; import { RequiredURLSearchParamsProvider } from './links/link'; import { StackedComponentProvider } from '../hooks/use-stacked-component'; +import { ContextMenuProvider } from './context-menu'; type GuideCueProviderProps = React.ComponentProps; @@ -135,15 +136,17 @@ export const CompassComponentsProvider = ({ > - - {typeof children === 'function' - ? children({ - darkMode, - portalContainerRef: setPortalContainer, - scrollContainerRef: setScrollContainer, - }) - : children} - + + + {typeof children === 'function' + ? children({ + darkMode, + portalContainerRef: setPortalContainer, + scrollContainerRef: setScrollContainer, + }) + : children} + + diff --git a/packages/compass-components/src/index.ts b/packages/compass-components/src/index.ts index e6f8068c130..69ce000a74f 100644 --- a/packages/compass-components/src/index.ts +++ b/packages/compass-components/src/index.ts @@ -93,10 +93,7 @@ export { ModalHeader } from './components/modals/modal-header'; export { FormModal } from './components/modals/form-modal'; export { InfoModal } from './components/modals/info-modal'; -export { - ContextMenuProvider, - useContextMenuItems, -} from './components/context-menu'; +export { useContextMenuItems } from './components/context-menu'; export type { FileInputBackend, diff --git a/packages/compass/src/app/components/entrypoint.tsx b/packages/compass/src/app/components/entrypoint.tsx index 866b3414188..28a79b35c37 100644 --- a/packages/compass/src/app/components/entrypoint.tsx +++ b/packages/compass/src/app/components/entrypoint.tsx @@ -102,9 +102,7 @@ export const CompassElectron = (props: React.ComponentProps) => { - - - + From 74f3d6ebacbc766f2987762dd8be6afe02b7f009 Mon Sep 17 00:00:00 2001 From: gagik Date: Tue, 10 Jun 2025 18:07:30 +0200 Subject: [PATCH 42/76] fix: remove unintended deletion --- configs/testing-library-compass/src/index.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/configs/testing-library-compass/src/index.tsx b/configs/testing-library-compass/src/index.tsx index 3739137c602..6f3bf3bb79d 100644 --- a/configs/testing-library-compass/src/index.tsx +++ b/configs/testing-library-compass/src/index.tsx @@ -751,6 +751,11 @@ function createPluginTestHelpers< */ const fireEvent = testingLibraryFireEvent; +/** + * @deprecated @testing-library/react installs these hooks automatically + */ +const cleanup = rtlCleanup; + /** * @deprecated @testing-library/react-hooks installs these hooks automatically */ From 4f05381b57cb2c9f62f9b224d35a5d3869cb2648 Mon Sep 17 00:00:00 2001 From: gagik Date: Tue, 10 Jun 2025 18:30:27 +0200 Subject: [PATCH 43/76] fix: adjust tests --- .../components/content-with-fallback.spec.tsx | 5 +- .../src/components/context-menu.spec.tsx | 210 +++++++++--------- .../src/components/context-menu.tsx | 1 + .../compass/src/app/components/entrypoint.tsx | 1 - 4 files changed, 104 insertions(+), 113 deletions(-) diff --git a/packages/compass-components/src/components/content-with-fallback.spec.tsx b/packages/compass-components/src/components/content-with-fallback.spec.tsx index bd4daa3861c..5b989316f5b 100644 --- a/packages/compass-components/src/components/content-with-fallback.spec.tsx +++ b/packages/compass-components/src/components/content-with-fallback.spec.tsx @@ -58,7 +58,10 @@ describe('ContentWithFallback', function () { { container } ); - expect(container).to.be.empty; + expect(container.children.length).to.equal(1); + expect(container.children[0].getAttribute('data-testid')).to.equal( + 'context-menu' + ); }); it('should render fallback when the timeout passes', async function () { diff --git a/packages/compass-components/src/components/context-menu.spec.tsx b/packages/compass-components/src/components/context-menu.spec.tsx index 14177575d9d..d2ecaa63cdf 100644 --- a/packages/compass-components/src/components/context-menu.spec.tsx +++ b/packages/compass-components/src/components/context-menu.spec.tsx @@ -28,40 +28,88 @@ describe('useContextMenuItems', function () { ); }; - describe('when used outside provider', function () { - it('throws an error', function () { - const items = [ - { - label: 'Test Item', - onAction: () => {}, - }, - ]; - - expect(() => { - render(); - }).to.throw('useContextMenu called outside of the provider'); - }); - }); - - describe('with a valid provider', function () { - it('renders without error', function () { - const items = [ - { - label: 'Test Item', - onAction: () => {}, - }, - ]; - + it('errors if the component is double wrapped', function () { + const items = [ + { + label: 'Test Item', + onAction: () => {}, + }, + ]; + + expect(() => { render( ); + }).to.throw( + 'Duplicated ContextMenuProvider found. Please remove the nested provider.' + ); + }); - expect(screen.getByTestId(menuTestTriggerId)).to.exist; - }); + it('renders without error', function () { + const items = [ + { + label: 'Test Item', + onAction: () => {}, + }, + ]; + + render(); + + expect(screen.getByTestId(menuTestTriggerId)).to.exist; + }); + + it('shows context menu with items on right click', function () { + const items = [ + { + label: 'Test Item 1', + onAction: () => {}, + }, + { + label: 'Test Item 2', + onAction: () => {}, + }, + ]; + + render(); + + const trigger = screen.getByTestId(menuTestTriggerId); + userEvent.click(trigger, { button: 2 }); + + // The menu items should be rendered + expect(screen.getByTestId('menu-group-0-item-0')).to.exist; + expect(screen.getByTestId('menu-group-0-item-1')).to.exist; + }); + + it('triggers the correct action when menu item is clicked', function () { + const onAction = sinon.spy(); + const items = [ + { + label: 'Test Item 1', + onAction: () => onAction(1), + }, + { + label: 'Test Item 2', + onAction: () => onAction(2), + }, + ]; + + render(); + + const trigger = screen.getByTestId(menuTestTriggerId); + userEvent.click(trigger, { button: 2 }); - it('shows context menu with items on right click', function () { + const menuItem = screen.getByTestId('menu-group-0-item-1'); + userEvent.click(menuItem); + + expect(onAction).to.have.been.calledOnceWithExactly(2); + }); + + describe('with nested components', function () { + const childTriggerId = 'child-trigger'; + + beforeEach(function () { const items = [ { label: 'Test Item 1', @@ -73,101 +121,41 @@ describe('useContextMenuItems', function () { }, ]; - render( - - - - ); - - const trigger = screen.getByTestId(menuTestTriggerId); - userEvent.click(trigger, { button: 2 }); - - // The menu items should be rendered - expect(screen.getByTestId('menu-group-0-item-0')).to.exist; - expect(screen.getByTestId('menu-group-0-item-1')).to.exist; - }); - - it('triggers the correct action when menu item is clicked', function () { - const onAction = sinon.spy(); - const items = [ + const childItems = [ { - label: 'Test Item 1', - onAction: () => onAction(1), - }, - { - label: 'Test Item 2', - onAction: () => onAction(2), + label: 'Child Item 1', + onAction: () => {}, }, ]; render( - - - + + + ); + }); - const trigger = screen.getByTestId(menuTestTriggerId); + it('renders menu items with separators', function () { + const trigger = screen.getByTestId(childTriggerId); userEvent.click(trigger, { button: 2 }); - const menuItem = screen.getByTestId('menu-group-0-item-1'); - userEvent.click(menuItem); + // Should find the menu item and the separator + expect(screen.getByTestId('menu-group-0').children.length).to.equal(2); + expect( + screen.getByTestId('menu-group-0').children.item(0)?.textContent + ).to.equal('Child Item 1'); - expect(onAction).to.have.been.calledOnceWithExactly(2); - }); + expect(screen.getByTestId('menu-group-0-separator')).to.exist; + + expect(screen.getByTestId('menu-group-1').children.length).to.equal(2); + expect( + screen.getByTestId('menu-group-1').children.item(0)?.textContent + ).to.equal('Test Item 1'); + expect( + screen.getByTestId('menu-group-1').children.item(1)?.textContent + ).to.equal('Test Item 2'); - describe('with nested components', function () { - const childTriggerId = 'child-trigger'; - - beforeEach(function () { - const items = [ - { - label: 'Test Item 1', - onAction: () => {}, - }, - { - label: 'Test Item 2', - onAction: () => {}, - }, - ]; - - const childItems = [ - { - label: 'Child Item 1', - onAction: () => {}, - }, - ]; - - render( - - - - - - ); - }); - - it('renders menu items with separators', function () { - const trigger = screen.getByTestId(childTriggerId); - userEvent.click(trigger, { button: 2 }); - - // Should find the menu item and the separator - expect(screen.getByTestId('menu-group-0').children.length).to.equal(2); - expect( - screen.getByTestId('menu-group-0').children.item(0)?.textContent - ).to.equal('Child Item 1'); - - expect(screen.getByTestId('menu-group-0-separator')).to.exist; - - expect(screen.getByTestId('menu-group-1').children.length).to.equal(2); - expect( - screen.getByTestId('menu-group-1').children.item(0)?.textContent - ).to.equal('Test Item 1'); - expect( - screen.getByTestId('menu-group-1').children.item(1)?.textContent - ).to.equal('Test Item 2'); - - expect(screen.queryByTestId('menu-group-1-separator')).not.to.exist; - }); + expect(screen.queryByTestId('menu-group-1-separator')).not.to.exist; }); }); }); diff --git a/packages/compass-components/src/components/context-menu.tsx b/packages/compass-components/src/components/context-menu.tsx index 288db7393a5..c12ff5fdd3e 100644 --- a/packages/compass-components/src/components/context-menu.tsx +++ b/packages/compass-components/src/components/context-menu.tsx @@ -32,6 +32,7 @@ export function ContextMenu({ menu }: ContextMenuWrapperProps) { return (
{ const loggerProviderValue = useRef({ From 3f6e8acf01e3b38caa0f98122f475ec7700bed5a Mon Sep 17 00:00:00 2001 From: gagik Date: Tue, 10 Jun 2025 18:33:53 +0200 Subject: [PATCH 44/76] fix: use compass components provider --- .../compass-crud/src/components/document-list-view.spec.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/compass-crud/src/components/document-list-view.spec.tsx b/packages/compass-crud/src/components/document-list-view.spec.tsx index ed257306567..5938ac2ab06 100644 --- a/packages/compass-crud/src/components/document-list-view.spec.tsx +++ b/packages/compass-crud/src/components/document-list-view.spec.tsx @@ -5,7 +5,7 @@ import HadronDocument from 'hadron-document'; import { expect } from 'chai'; import DocumentListView from './document-list-view'; -import { ContextMenuProvider } from '@mongodb-js/compass-components'; +import { CompassComponentsProvider } from '@mongodb-js/compass-components'; describe('', function () { describe('#render', function () { @@ -20,7 +20,7 @@ describe('', function () { isEditable={false} isTimeSeries={false} />, - { wrappingComponent: ContextMenuProvider } + { wrappingComponent: CompassComponentsProvider } ); }); From 28636763b07f09f8f52d21105207fdaf84286377 Mon Sep 17 00:00:00 2001 From: gagik Date: Tue, 10 Jun 2025 19:03:32 +0200 Subject: [PATCH 45/76] fix: use a shared hook and minimize test duplication --- .../document-json-view-item.spec.tsx | 107 +++++++ .../components/document-json-view-item.tsx | 45 ++- .../document-list-view-item.spec.tsx | 280 ++++++------------ .../components/document-list-view-item.tsx | 36 ++- .../use-document-item-context-menu.spec.tsx | 255 ++++++++++++++++ .../use-document-item-context-menu.tsx | 67 +++++ 6 files changed, 572 insertions(+), 218 deletions(-) create mode 100644 packages/compass-crud/src/components/document-json-view-item.spec.tsx create mode 100644 packages/compass-crud/src/components/use-document-item-context-menu.spec.tsx create mode 100644 packages/compass-crud/src/components/use-document-item-context-menu.tsx diff --git a/packages/compass-crud/src/components/document-json-view-item.spec.tsx b/packages/compass-crud/src/components/document-json-view-item.spec.tsx new file mode 100644 index 00000000000..767ab08350b --- /dev/null +++ b/packages/compass-crud/src/components/document-json-view-item.spec.tsx @@ -0,0 +1,107 @@ +import React from 'react'; +import { render, screen, userEvent } from '@mongodb-js/testing-library-compass'; +import { expect } from 'chai'; +import sinon from 'sinon'; +import HadronDocument from 'hadron-document'; +import { DocumentJsonViewItem } from './document-json-view-item'; + +describe('DocumentJsonViewItem', function () { + let doc: HadronDocument; + let copyToClipboardStub: sinon.SinonStub; + let openInsertDocumentDialogStub: sinon.SinonStub; + + beforeEach(function () { + doc = new HadronDocument({ + _id: 1, + name: 'test', + url: 'https://mongodb.com', + nested: { field: 'value' }, + }); + + copyToClipboardStub = sinon.stub(); + openInsertDocumentDialogStub = sinon.stub(); + }); + + afterEach(function () { + sinon.restore(); + }); + + it('renders the JSON editor component', function () { + render( + + ); + + // Should render without error + expect(document.querySelector('[data-testid="editable-json"]')).to.exist; + }); + + it('renders context menu when right-clicked', function () { + const { container } = render( + + ); + + const element = container.firstChild as HTMLElement; + + // Right-click to open context menu + userEvent.click(element, { button: 2 }); + + // Should show context menu with expected items + expect(screen.getByText('Copy document')).to.exist; + expect(screen.getByText('Clone document...')).to.exist; + expect(screen.getByText('Delete document')).to.exist; + }); + + it('renders scroll trigger when docIndex is 0', function () { + const scrollTriggerRef = React.createRef(); + + render( + + ); + + expect(scrollTriggerRef.current).to.exist; + }); + + it('does not render scroll trigger when docIndex is not 0', function () { + const scrollTriggerRef = React.createRef(); + + render( + + ); + + expect(scrollTriggerRef.current).to.be.null; + }); +}); diff --git a/packages/compass-crud/src/components/document-json-view-item.tsx b/packages/compass-crud/src/components/document-json-view-item.tsx index f368b476f09..f89ccce2240 100644 --- a/packages/compass-crud/src/components/document-json-view-item.tsx +++ b/packages/compass-crud/src/components/document-json-view-item.tsx @@ -1,6 +1,6 @@ import React from 'react'; import type HadronDocument from 'hadron-document'; -import { css, KeylineCard, spacing } from '@mongodb-js/compass-components'; +import { css, KeylineCard } from '@mongodb-js/compass-components'; import JSONEditor, { type JSONEditorProps } from './json-editor'; import { useContextMenuItems } from '@mongodb-js/compass-components'; @@ -43,23 +43,50 @@ const DocumentJsonViewItem: React.FC = ({ }) => { const ref = useContextMenuItems([ { - label: 'Update document', + label: doc.expanded ? 'Collapse all fields' : 'Expand all fields', onAction: () => { - updateDocument?.(doc); + if (doc.expanded) { + doc.collapse(); + } else { + doc.expand(); + } }, }, + ...(isEditable && !doc.editing + ? [ + { + label: 'Edit document', + onAction: () => { + doc.startEditing(); + }, + }, + ] + : []), { label: 'Copy document', onAction: () => { copyToClipboard?.(doc); }, }, - { - label: 'Delete document', - onAction: () => { - removeDocument?.(doc); - }, - }, + ...(isEditable + ? [ + { + label: 'Clone document...', + onAction: () => { + const clonedDoc = doc.generateObject({ + excludeInternalFields: true, + }); + openInsertDocumentDialog?.(clonedDoc, true); + }, + }, + { + label: 'Delete document', + onAction: () => { + doc.markForDeletion(); + }, + }, + ] + : []), ]); return ( diff --git a/packages/compass-crud/src/components/document-list-view-item.spec.tsx b/packages/compass-crud/src/components/document-list-view-item.spec.tsx index 22cc990d00a..5ad9ffa8798 100644 --- a/packages/compass-crud/src/components/document-list-view-item.spec.tsx +++ b/packages/compass-crud/src/components/document-list-view-item.spec.tsx @@ -6,205 +6,99 @@ import HadronDocument from 'hadron-document'; import { DocumentListViewItem } from './document-list-view-item'; describe('DocumentListViewItem', function () { - describe('document context menu', function () { - let doc: HadronDocument; - let copyToClipboardStub: sinon.SinonStub; - let openInsertDocumentDialogStub: sinon.SinonStub; - let collapseStub: sinon.SinonStub; - let expandStub: sinon.SinonStub; - let startEditingStub: sinon.SinonStub; - let markForDeletionStub: sinon.SinonStub; - let generateObjectStub: sinon.SinonStub; - - beforeEach(function () { - doc = new HadronDocument({ - _id: 1, - name: 'test', - url: 'https://mongodb.com', - nested: { field: 'value' }, - }); - - copyToClipboardStub = sinon.stub(); - openInsertDocumentDialogStub = sinon.stub(); - - // Set up document methods as stubs - collapseStub = sinon.stub(doc, 'collapse'); - expandStub = sinon.stub(doc, 'expand'); - startEditingStub = sinon.stub(doc, 'startEditing'); - markForDeletionStub = sinon.stub(doc, 'markForDeletion'); - generateObjectStub = sinon.stub(doc, 'generateObject').returns({ - _id: 1, - name: 'test', - url: 'https://mongodb.com', - nested: { field: 'value' }, - }); + let doc: HadronDocument; + let copyToClipboardStub: sinon.SinonStub; + let openInsertDocumentDialogStub: sinon.SinonStub; + + beforeEach(function () { + doc = new HadronDocument({ + _id: 1, + name: 'test', + url: 'https://mongodb.com', + nested: { field: 'value' }, }); - /** - * Renders the element and returns a reference to the first child of the container. - */ - const renderDocumentListViewItem = (doc: HadronDocument): HTMLElement => { - const { container } = render( - - ); - return container.firstChild as HTMLElement; - }; - - afterEach(function () { - sinon.restore(); - }); - - it('shows "Expand all fields" when document is collapsed', function () { - doc.expanded = false; - - const container = renderDocumentListViewItem(doc); - - // Right-click to open context menu - userEvent.click(container, { button: 2 }); - - expect(screen.getByText('Expand all fields')).to.exist; - }); - - it('shows "Collapse all fields" when document is expanded', function () { - doc.expanded = true; - - const container = renderDocumentListViewItem(doc); - - // Right-click to open context menu - userEvent.click(container, { button: 2 }); - - expect(screen.getByText('Collapse all fields')).to.exist; - }); - - it('expands document when "Expand all fields" is clicked', function () { - doc.expanded = false; - - const container = renderDocumentListViewItem(doc); - - // Right-click to open context menu - userEvent.click(container, { button: 2 }); - - // Click expand option - userEvent.click(screen.getByText('Expand all fields')); - - expect(expandStub).to.have.been.calledOnce; - }); - - it('collapses document when "Collapse all fields" is clicked', function () { - doc.expanded = true; - - const container = renderDocumentListViewItem(doc); - - // Right-click to open context menu - userEvent.click(container, { button: 2 }); - - // Click collapse option - userEvent.click(screen.getByText('Collapse all fields'), undefined, { - skipPointerEventsCheck: true, - }); - - expect(collapseStub).to.have.been.calledOnce; - }); - - it('shows "Edit document" when document is not in editing mode', function () { - doc.editing = false; - - const container = renderDocumentListViewItem(doc); - - // Right-click to open context menu - userEvent.click(container, { button: 2 }); - - expect(screen.getByText('Edit document')).to.exist; - }); - - it('does not show "Edit document" when document is in editing mode', function () { - doc.editing = true; - - const container = renderDocumentListViewItem(doc); - - // Right-click to open context menu - userEvent.click(container, { button: 2 }); - - expect(screen.queryByText('Edit document')).to.not.exist; - }); - - it('starts editing when "Edit document" is clicked', function () { - doc.editing = false; - - const container = renderDocumentListViewItem(doc); - - // Right-click to open context menu - userEvent.click(container, { button: 2 }); - - // Click edit option - userEvent.click(screen.getByText('Edit document'), undefined, { - skipPointerEventsCheck: true, - }); - - expect(startEditingStub).to.have.been.calledOnce; - }); - - it('calls copyToClipboard when "Copy document" is clicked', function () { - const container = renderDocumentListViewItem(doc); - - // Right-click to open context menu - userEvent.click(container, { button: 2 }); - - // Click copy option - userEvent.click(screen.getByText('Copy document'), undefined, { - skipPointerEventsCheck: true, - }); - - expect(copyToClipboardStub).to.have.been.calledWith(doc); - }); - - it('opens insert dialog with cloned document when "Clone document..." is clicked', async function () { - const container = renderDocumentListViewItem(doc); - - // Right-click to open context menu - userEvent.click(container, { button: 2 }); - - // Click clone option - userEvent.click(screen.getByText('Clone document...'), undefined, { - skipPointerEventsCheck: true, - }); - - await new Promise((resolve) => setTimeout(resolve, 1000)); - - expect(generateObjectStub).to.have.been.calledWith({ - excludeInternalFields: true, - }); + copyToClipboardStub = sinon.stub(); + openInsertDocumentDialogStub = sinon.stub(); + }); - expect(openInsertDocumentDialogStub).to.have.been.calledWith( - { - _id: 1, - name: 'test', - url: 'https://mongodb.com', - nested: { field: 'value' }, - }, - true - ); - }); + afterEach(function () { + sinon.restore(); + }); - it('marks document for deletion when "Delete document" is clicked', function () { - const container = renderDocumentListViewItem(doc); + it('renders the document component', function () { + render( + + ); + + // Should render without error + expect(document.querySelector('[data-testid="editable-document"]')).to + .exist; + }); - // Right-click to open context menu - userEvent.click(container, { button: 2 }); + it('renders context menu when right-clicked', function () { + const { container } = render( + + ); + + const element = container.firstChild as HTMLElement; + + // Right-click to open context menu + userEvent.click(element, { button: 2 }); + + // Should show context menu with expected items + expect(screen.getByText('Copy document')).to.exist; + expect(screen.getByText('Clone document...')).to.exist; + expect(screen.getByText('Delete document')).to.exist; + }); - // Click delete option - userEvent.click(screen.getByText('Delete document'), undefined, { - skipPointerEventsCheck: true, - }); + it('renders scroll trigger when docIndex is 0', function () { + const scrollTriggerRef = React.createRef(); + + render( + + ); + + expect(scrollTriggerRef.current).to.exist; + }); - expect(markForDeletionStub).to.have.been.calledOnce; - }); + it('does not render scroll trigger when docIndex is not 0', function () { + const scrollTriggerRef = React.createRef(); + + render( + + ); + + expect(scrollTriggerRef.current).to.be.null; }); }); diff --git a/packages/compass-crud/src/components/document-list-view-item.tsx b/packages/compass-crud/src/components/document-list-view-item.tsx index 493f7bc52c4..ffe859d908f 100644 --- a/packages/compass-crud/src/components/document-list-view-item.tsx +++ b/packages/compass-crud/src/components/document-list-view-item.tsx @@ -44,7 +44,7 @@ const DocumentListViewItem: React.FC = ({ } }, }, - ...(!doc.editing + ...(isEditable && !doc.editing ? [ { label: 'Edit document', @@ -60,21 +60,25 @@ const DocumentListViewItem: React.FC = ({ copyToClipboard?.(doc); }, }, - { - label: 'Clone document...', - onAction: () => { - const clonedDoc = doc.generateObject({ - excludeInternalFields: true, - }); - openInsertDocumentDialog?.(clonedDoc, true); - }, - }, - { - label: 'Delete document', - onAction: () => { - doc.markForDeletion(); - }, - }, + ...(isEditable + ? [ + { + label: 'Clone document...', + onAction: () => { + const clonedDoc = doc.generateObject({ + excludeInternalFields: true, + }); + openInsertDocumentDialog?.(clonedDoc, true); + }, + }, + { + label: 'Delete document', + onAction: () => { + doc.markForDeletion(); + }, + }, + ] + : []), ]); return ( diff --git a/packages/compass-crud/src/components/use-document-item-context-menu.spec.tsx b/packages/compass-crud/src/components/use-document-item-context-menu.spec.tsx new file mode 100644 index 00000000000..0edf522414e --- /dev/null +++ b/packages/compass-crud/src/components/use-document-item-context-menu.spec.tsx @@ -0,0 +1,255 @@ +import React from 'react'; +import { render, screen, userEvent } from '@mongodb-js/testing-library-compass'; +import { expect } from 'chai'; +import sinon from 'sinon'; +import HadronDocument from 'hadron-document'; +import { useDocumentItemContextMenu } from './use-document-item-context-menu'; + +// Test component that uses the hook +const TestComponent: React.FC<{ + doc: HadronDocument; + isEditable: boolean; + copyToClipboard?: (doc: HadronDocument) => void; + openInsertDocumentDialog?: ( + doc: Record, + cloned: boolean + ) => void; +}> = ({ doc, isEditable, copyToClipboard, openInsertDocumentDialog }) => { + const ref = useDocumentItemContextMenu({ + doc, + isEditable, + copyToClipboard, + openInsertDocumentDialog, + }); + + return ( +
+ Test Content +
+ ); +}; + +describe('useDocumentItemContextMenu', function () { + let doc: HadronDocument; + let copyToClipboardStub: sinon.SinonStub; + let openInsertDocumentDialogStub: sinon.SinonStub; + let collapseStub: sinon.SinonStub; + let expandStub: sinon.SinonStub; + let startEditingStub: sinon.SinonStub; + let markForDeletionStub: sinon.SinonStub; + let generateObjectStub: sinon.SinonStub; + + beforeEach(function () { + doc = new HadronDocument({ + _id: 1, + name: 'test', + nested: { field: 'value' }, + }); + + copyToClipboardStub = sinon.stub(); + openInsertDocumentDialogStub = sinon.stub(); + + // Set up document methods as stubs + collapseStub = sinon.stub(doc, 'collapse'); + expandStub = sinon.stub(doc, 'expand'); + startEditingStub = sinon.stub(doc, 'startEditing'); + markForDeletionStub = sinon.stub(doc, 'markForDeletion'); + generateObjectStub = sinon.stub(doc, 'generateObject').returns({ + _id: 1, + name: 'test', + nested: { field: 'value' }, + }); + }); + + afterEach(function () { + sinon.restore(); + }); + + describe('when editable', function () { + it('shows all menu items when document is editable and not editing', function () { + doc.expanded = false; + doc.editing = false; + + render( + + ); + + // Right-click to open context menu + userEvent.click(screen.getByTestId('test-container'), { button: 2 }); + + // Should show all operations + expect(screen.getByText('Expand all fields')).to.exist; + expect(screen.getByText('Edit document')).to.exist; + expect(screen.getByText('Copy document')).to.exist; + expect(screen.getByText('Clone document...')).to.exist; + expect(screen.getByText('Delete document')).to.exist; + }); + + it('hides edit document when document is editing', function () { + doc.expanded = false; + doc.editing = true; + + render( + + ); + + // Right-click to open context menu + userEvent.click(screen.getByTestId('test-container'), { button: 2 }); + + // Should hide edit document when editing + expect(screen.queryByText('Edit document')).to.not.exist; + // But show other operations + expect(screen.getByText('Expand all fields')).to.exist; + expect(screen.getByText('Copy document')).to.exist; + expect(screen.getByText('Clone document...')).to.exist; + expect(screen.getByText('Delete document')).to.exist; + }); + }); + + describe('when read-only', function () { + it('shows only non-mutating operations when not editable', function () { + doc.expanded = false; + doc.editing = false; + + render( + + ); + + // Right-click to open context menu + userEvent.click(screen.getByTestId('test-container'), { button: 2 }); + + // Should show non-mutating operations + expect(screen.getByText('Expand all fields')).to.exist; + expect(screen.getByText('Copy document')).to.exist; + + // Should hide mutating operations + expect(screen.queryByText('Edit document')).to.not.exist; + expect(screen.queryByText('Clone document...')).to.not.exist; + expect(screen.queryByText('Delete document')).to.not.exist; + }); + + it('collapses document when collapse is clicked', function () { + doc.expanded = true; + + // Render with expanded document + render( + + ); + + // Right-click to open context menu + userEvent.click(screen.getByTestId('test-container'), { button: 2 }); + + // Click collapse + userEvent.click(screen.getByText('Collapse all fields'), undefined, { + skipPointerEventsCheck: true, + }); + + expect(collapseStub).to.have.been.calledOnce; + }); + }); + + describe('functionality', function () { + beforeEach(function () { + render( + + ); + }); + + it('toggles expand/collapse correctly', function () { + doc.expanded = false; + + // Right-click to open context menu + userEvent.click(screen.getByTestId('test-container'), { button: 2 }); + + // Click expand + userEvent.click(screen.getByText('Expand all fields')); + + expect(expandStub).to.have.been.calledOnce; + }); + + it('starts editing when edit is clicked', function () { + doc.editing = false; + + // Right-click to open context menu + userEvent.click(screen.getByTestId('test-container'), { button: 2 }); + + // Click edit + userEvent.click(screen.getByText('Edit document'), undefined, { + skipPointerEventsCheck: true, + }); + + expect(startEditingStub).to.have.been.calledOnce; + }); + + it('calls copyToClipboard when copy is clicked', function () { + // Right-click to open context menu + userEvent.click(screen.getByTestId('test-container'), { button: 2 }); + + // Click copy + userEvent.click(screen.getByText('Copy document'), undefined, { + skipPointerEventsCheck: true, + }); + + expect(copyToClipboardStub).to.have.been.calledWith(doc); + }); + + it('calls openInsertDocumentDialog with cloned document when clone is clicked', function () { + // Right-click to open context menu + userEvent.click(screen.getByTestId('test-container'), { button: 2 }); + + // Click clone + userEvent.click(screen.getByText('Clone document...'), undefined, { + skipPointerEventsCheck: true, + }); + + expect(generateObjectStub).to.have.been.calledWith({ + excludeInternalFields: true, + }); + expect(openInsertDocumentDialogStub).to.have.been.calledWith( + { + _id: 1, + name: 'test', + nested: { field: 'value' }, + }, + true + ); + }); + + it('marks document for deletion when delete is clicked', function () { + // Right-click to open context menu + userEvent.click(screen.getByTestId('test-container'), { button: 2 }); + + // Click delete + userEvent.click(screen.getByText('Delete document'), undefined, { + skipPointerEventsCheck: true, + }); + + expect(markForDeletionStub).to.have.been.calledOnce; + }); + }); +}); diff --git a/packages/compass-crud/src/components/use-document-item-context-menu.tsx b/packages/compass-crud/src/components/use-document-item-context-menu.tsx new file mode 100644 index 00000000000..f457afd7dd7 --- /dev/null +++ b/packages/compass-crud/src/components/use-document-item-context-menu.tsx @@ -0,0 +1,67 @@ +import type HadronDocument from 'hadron-document'; +import { useContextMenuItems } from '@mongodb-js/compass-components'; + +export interface UseDocumentItemContextMenuProps { + doc: HadronDocument; + isEditable: boolean; + copyToClipboard?: (doc: HadronDocument) => void; + openInsertDocumentDialog?: ( + doc: Record, + cloned: boolean + ) => void; +} + +export function useDocumentItemContextMenu({ + doc, + isEditable, + copyToClipboard, + openInsertDocumentDialog, +}: UseDocumentItemContextMenuProps) { + return useContextMenuItems([ + { + label: doc.expanded ? 'Collapse all fields' : 'Expand all fields', + onAction: () => { + if (doc.expanded) { + doc.collapse(); + } else { + doc.expand(); + } + }, + }, + ...(isEditable && !doc.editing + ? [ + { + label: 'Edit document', + onAction: () => { + doc.startEditing(); + }, + }, + ] + : []), + { + label: 'Copy document', + onAction: () => { + copyToClipboard?.(doc); + }, + }, + ...(isEditable + ? [ + { + label: 'Clone document...', + onAction: () => { + const clonedDoc = doc.generateObject({ + excludeInternalFields: true, + }); + openInsertDocumentDialog?.(clonedDoc, true); + }, + }, + { + label: 'Delete document', + onAction: () => { + doc.markForDeletion(); + }, + }, + ] + : []), + ]); +} From 768d0f20860c046d2741c3cce6e3bdf7b1264e99 Mon Sep 17 00:00:00 2001 From: Sergey Petushkov Date: Tue, 17 Jun 2025 10:08:56 +0200 Subject: [PATCH 46/76] chore: cleanup package-lock from removed workspaces (#7025) --- package-lock.json | 675 ---------------------------------------------- 1 file changed, 675 deletions(-) diff --git a/package-lock.json b/package-lock.json index d0ac9559e2a..33e294fdb7a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -50302,195 +50302,6 @@ "url": "https://opencollective.com/sinon" } }, - "packages/mongodb-test-server/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "extraneous": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "packages/mongodb-test-server/node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "extraneous": true, - "engines": { - "node": ">=0.3.1" - } - }, - "packages/mongodb-test-server/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "extraneous": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "packages/mongodb-test-server/node_modules/foreground-child": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", - "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", - "extraneous": true, - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "packages/mongodb-test-server/node_modules/glob": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.2.6.tgz", - "integrity": "sha512-U/rnDpXJGF414QQQZv5uVsabTVxMSwzS5CH0p3DRCIV6ownl4f7PzGnkGmvlum2wB+9RlJWJZ6ACU1INnBqiPA==", - "extraneous": true, - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.0.3", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2", - "path-scurry": "^1.7.0" - }, - "bin": { - "glob": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "packages/mongodb-test-server/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "extraneous": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "packages/mongodb-test-server/node_modules/minimatch": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", - "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", - "extraneous": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "packages/mongodb-test-server/node_modules/minipass": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-6.0.2.tgz", - "integrity": "sha512-MzWSV5nYVT7mVyWCwn2o7JH13w2TBRmmSqSRCKzTw+lmft9X4z+3wjvs06Tzijo5z4W/kahUCDpRXTF+ZrmF/w==", - "extraneous": true, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "packages/mongodb-test-server/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "extraneous": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "packages/mongodb-test-server/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "extraneous": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "packages/mongodb-test-server/node_modules/rimraf": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.1.tgz", - "integrity": "sha512-OfFZdwtd3lZ+XZzYP/6gTACubwFcHdLRqS9UX3UwpU2dnGQYkPFISRwvM3w9IiB2w7bW5qGo/uAwE4SmXXSKvg==", - "extraneous": true, - "dependencies": { - "glob": "^10.2.5" - }, - "bin": { - "rimraf": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "packages/mongodb-test-server/node_modules/signal-exit": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.2.tgz", - "integrity": "sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==", - "extraneous": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "packages/mongodb-test-server/node_modules/sinon": { - "version": "9.2.4", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.4.tgz", - "integrity": "sha512-zljcULZQsJxVra28qIAL6ow1Z9tpattkCTEJR4RBP3TGc00FcttsP5pK284Nas5WjMZU5Yzy3kAIp3B3KRf5Yg==", - "extraneous": true, - "dependencies": { - "@sinonjs/commons": "^1.8.1", - "@sinonjs/fake-timers": "^6.0.1", - "@sinonjs/samsam": "^5.3.1", - "diff": "^4.0.2", - "nise": "^4.0.4", - "supports-color": "^7.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/sinon" - } - }, - "packages/mongodb-test-server/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "extraneous": true, - "engines": { - "node": ">=12" - } - }, "packages/my-queries-storage": { "name": "@mongodb-js/my-queries-storage", "version": "0.27.3", @@ -50587,492 +50398,6 @@ "typescript": "^5.0.4" } }, - "packages/reflux-store/node_modules/acorn": { - "version": "5.7.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", - "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", - "extraneous": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "packages/reflux-store/node_modules/acorn-jsx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", - "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", - "extraneous": true, - "dependencies": { - "acorn": "^3.0.4" - } - }, - "packages/reflux-store/node_modules/ajv": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", - "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", - "extraneous": true, - "dependencies": { - "co": "^4.6.0", - "json-stable-stringify": "^1.0.1" - } - }, - "packages/reflux-store/node_modules/ajv-keywords": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", - "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", - "extraneous": true, - "peerDependencies": { - "ajv": ">=4.10.0" - } - }, - "packages/reflux-store/node_modules/ansi-escapes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", - "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", - "extraneous": true, - "engines": { - "node": ">=0.10.0" - } - }, - "packages/reflux-store/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "extraneous": true, - "engines": { - "node": ">=0.10.0" - } - }, - "packages/reflux-store/node_modules/chai": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz", - "integrity": "sha1-TQJjewZ/6Vi9v906QOxW/vc3Mkc=", - "extraneous": true, - "dependencies": { - "assertion-error": "^1.0.1", - "deep-eql": "^0.1.3", - "type-detect": "^1.0.0" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "packages/reflux-store/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "extraneous": true, - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "packages/reflux-store/node_modules/cli-cursor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", - "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", - "extraneous": true, - "dependencies": { - "restore-cursor": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "packages/reflux-store/node_modules/cli-width": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", - "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", - "extraneous": true - }, - "packages/reflux-store/node_modules/debug": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", - "integrity": "sha512-X0rGvJcskG1c3TgSCPqHJ0XJgwlcvOC7elJ5Y0hYuKBZoVqWpAMfLOeIh2UI/DCQ5ruodIjvsugZtjUYUw2pUw==", - "extraneous": true, - "dependencies": { - "ms": "0.7.1" - } - }, - "packages/reflux-store/node_modules/deep-eql": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", - "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", - "extraneous": true, - "dependencies": { - "type-detect": "0.1.1" - }, - "engines": { - "node": "*" - } - }, - "packages/reflux-store/node_modules/doctrine": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", - "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", - "extraneous": true, - "dependencies": { - "esutils": "^2.0.2", - "isarray": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "packages/reflux-store/node_modules/eslint-config-mongodb-js": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/eslint-config-mongodb-js/-/eslint-config-mongodb-js-2.3.0.tgz", - "integrity": "sha512-9zxJawyp68GNX63pfqeLV47/ShSyY7Hce3l/XhrD8dihFhygs+5C7lk12ogDePK3OmOer1pREvwgR8q0YvV4Pw==", - "extraneous": true, - "dependencies": { - "babel-eslint": "^7.1.0", - "eslint": "^3.3.1", - "eslint-plugin-chai-friendly": "^0.4.0", - "eslint-plugin-react": "^6.1.2" - } - }, - "packages/reflux-store/node_modules/eslint-plugin-react": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-6.10.3.tgz", - "integrity": "sha1-xUNb6wZ3ThLH2y9qut3L+QDNP3g=", - "extraneous": true, - "dependencies": { - "array.prototype.find": "^2.0.1", - "doctrine": "^1.2.2", - "has": "^1.0.1", - "jsx-ast-utils": "^1.3.4", - "object.assign": "^4.0.4" - }, - "engines": { - "node": ">=0.10" - }, - "peerDependencies": { - "eslint": "^2.0.0 || ^3.0.0" - } - }, - "packages/reflux-store/node_modules/espree": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", - "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", - "extraneous": true, - "dependencies": { - "acorn": "^5.5.0", - "acorn-jsx": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "packages/reflux-store/node_modules/figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "extraneous": true, - "dependencies": { - "escape-string-regexp": "^1.0.5", - "object-assign": "^4.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "packages/reflux-store/node_modules/file-entry-cache": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", - "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", - "extraneous": true, - "dependencies": { - "flat-cache": "^1.2.1", - "object-assign": "^4.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "packages/reflux-store/node_modules/flat-cache": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", - "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", - "extraneous": true, - "dependencies": { - "circular-json": "^0.3.1", - "graceful-fs": "^4.1.2", - "rimraf": "~2.6.2", - "write": "^0.2.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "packages/reflux-store/node_modules/globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "extraneous": true, - "engines": { - "node": ">=0.10.0" - } - }, - "packages/reflux-store/node_modules/ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", - "extraneous": true - }, - "packages/reflux-store/node_modules/inquirer": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz", - "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=", - "extraneous": true, - "dependencies": { - "ansi-escapes": "^1.1.0", - "ansi-regex": "^2.0.0", - "chalk": "^1.0.0", - "cli-cursor": "^1.0.1", - "cli-width": "^2.0.0", - "figures": "^1.3.5", - "lodash": "^4.3.0", - "readline2": "^1.0.1", - "run-async": "^0.1.0", - "rx-lite": "^3.1.2", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.0", - "through": "^2.3.6" - } - }, - "packages/reflux-store/node_modules/is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "extraneous": true, - "dependencies": { - "number-is-nan": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "packages/reflux-store/node_modules/jsx-ast-utils": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-1.4.1.tgz", - "integrity": "sha1-OGchPo3Xm/Ho8jAMDPwe+xgsDfE=", - "extraneous": true, - "engines": { - "node": ">=4.0" - } - }, - "packages/reflux-store/node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "extraneous": true, - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "packages/reflux-store/node_modules/minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "extraneous": true - }, - "packages/reflux-store/node_modules/mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", - "extraneous": true, - "dependencies": { - "minimist": "0.0.8" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "packages/reflux-store/node_modules/ms": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", - "integrity": "sha512-lRLiIR9fSNpnP6TC4v8+4OU7oStC01esuNowdQ34L+Gk8e5Puoc88IqJ+XAY/B3Mn2ZKis8l8HX90oU8ivzUHg==", - "extraneous": true - }, - "packages/reflux-store/node_modules/onetime": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", - "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", - "extraneous": true, - "engines": { - "node": ">=0.10.0" - } - }, - "packages/reflux-store/node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "extraneous": true, - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "packages/reflux-store/node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "extraneous": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "packages/reflux-store/node_modules/progress": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", - "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", - "extraneous": true, - "engines": { - "node": ">=0.4.0" - } - }, - "packages/reflux-store/node_modules/restore-cursor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", - "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", - "extraneous": true, - "dependencies": { - "exit-hook": "^1.0.0", - "onetime": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "packages/reflux-store/node_modules/rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "extraneous": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "packages/reflux-store/node_modules/run-async": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", - "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=", - "extraneous": true, - "dependencies": { - "once": "^1.3.0" - } - }, - "packages/reflux-store/node_modules/slice-ansi": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", - "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", - "extraneous": true, - "engines": { - "node": ">=0.10.0" - } - }, - "packages/reflux-store/node_modules/string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "extraneous": true, - "dependencies": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "packages/reflux-store/node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "extraneous": true, - "engines": { - "node": ">=4" - } - }, - "packages/reflux-store/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "extraneous": true, - "engines": { - "node": ">=0.10.0" - } - }, - "packages/reflux-store/node_modules/table": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/table/-/table-3.8.3.tgz", - "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=", - "extraneous": true, - "dependencies": { - "ajv": "^4.7.0", - "ajv-keywords": "^1.0.0", - "chalk": "^1.1.1", - "lodash": "^4.0.0", - "slice-ansi": "0.0.4", - "string-width": "^2.0.0" - } - }, - "packages/reflux-store/node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "extraneous": true, - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "packages/reflux-store/node_modules/type-detect": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-1.0.0.tgz", - "integrity": "sha1-diIXzAbbJY7EiQihKY6LlRIejqI=", - "extraneous": true, - "engines": { - "node": "*" - } - }, - "packages/reflux-store/node_modules/write": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", - "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", - "extraneous": true, - "dependencies": { - "mkdirp": "^0.5.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "packages/schema-analysis": { "name": "@mongodb-js/compass-schema-analysis", "version": "1.0.0", From 70c865162461a7a417b400bf46d410f622ddbe24 Mon Sep 17 00:00:00 2001 From: "mongodb-devtools-bot[bot]" <189715634+mongodb-devtools-bot[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 08:17:01 +0000 Subject: [PATCH 47/76] chore: update AUTHORS, THIRD-PARTY-NOTICES, Security Test Summary --- THIRD-PARTY-NOTICES.md | 2 +- docs/tracking-plan.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/THIRD-PARTY-NOTICES.md b/THIRD-PARTY-NOTICES.md index 601506316d8..bfc20bedfcc 100644 --- a/THIRD-PARTY-NOTICES.md +++ b/THIRD-PARTY-NOTICES.md @@ -1,5 +1,5 @@ The following third-party software is used by and included in **Mongodb Compass**. -This document was automatically generated on Mon Jun 16 2025. +This document was automatically generated on Tue Jun 17 2025. ## List of dependencies diff --git a/docs/tracking-plan.md b/docs/tracking-plan.md index 5ff05d555e6..2cc169954c0 100644 --- a/docs/tracking-plan.md +++ b/docs/tracking-plan.md @@ -6,7 +6,7 @@ > the tracking plan for the specific Compass version you can use the following > URL: `https://github.com/mongodb-js/compass/blob//docs/tracking-plan.md` -Generated on Mon, Jun 16, 2025 +Generated on Tue, Jun 17, 2025 ## Table of Contents From 37c963355083f825ec8ee32a9397a06a046766dd Mon Sep 17 00:00:00 2001 From: Sergey Petushkov Date: Tue, 17 Jun 2025 14:15:20 +0200 Subject: [PATCH 48/76] chore(ci): extend smoketests timeouts to account for slow windows machines COMPASS-9464 (#7031) chore(ci): extend smoketests timeouts to account for slow windows machiens --- .github/workflows/test-installers.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-installers.yml b/.github/workflows/test-installers.yml index a4494080753..4de63cc6403 100644 --- a/.github/workflows/test-installers.yml +++ b/.github/workflows/test-installers.yml @@ -46,7 +46,10 @@ jobs: run: echo "[Evergreen Task](${{ github.event.inputs.evergreen_task_url }})" >> $GITHUB_STEP_SUMMARY test: name: ${{ matrix.package }} test ${{ matrix.test }} (${{ matrix.hadron-distribution }}) - timeout-minutes: 30 + # Windows specifically takes A TON of time to bootstrap itself before being + # able to run tests, so we're setting the timeout pretty high to account for + # that + timeout-minutes: 60 strategy: fail-fast: false matrix: @@ -174,7 +177,7 @@ jobs: runs-on: ${{ matrix.runs-on }} container: ${{ matrix.container }} env: - DEBUG: compass:smoketests:* + DEBUG: compass:smoketests:*,compass-e2e-tests:* steps: - name: Checkout uses: actions/checkout@v2 From 1b931f55fb2841e198bb7a4d06f51547a1a72ec5 Mon Sep 17 00:00:00 2001 From: Rhys Date: Tue, 17 Jun 2025 08:37:56 -0400 Subject: [PATCH 49/76] chore(e2e-tests): update interval on starting import to ensure we disconnect before fully importing (#7029) --- packages/compass-e2e-tests/tests/collection-import.test.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/compass-e2e-tests/tests/collection-import.test.ts b/packages/compass-e2e-tests/tests/collection-import.test.ts index 0ade8112284..58cdeeec56a 100644 --- a/packages/compass-e2e-tests/tests/collection-import.test.ts +++ b/packages/compass-e2e-tests/tests/collection-import.test.ts @@ -1471,7 +1471,11 @@ describe('Collection import', function () { // Confirm import. await browser.clickVisible(Selectors.ImportConfirm); // Wait for the in progress toast to appear. - await browser.$(Selectors.ImportToastAbort).waitForDisplayed(); + await browser.$(Selectors.ImportToastAbort).waitForDisplayed({ + // This is defaulted to 100, which can cause a race condition as the import could succeed. + // So we make it quicker. This could still cause flakes, but it is less likely. + interval: 1, + }); await browser.disconnectAll({ closeToasts: false }); From 02365932fe1b5f54c789a94caa32dd65645d0c0b Mon Sep 17 00:00:00 2001 From: gagik Date: Tue, 17 Jun 2025 14:38:26 +0200 Subject: [PATCH 50/76] fix: use the shared hook --- .../components/document-json-view-item.tsx | 55 +++---------------- .../components/document-list-view-item.tsx | 55 +++---------------- 2 files changed, 14 insertions(+), 96 deletions(-) diff --git a/packages/compass-crud/src/components/document-json-view-item.tsx b/packages/compass-crud/src/components/document-json-view-item.tsx index f89ccce2240..003f82ab73a 100644 --- a/packages/compass-crud/src/components/document-json-view-item.tsx +++ b/packages/compass-crud/src/components/document-json-view-item.tsx @@ -3,7 +3,7 @@ import type HadronDocument from 'hadron-document'; import { css, KeylineCard } from '@mongodb-js/compass-components'; import JSONEditor, { type JSONEditorProps } from './json-editor'; -import { useContextMenuItems } from '@mongodb-js/compass-components'; +import { useDocumentItemContextMenu } from './use-document-item-context-menu'; const keylineCardStyles = css({ overflow: 'hidden', @@ -41,53 +41,12 @@ const DocumentJsonViewItem: React.FC = ({ updateDocument, openInsertDocumentDialog, }) => { - const ref = useContextMenuItems([ - { - label: doc.expanded ? 'Collapse all fields' : 'Expand all fields', - onAction: () => { - if (doc.expanded) { - doc.collapse(); - } else { - doc.expand(); - } - }, - }, - ...(isEditable && !doc.editing - ? [ - { - label: 'Edit document', - onAction: () => { - doc.startEditing(); - }, - }, - ] - : []), - { - label: 'Copy document', - onAction: () => { - copyToClipboard?.(doc); - }, - }, - ...(isEditable - ? [ - { - label: 'Clone document...', - onAction: () => { - const clonedDoc = doc.generateObject({ - excludeInternalFields: true, - }); - openInsertDocumentDialog?.(clonedDoc, true); - }, - }, - { - label: 'Delete document', - onAction: () => { - doc.markForDeletion(); - }, - }, - ] - : []), - ]); + const ref = useDocumentItemContextMenu({ + doc, + isEditable, + copyToClipboard, + openInsertDocumentDialog, + }); return (
diff --git a/packages/compass-crud/src/components/document-list-view-item.tsx b/packages/compass-crud/src/components/document-list-view-item.tsx index ffe859d908f..288ce2b8621 100644 --- a/packages/compass-crud/src/components/document-list-view-item.tsx +++ b/packages/compass-crud/src/components/document-list-view-item.tsx @@ -2,7 +2,7 @@ import React from 'react'; import type HadronDocument from 'hadron-document'; import { KeylineCard } from '@mongodb-js/compass-components'; import Document, { type DocumentProps } from './document'; -import { useContextMenuItems } from '@mongodb-js/compass-components'; +import { useDocumentItemContextMenu } from './use-document-item-context-menu'; export type DocumentListViewItemProps = { doc: HadronDocument; @@ -33,53 +33,12 @@ const DocumentListViewItem: React.FC = ({ updateDocument, openInsertDocumentDialog, }) => { - const ref = useContextMenuItems([ - { - label: doc.expanded ? 'Collapse all fields' : 'Expand all fields', - onAction: () => { - if (doc.expanded) { - doc.collapse(); - } else { - doc.expand(); - } - }, - }, - ...(isEditable && !doc.editing - ? [ - { - label: 'Edit document', - onAction: () => { - doc.startEditing(); - }, - }, - ] - : []), - { - label: 'Copy document', - onAction: () => { - copyToClipboard?.(doc); - }, - }, - ...(isEditable - ? [ - { - label: 'Clone document...', - onAction: () => { - const clonedDoc = doc.generateObject({ - excludeInternalFields: true, - }); - openInsertDocumentDialog?.(clonedDoc, true); - }, - }, - { - label: 'Delete document', - onAction: () => { - doc.markForDeletion(); - }, - }, - ] - : []), - ]); + const ref = useDocumentItemContextMenu({ + doc, + isEditable, + copyToClipboard, + openInsertDocumentDialog, + }); return (
From 7ced8fcbd35187cc71bc60bef91b722e02f451a4 Mon Sep 17 00:00:00 2001 From: gagik Date: Mon, 16 Jun 2025 15:17:04 +0200 Subject: [PATCH 51/76] refactor: turn cell render into a functional component --- .../components/table-view/cell-renderer.tsx | 429 ++++++++---------- 1 file changed, 196 insertions(+), 233 deletions(-) diff --git a/packages/compass-crud/src/components/table-view/cell-renderer.tsx b/packages/compass-crud/src/components/table-view/cell-renderer.tsx index 86f7051852d..7e60bd27a0d 100644 --- a/packages/compass-crud/src/components/table-view/cell-renderer.tsx +++ b/packages/compass-crud/src/components/table-view/cell-renderer.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { BSONValue, css, @@ -10,7 +9,6 @@ import { withDarkMode, } from '@mongodb-js/compass-components'; import { Element } from 'hadron-document'; -import type { ICellRendererReactComp } from 'ag-grid-react'; import type { ICellRendererParams } from 'ag-grid-community'; import type { GridActions, TableHeaderType } from '../../stores/grid-store'; import type { CrudActions } from '../../stores/crud-store'; @@ -95,168 +93,148 @@ export type CellRendererProps = Omit & { /** * The custom cell renderer that renders a cell in the table view. */ -class CellRenderer - extends React.Component - implements ICellRendererReactComp -{ - element: Element; - isEmpty: boolean; - isDeleted: boolean; - editable: boolean; - - constructor(props: CellRendererProps) { - super(props); - - this.isEmpty = props.value === undefined || props.value === null; - this.isDeleted = false; - this.element = props.value; - +const CellRenderer: React.FC = ({ + value, + context, + column, + node, + parentType, + elementAdded, + elementRemoved, + elementTypeChanged, + drillDown, + api, + darkMode, +}) => { + const element = value as Element; + + const isEmpty = element === undefined || element === null; + const [isDeleted, setIsDeleted] = React.useState(false); + const [, forceUpdate] = React.useReducer((x) => x + 1, 0); + + const isEditable = React.useMemo(() => { /* Can't get the editable() function from here, so have to reevaluate */ - this.editable = true; - if (props.context.path.length > 0 && props.column.getColId() !== '$_id') { - const parent = props.node.data.hadronDocument.getChild( - props.context.path - ); - if ( - !parent || - (props.parentType && parent.currentType !== props.parentType) - ) { - this.editable = false; + let editable = true; + if (context.path.length > 0 && column.getColId() !== '$_id') { + const parent = node.data.hadronDocument.getChild(context.path); + if (!parent || (parentType && parent.currentType !== parentType)) { + editable = false; } else if (parent.currentType === 'Array') { let maxKey = 0; if (parent.elements.lastElement) { maxKey = +parent.elements.lastElement.currentKey + 1; } - if (+props.column.getColId() > maxKey) { - this.editable = false; + if (+column.getColId() > maxKey) { + editable = false; } } } - } - - componentDidMount() { - if (!this.isEmpty) { - this.subscribeElementEvents(); - } - } - - componentWillUnmount() { - if (!this.isEmpty) { - this.unsubscribeElementEvents(); - } - } - - subscribeElementEvents() { - this.element.on(Element.Events.Added, this.handleElementEvent); - this.element.on(Element.Events.Converted, this.handleElementEvent); - this.element.on(Element.Events.Edited, this.handleElementEvent); - this.element.on(Element.Events.Reverted, this.handleElementEvent); - } - - unsubscribeElementEvents() { - this.element.removeListener(Element.Events.Added, this.handleElementEvent); - this.element.removeListener( - Element.Events.Converted, - this.handleElementEvent - ); - this.element.removeListener(Element.Events.Edited, this.handleElementEvent); - this.element.removeListener( - Element.Events.Reverted, - this.handleElementEvent - ); - } - - handleElementEvent = () => { - this.forceUpdate(); - }; - - handleUndo = (event: React.MouseEvent) => { - event.stopPropagation(); - const oid = this.props.node.data.hadronDocument.getStringId(); - if (this.element.isAdded()) { - this.isDeleted = true; - const isArray = - !this.element.parent?.isRoot() && - this.element.parent?.currentType === 'Array'; - this.props.elementRemoved(String(this.element.currentKey), oid, isArray); - } else if (this.element.isRemoved()) { - this.props.elementAdded( - String(this.element.currentKey), - this.element.currentType, - oid - ); - } else { - this.props.elementTypeChanged( - String(this.element.currentKey), - this.element.type, - oid - ); + return editable; + }, [context.path, column, node.data.hadronDocument, parentType]); + + const handleElementEvent = React.useCallback(() => { + forceUpdate(); + }, []); + + // Subscribe to element events + React.useEffect(() => { + if (!isEmpty && element) { + element.on(Element.Events.Added, handleElementEvent); + element.on(Element.Events.Converted, handleElementEvent); + element.on(Element.Events.Edited, handleElementEvent); + element.on(Element.Events.Reverted, handleElementEvent); + + return () => { + element.removeListener(Element.Events.Added, handleElementEvent); + element.removeListener(Element.Events.Converted, handleElementEvent); + element.removeListener(Element.Events.Edited, handleElementEvent); + element.removeListener(Element.Events.Reverted, handleElementEvent); + }; } - this.element.revert(); - }; - - handleDrillDown(event: React.MouseEvent) { - event.stopPropagation(); - this.props.drillDown(this.props.node.data.hadronDocument, this.element); - } - - handleClicked() { - if (this.props.node.data.state === 'editing') { - this.props.api.startEditingCell({ - rowIndex: this.props.node.rowIndex, - colKey: this.props.column.getColId(), + }, [isEmpty, element, handleElementEvent]); + + const handleUndo = React.useCallback( + (event: React.MouseEvent) => { + event.stopPropagation(); + const oid: string = node.data.hadronDocument.getStringId(); + if (element.isAdded()) { + setIsDeleted(true); + const isArray = + !element.parent?.isRoot() && element.parent?.currentType === 'Array'; + elementRemoved(String(element.currentKey), oid, isArray); + } else if (element.isRemoved()) { + elementAdded(String(element.currentKey), element.currentType, oid); + } else { + elementTypeChanged(String(element.currentKey), element.type, oid); + } + element.revert(); + }, + [ + element, + node.data.hadronDocument, + elementRemoved, + elementAdded, + elementTypeChanged, + ] + ); + + const handleDrillDown = React.useCallback( + (event: React.MouseEvent) => { + event.stopPropagation(); + drillDown(node.data.hadronDocument, element); + }, + [drillDown, node.data.hadronDocument, element] + ); + + const handleClicked = React.useCallback(() => { + if (node.data.state === 'editing') { + api.startEditingCell({ + rowIndex: node.rowIndex, + colKey: column.getColId(), }); } - } + }, [node, api, column]); - refresh() { - return true; - } - - renderInvalidCell() { - let valueClass = `${VALUE_CLASS}-is-${this.element.currentType.toLowerCase()}`; + const renderInvalidCell = React.useCallback(() => { + let valueClass = `${VALUE_CLASS}-is-${element.currentType.toLowerCase()}`; valueClass = `${valueClass} ${INVALID_VALUE}`; - /* Return internal div because invalid cells should only hightlight text? */ - - return
{this.element.currentValue}
; - } + return
{element.currentValue}
; + }, [element]); - getLength(): number | undefined { - if (this.element.currentType === 'Object') { - return Object.keys(this.element.generateObject() as object).length; + const getLength = React.useCallback((): number | undefined => { + if (element.currentType === 'Object') { + return Object.keys(element.generateObject() as object).length; } - if (this.element.currentType === 'Array') { - return this.element.elements!.size; + if (element.currentType === 'Array' && element.elements) { + return element.elements.size; } - } + }, [element]); - renderValidCell() { + const renderValidCell = React.useCallback(() => { let className = VALUE_BASE; - let element: string | JSX.Element = ''; - if (this.element.isAdded()) { + let elementContent: string | JSX.Element = ''; + if (element.isAdded()) { className = `${className} ${VALUE_BASE}-${ADDED}`; - } else if (this.element.isEdited()) { + } else if (element.isEdited()) { className = `${className} ${VALUE_BASE}-${EDITED}`; } - if (this.element.currentType === 'Object') { - element = `{} ${this.getLength() as number} fields`; - } else if (this.element.currentType === 'Array') { - element = `[] ${this.getLength() as number} elements`; + if (element.currentType === 'Object') { + elementContent = `{} ${getLength() as number} fields`; + } else if (element.currentType === 'Array') { + elementContent = `[] ${getLength() as number} elements`; } else { - element = ( - + elementContent = ( + //@ts-expect-error Types for this are currently not consistent + ); } return (
- {this.props.value.decrypted && ( + {element.decrypted && ( )} - {element} + {elementContent}
); - } - - renderUndo(canUndo: boolean, canExpand: boolean) { - let undoButtonClass = `${BUTTON_CLASS} ${BUTTON_CLASS}-undo`; - if (canUndo && canExpand) { - undoButtonClass = `${undoButtonClass} ${BUTTON_CLASS}-left`; - } + }, [element, getLength]); - if (!canUndo) { - return null; - } - return ( - - - - ); - } + const renderUndo = React.useCallback( + (canUndo: boolean, canExpand: boolean) => { + let undoButtonClass = `${BUTTON_CLASS} ${BUTTON_CLASS}-undo`; + if (canUndo && canExpand) { + undoButtonClass = `${undoButtonClass} ${BUTTON_CLASS}-left`; + } - renderExpand(canExpand: boolean) { - if (!canExpand) { - return null; - } - return ( - + if (!canUndo) { + return null; + } + return ( - + - - ); - } - - render() { - let element; - let className = BEM_BASE; - let canUndo = false; - let canExpand = false; - - if (!this.editable) { - element = ''; - className = `${className}-${UNEDITABLE}`; - } else if (this.isEmpty || this.isDeleted) { - element = 'No field'; - className = `${className}-${EMPTY}`; - } else if (!this.element.isCurrentTypeValid()) { - element = this.renderInvalidCell(); - className = `${className}-${INVALID}`; + ); + }, + [handleUndo] + ); + + const renderExpand = React.useCallback( + (canExpand: boolean) => { + if (!canExpand) { + return null; + } + return ( + + + + + + ); + }, + [handleDrillDown] + ); + + // Render logic + let elementToRender; + let className = BEM_BASE; + let canUndo = false; + let canExpand = false; + + if (!isEditable) { + elementToRender = ''; + className = `${className}-${UNEDITABLE}`; + } else if (isEmpty || isDeleted) { + elementToRender = 'No field'; + className = `${className}-${EMPTY}`; + } else if (!element.isCurrentTypeValid()) { + elementToRender = renderInvalidCell(); + className = `${className}-${INVALID}`; + canUndo = true; + } else if (element.isRemoved()) { + elementToRender = 'Deleted field'; + className = `${className}-${DELETED}`; + canUndo = true; + } else { + elementToRender = renderValidCell(); + if (element.isAdded()) { + className = `${className}-${ADDED}`; canUndo = true; - } else if (this.element.isRemoved()) { - element = 'Deleted field'; - className = `${className}-${DELETED}`; + } else if (element.isModified()) { + className = `${className}-${EDITED}`; canUndo = true; - } else { - element = this.renderValidCell(); - if (this.element.isAdded()) { - className = `${className}-${ADDED}`; - canUndo = true; - } else if (this.element.isModified()) { - className = `${className}-${EDITED}`; - canUndo = true; - } - canExpand = - this.element.currentType === 'Object' || - this.element.currentType === 'Array'; } + canExpand = + element.currentType === 'Object' || element.currentType === 'Array'; + } - return ( - // `ag-grid` renders this component outside of the context chain - // so we re-supply the dark mode theme here. - + return ( + // `ag-grid` renders this component outside of the context chain + // so we re-supply the dark mode theme here. + +
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/interactive-supports-focus*/} -
- {this.renderUndo(canUndo, canExpand)} - {this.renderExpand(canExpand)} - {element} +
+ {renderUndo(canUndo, canExpand)} + {renderExpand(canExpand)} + {elementToRender}
- - ); - } - - static propTypes = { - api: PropTypes.any, - value: PropTypes.any, - node: PropTypes.any, - column: PropTypes.any, - context: PropTypes.any, - parentType: PropTypes.any.isRequired, - elementAdded: PropTypes.func.isRequired, - elementRemoved: PropTypes.func.isRequired, - elementTypeChanged: PropTypes.func.isRequired, - drillDown: PropTypes.func.isRequired, - tz: PropTypes.string.isRequired, - darkMode: PropTypes.bool, - }; - - static displayName = 'CellRenderer'; -} +
+ + ); +}; export default withDarkMode(CellRenderer); From 5d932a07330665b831c56193a1236a8dd409a0b3 Mon Sep 17 00:00:00 2001 From: gagik Date: Mon, 16 Jun 2025 18:21:31 +0200 Subject: [PATCH 52/76] refactor: untangle usage of nested if statements --- .../components/table-view/cell-renderer.tsx | 398 +++++++++++------- 1 file changed, 237 insertions(+), 161 deletions(-) diff --git a/packages/compass-crud/src/components/table-view/cell-renderer.tsx b/packages/compass-crud/src/components/table-view/cell-renderer.tsx index 7e60bd27a0d..4af6d3ada32 100644 --- a/packages/compass-crud/src/components/table-view/cell-renderer.tsx +++ b/packages/compass-crud/src/components/table-view/cell-renderer.tsx @@ -1,4 +1,10 @@ -import React from 'react'; +import React, { + useMemo, + useCallback, + useEffect, + useReducer, + useState, +} from 'react'; import { BSONValue, css, @@ -8,7 +14,7 @@ import { spacing, withDarkMode, } from '@mongodb-js/compass-components'; -import { Element } from 'hadron-document'; +import { type Document, Element } from 'hadron-document'; import type { ICellRendererParams } from 'ag-grid-community'; import type { GridActions, TableHeaderType } from '../../stores/grid-store'; import type { CrudActions } from '../../stores/crud-store'; @@ -59,6 +65,11 @@ const UNEDITABLE = 'is-uneditable'; */ const INVALID = 'is-invalid'; +/** + * The valid constant. + */ +const VALID = 'valid'; + /** * The deleted constant. */ @@ -79,6 +90,141 @@ const decrypdedIconStyles = css({ display: 'flex', }); +interface CellContentProps { + element: Element | undefined | null; + cellState: + | typeof UNEDITABLE + | typeof EMPTY + | typeof INVALID + | typeof DELETED + | typeof ADDED + | typeof EDITED + | typeof VALID; + onUndo: (event: React.MouseEvent) => void; + onExpand: (event: React.MouseEvent) => void; +} + +const CellContent: React.FC = ({ + element, + cellState, + onUndo, + onExpand, +}) => { + const [, forceUpdate] = useReducer((x: number) => x + 1, 0); + const isEmpty = element === undefined || element === null; + const handleElementEvent = useCallback(() => { + forceUpdate(); + }, []); + + // Subscribe to element events + useEffect(() => { + if (!isEmpty && element) { + element.on(Element.Events.Added, handleElementEvent); + element.on(Element.Events.Converted, handleElementEvent); + element.on(Element.Events.Edited, handleElementEvent); + element.on(Element.Events.Reverted, handleElementEvent); + + return () => { + element.removeListener(Element.Events.Added, handleElementEvent); + element.removeListener(Element.Events.Converted, handleElementEvent); + element.removeListener(Element.Events.Edited, handleElementEvent); + element.removeListener(Element.Events.Reverted, handleElementEvent); + }; + } + }, [isEmpty, element, handleElementEvent]); + + const elementLength = useMemo((): number | undefined => { + if (!element) { + return undefined; + } + + if (element.currentType === 'Object') { + return Object.keys(element.generateObject() as object).length; + } + if (element.currentType === 'Array' && element.elements) { + return element.elements.size; + } + }, [element]); + + const renderContent = useCallback(() => { + if (cellState === EMPTY || !element) { + return 'No field'; + } + + if (cellState === UNEDITABLE) { + return ''; + } + + if (cellState === DELETED) { + return 'Deleted field'; + } + + if (cellState === INVALID) { + let valueClass = `${VALUE_CLASS}-is-${element.currentType.toLowerCase()}`; + valueClass = `${valueClass} ${INVALID_VALUE}`; + + return
{element.currentValue}
; + } + + let className = VALUE_BASE; + let elementContent: string | JSX.Element = ''; + if (cellState === ADDED || cellState === EDITED) { + className = `${className} ${VALUE_BASE}-${cellState}`; + } + + const isArrayOrObject = + element.currentType === 'Array' || element.currentType === 'Object'; + + if (elementLength !== undefined && isArrayOrObject) { + if (element.currentType === 'Object') { + elementContent = `{} ${elementLength} fields`; + } else if (element.currentType === 'Array') { + elementContent = `[] ${elementLength} elements`; + } + } else { + elementContent = ( + //@ts-expect-error Types for this are currently not consistent + + ); + } + + return ( +
+
+ {element.decrypted && ( + + + + )} + {elementContent} +
+
+ ); + }, [element, elementLength, cellState]); + + const canUndo = + cellState === ADDED || + cellState === EDITED || + cellState === INVALID || + cellState === DELETED; + + const canExpand = + (cellState === VALID || cellState === ADDED || cellState === EDITED) && + (element?.currentType === 'Object' || element?.currentType === 'Array'); + + return ( + <> + {canUndo && } + {canExpand && } + {renderContent()} + + ); +}; + export type CellRendererProps = Omit & { context: GridContext; parentType: TableHeaderType; @@ -106,13 +252,12 @@ const CellRenderer: React.FC = ({ api, darkMode, }) => { - const element = value as Element; + const element = value as Element | undefined | null; const isEmpty = element === undefined || element === null; - const [isDeleted, setIsDeleted] = React.useState(false); - const [, forceUpdate] = React.useReducer((x) => x + 1, 0); + const [isDeleted, setIsDeleted] = useState(false); - const isEditable = React.useMemo(() => { + const isEditable = useMemo(() => { /* Can't get the editable() function from here, so have to reevaluate */ let editable = true; if (context.path.length > 0 && column.getColId() !== '$_id') { @@ -132,30 +277,12 @@ const CellRenderer: React.FC = ({ return editable; }, [context.path, column, node.data.hadronDocument, parentType]); - const handleElementEvent = React.useCallback(() => { - forceUpdate(); - }, []); - - // Subscribe to element events - React.useEffect(() => { - if (!isEmpty && element) { - element.on(Element.Events.Added, handleElementEvent); - element.on(Element.Events.Converted, handleElementEvent); - element.on(Element.Events.Edited, handleElementEvent); - element.on(Element.Events.Reverted, handleElementEvent); - - return () => { - element.removeListener(Element.Events.Added, handleElementEvent); - element.removeListener(Element.Events.Converted, handleElementEvent); - element.removeListener(Element.Events.Edited, handleElementEvent); - element.removeListener(Element.Events.Reverted, handleElementEvent); - }; - } - }, [isEmpty, element, handleElementEvent]); - - const handleUndo = React.useCallback( + const handleUndo = useCallback( (event: React.MouseEvent) => { event.stopPropagation(); + if (!element) { + return; + } const oid: string = node.data.hadronDocument.getStringId(); if (element.isAdded()) { setIsDeleted(true); @@ -178,15 +305,18 @@ const CellRenderer: React.FC = ({ ] ); - const handleDrillDown = React.useCallback( + const handleDrillDown = useCallback( (event: React.MouseEvent) => { event.stopPropagation(); - drillDown(node.data.hadronDocument, element); + if (!element) { + return; + } + drillDown(node.data.hadronDocument as Document, element); }, [drillDown, node.data.hadronDocument, element] ); - const handleClicked = React.useCallback(() => { + const handleClicked = useCallback(() => { if (node.data.state === 'editing') { api.startEditingCell({ rowIndex: node.rowIndex, @@ -195,138 +325,30 @@ const CellRenderer: React.FC = ({ } }, [node, api, column]); - const renderInvalidCell = React.useCallback(() => { - let valueClass = `${VALUE_CLASS}-is-${element.currentType.toLowerCase()}`; - valueClass = `${valueClass} ${INVALID_VALUE}`; - - return
{element.currentValue}
; - }, [element]); - - const getLength = React.useCallback((): number | undefined => { - if (element.currentType === 'Object') { - return Object.keys(element.generateObject() as object).length; - } - if (element.currentType === 'Array' && element.elements) { - return element.elements.size; - } - }, [element]); - - const renderValidCell = React.useCallback(() => { - let className = VALUE_BASE; - let elementContent: string | JSX.Element = ''; - if (element.isAdded()) { - className = `${className} ${VALUE_BASE}-${ADDED}`; - } else if (element.isEdited()) { - className = `${className} ${VALUE_BASE}-${EDITED}`; - } - - if (element.currentType === 'Object') { - elementContent = `{} ${getLength() as number} fields`; - } else if (element.currentType === 'Array') { - elementContent = `[] ${getLength() as number} elements`; - } else { - elementContent = ( - //@ts-expect-error Types for this are currently not consistent - - ); - } - - return ( -
-
- {element.decrypted && ( - - - - )} - {elementContent} -
-
- ); - }, [element, getLength]); - - const renderUndo = React.useCallback( - (canUndo: boolean, canExpand: boolean) => { - let undoButtonClass = `${BUTTON_CLASS} ${BUTTON_CLASS}-undo`; - if (canUndo && canExpand) { - undoButtonClass = `${undoButtonClass} ${BUTTON_CLASS}-left`; - } - - if (!canUndo) { - return null; - } - return ( - - - - ); - }, - [handleUndo] - ); - - const renderExpand = React.useCallback( - (canExpand: boolean) => { - if (!canExpand) { - return null; - } - return ( - - - - - - ); - }, - [handleDrillDown] - ); - - // Render logic - let elementToRender; - let className = BEM_BASE; - let canUndo = false; - let canExpand = false; + // Determine cell state + let cellState: + | typeof UNEDITABLE + | typeof EMPTY + | typeof INVALID + | typeof DELETED + | typeof ADDED + | typeof EDITED + | typeof VALID; if (!isEditable) { - elementToRender = ''; - className = `${className}-${UNEDITABLE}`; + cellState = UNEDITABLE; } else if (isEmpty || isDeleted) { - elementToRender = 'No field'; - className = `${className}-${EMPTY}`; + cellState = EMPTY; } else if (!element.isCurrentTypeValid()) { - elementToRender = renderInvalidCell(); - className = `${className}-${INVALID}`; - canUndo = true; + cellState = INVALID; } else if (element.isRemoved()) { - elementToRender = 'Deleted field'; - className = `${className}-${DELETED}`; - canUndo = true; + cellState = DELETED; + } else if (element.isAdded()) { + cellState = ADDED; + } else if (element.isModified()) { + cellState = EDITED; } else { - elementToRender = renderValidCell(); - if (element.isAdded()) { - className = `${className}-${ADDED}`; - canUndo = true; - } else if (element.isModified()) { - className = `${className}-${EDITED}`; - canUndo = true; - } - canExpand = - element.currentType === 'Object' || element.currentType === 'Array'; + cellState = VALID; } return ( @@ -335,10 +357,19 @@ const CellRenderer: React.FC = ({
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/interactive-supports-focus*/} -
- {renderUndo(canUndo, canExpand)} - {renderExpand(canExpand)} - {elementToRender} +
+
@@ -346,3 +377,48 @@ const CellRenderer: React.FC = ({ }; export default withDarkMode(CellRenderer); + +interface CellUndoButtonProps { + alignLeft: boolean; + onClick: (event: React.MouseEvent) => void; +} + +const CellUndoButton: React.FC = ({ + alignLeft, + onClick, +}) => { + let undoButtonClass = `${BUTTON_CLASS} ${BUTTON_CLASS}-undo`; + if (alignLeft) { + undoButtonClass = `${undoButtonClass} ${BUTTON_CLASS}-left`; + } + + return ( + + + + ); +}; + +interface CellExpandButtonProps { + onClick: (event: React.MouseEvent) => void; +} + +const CellExpandButton: React.FC = ({ onClick }) => { + return ( + + + + ); +}; From fc6068e9c7409d1788d14b64cbda16fe3415ee58 Mon Sep 17 00:00:00 2001 From: gagik Date: Tue, 17 Jun 2025 13:13:53 +0200 Subject: [PATCH 53/76] fix: use CellState wherever possible --- .../components/table-view/cell-renderer.tsx | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/packages/compass-crud/src/components/table-view/cell-renderer.tsx b/packages/compass-crud/src/components/table-view/cell-renderer.tsx index 4af6d3ada32..32ca17e889f 100644 --- a/packages/compass-crud/src/components/table-view/cell-renderer.tsx +++ b/packages/compass-crud/src/components/table-view/cell-renderer.tsx @@ -277,6 +277,32 @@ const CellRenderer: React.FC = ({ return editable; }, [context.path, column, node.data.hadronDocument, parentType]); + // Determine cell state + let cellState: + | typeof UNEDITABLE + | typeof EMPTY + | typeof INVALID + | typeof DELETED + | typeof ADDED + | typeof EDITED + | typeof VALID; + + if (!isEditable) { + cellState = UNEDITABLE; + } else if (isEmpty || isDeleted) { + cellState = EMPTY; + } else if (!element.isCurrentTypeValid()) { + cellState = INVALID; + } else if (element.isRemoved()) { + cellState = DELETED; + } else if (element.isAdded()) { + cellState = ADDED; + } else if (element.isModified()) { + cellState = EDITED; + } else { + cellState = VALID; + } + const handleUndo = useCallback( (event: React.MouseEvent) => { event.stopPropagation(); @@ -284,12 +310,12 @@ const CellRenderer: React.FC = ({ return; } const oid: string = node.data.hadronDocument.getStringId(); - if (element.isAdded()) { + if (cellState === ADDED) { setIsDeleted(true); const isArray = !element.parent?.isRoot() && element.parent?.currentType === 'Array'; elementRemoved(String(element.currentKey), oid, isArray); - } else if (element.isRemoved()) { + } else if (cellState === DELETED) { elementAdded(String(element.currentKey), element.currentType, oid); } else { elementTypeChanged(String(element.currentKey), element.type, oid); @@ -325,32 +351,6 @@ const CellRenderer: React.FC = ({ } }, [node, api, column]); - // Determine cell state - let cellState: - | typeof UNEDITABLE - | typeof EMPTY - | typeof INVALID - | typeof DELETED - | typeof ADDED - | typeof EDITED - | typeof VALID; - - if (!isEditable) { - cellState = UNEDITABLE; - } else if (isEmpty || isDeleted) { - cellState = EMPTY; - } else if (!element.isCurrentTypeValid()) { - cellState = INVALID; - } else if (element.isRemoved()) { - cellState = DELETED; - } else if (element.isAdded()) { - cellState = ADDED; - } else if (element.isModified()) { - cellState = EDITED; - } else { - cellState = VALID; - } - return ( // `ag-grid` renders this component outside of the context chain // so we re-supply the dark mode theme here. From 16bf358ced1b806d1b10df50c24615b5d8d69043 Mon Sep 17 00:00:00 2001 From: gagik Date: Tue, 17 Jun 2025 14:37:08 +0200 Subject: [PATCH 54/76] wip --- .../components/table-view/cell-renderer.tsx | 71 ++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/packages/compass-crud/src/components/table-view/cell-renderer.tsx b/packages/compass-crud/src/components/table-view/cell-renderer.tsx index 32ca17e889f..dd423f889f3 100644 --- a/packages/compass-crud/src/components/table-view/cell-renderer.tsx +++ b/packages/compass-crud/src/components/table-view/cell-renderer.tsx @@ -13,8 +13,10 @@ import { LeafyGreenProvider, spacing, withDarkMode, + useContextMenuItems, } from '@mongodb-js/compass-components'; import { type Document, Element } from 'hadron-document'; +import { objectToIdiomaticEJSON } from 'hadron-document'; import type { ICellRendererParams } from 'ag-grid-community'; import type { GridActions, TableHeaderType } from '../../stores/grid-store'; import type { CrudActions } from '../../stores/crud-store'; @@ -257,6 +259,72 @@ const CellRenderer: React.FC = ({ const isEmpty = element === undefined || element === null; const [isDeleted, setIsDeleted] = useState(false); + // Helper function to check if a string is a URL + const isValidUrl = useCallback((str: string): boolean => { + try { + const url = new URL(str); + return url.protocol === 'http:' || url.protocol === 'https:'; + } catch { + return false; + } + }, []); + + // Add context menu functionality + const contextMenuRef = useContextMenuItems([ + ...(element && !isEmpty + ? [ + { + label: 'Copy field & value', + onAction: () => { + const fieldName = column.getColId(); + const fieldStr = `${fieldName}: ${objectToIdiomaticEJSON( + element.currentValue + )}`; + void navigator.clipboard.writeText(fieldStr); + }, + }, + ] + : []), + ...(element && + element.currentType === 'String' && + isValidUrl(element.currentValue) + ? [ + { + label: 'Open URL in browser', + onAction: () => { + window.open(element.currentValue, '_blank', 'noopener'); + }, + }, + ] + : []), + ...(element && + (element.currentType === 'Object' || element.currentType === 'Array') + ? [ + { + label: 'Expand field', + onAction: () => { + handleDrillDown({ + stopPropagation: () => {}, + } as React.MouseEvent); + }, + }, + ] + : []), + ...(cellState === ADDED || + cellState === EDITED || + cellState === INVALID || + cellState === DELETED + ? [ + { + label: 'Undo changes', + onAction: () => { + handleUndo({ stopPropagation: () => {} } as React.MouseEvent); + }, + }, + ] + : []), + ]); + const isEditable = useMemo(() => { /* Can't get the editable() function from here, so have to reevaluate */ let editable = true; @@ -328,6 +396,7 @@ const CellRenderer: React.FC = ({ elementRemoved, elementAdded, elementTypeChanged, + cellState, ] ); @@ -355,7 +424,7 @@ const CellRenderer: React.FC = ({ // `ag-grid` renders this component outside of the context chain // so we re-supply the dark mode theme here. -
+
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/interactive-supports-focus*/}
Date: Tue, 17 Jun 2025 08:39:04 -0400 Subject: [PATCH 55/76] fix(aggregations): stage description tooltip overflow COMPASS-9463 (#7028) --- .../components/stage-preview/stage-preview-header.tsx | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/compass-aggregations/src/components/stage-preview/stage-preview-header.tsx b/packages/compass-aggregations/src/components/stage-preview/stage-preview-header.tsx index df18e022c9a..f133bf46bcb 100644 --- a/packages/compass-aggregations/src/components/stage-preview/stage-preview-header.tsx +++ b/packages/compass-aggregations/src/components/stage-preview/stage-preview-header.tsx @@ -1,16 +1,10 @@ import React from 'react'; import { connect } from 'react-redux'; -import { Body, Link, Tooltip, css } from '@mongodb-js/compass-components'; +import { Body, Link, Tooltip } from '@mongodb-js/compass-components'; import type { RootState } from '../../modules'; import { getStageInfo } from '../../utils/stage'; import type { StoreStage } from '../../modules/pipeline-builder/stage-editor'; -const toolbarTextStyles = css({ - whiteSpace: 'nowrap', - overflow: 'hidden', - textOverflow: 'ellipsis', -}); - const OperatorLink: React.FunctionComponent<{ stageOperator: string; description?: string; @@ -56,7 +50,7 @@ function StagePreviewHeader({ return null; } return ( - + {destination ? ( `Documents will be saved to ${destination}.` ) : ( From 1e4eeed507238b792d29b5dbdf7d8567b82346c3 Mon Sep 17 00:00:00 2001 From: Rhys Date: Tue, 17 Jun 2025 10:50:14 -0400 Subject: [PATCH 56/76] feat(workspaces): move tab rendering to the plugins COMPASS-9413 (#6997) --- packages/compass-collection/src/index.ts | 42 +-- .../src/plugin-tab-title.tsx | 90 ++++++ .../src/components/workspace-tabs/tab.tsx | 30 +- .../workspace-tabs/workspace-tabs.spec.tsx | 19 +- .../workspace-tabs/workspace-tabs.tsx | 47 ++- packages/compass-components/src/index.ts | 6 +- .../compass-data-modeling/src/index.spec.tsx | 12 +- packages/compass-data-modeling/src/index.ts | 46 +-- .../src/plugin-tab-title.tsx | 37 +++ .../src/index.spec.tsx | 18 +- .../src/index.ts | 52 ++-- .../src/plugin-tab-title.tsx | 22 ++ .../src/stores/index.ts | 6 +- packages/compass-serverstats/src/index.ts | 67 +++-- .../src/plugin-tab-title.tsx | 38 +++ packages/compass-shell/src/index.ts | 42 +-- .../compass-shell/src/plugin-tab-title.tsx | 35 +++ packages/compass-shell/src/plugin.spec.tsx | 11 +- .../multiple-connections/sidebar.spec.tsx | 14 +- packages/compass-welcome/src/index.ts | 32 ++- .../compass-welcome/src/plugin-tab-title.tsx | 22 ++ .../src/components/index.tsx | 2 +- .../components/workspace-close-handler.tsx | 2 +- .../workspace-tab-context-provider.tsx | 42 ++- .../src/components/workspaces-provider.tsx | 35 +-- .../src/components/workspaces.tsx | 267 ++++++------------ .../compass-workspaces/src/index.spec.tsx | 82 ++++-- packages/compass-workspaces/src/index.ts | 8 +- packages/compass-workspaces/src/provider.tsx | 8 +- .../src/stores/workspaces.spec.ts | 2 +- .../src/stores/workspaces.ts | 31 +- packages/compass-workspaces/src/types.ts | 34 ++- .../src/collections-plugin-title.tsx | 42 +++ .../src/collections-plugin.spec.tsx | 10 +- .../src/collections-plugin.tsx | 10 +- .../src/databases-plugin-title.tsx | 35 +++ .../src/databases-plugin.spec.tsx | 10 +- .../src/databases-plugin.tsx | 10 +- packages/databases-collections/src/index.ts | 33 ++- .../src/stores/databases-store.ts | 5 +- 40 files changed, 885 insertions(+), 471 deletions(-) create mode 100644 packages/compass-collection/src/plugin-tab-title.tsx create mode 100644 packages/compass-data-modeling/src/plugin-tab-title.tsx create mode 100644 packages/compass-saved-aggregations-queries/src/plugin-tab-title.tsx create mode 100644 packages/compass-serverstats/src/plugin-tab-title.tsx create mode 100644 packages/compass-shell/src/plugin-tab-title.tsx create mode 100644 packages/compass-welcome/src/plugin-tab-title.tsx create mode 100644 packages/databases-collections/src/collections-plugin-title.tsx create mode 100644 packages/databases-collections/src/databases-plugin-title.tsx diff --git a/packages/compass-collection/src/index.ts b/packages/compass-collection/src/index.ts index 966639c078c..4f754f76b0b 100644 --- a/packages/compass-collection/src/index.ts +++ b/packages/compass-collection/src/index.ts @@ -1,3 +1,4 @@ +import React from 'react'; import CollectionTab from './components/collection-tab'; import { activatePlugin as activateCollectionTabPlugin } from './stores/collection-tab'; import { registerHadronPlugin } from 'hadron-app-registry'; @@ -7,27 +8,32 @@ import { type DataService, } from '@mongodb-js/compass-connections/provider'; import { collectionModelLocator } from '@mongodb-js/compass-app-stores/provider'; -import type { WorkspaceComponent } from '@mongodb-js/compass-workspaces'; +import type { WorkspacePlugin } from '@mongodb-js/compass-workspaces'; import { workspacesServiceLocator } from '@mongodb-js/compass-workspaces/provider'; +import { + CollectionWorkspaceTitle, + CollectionPluginTitleComponent, +} from './plugin-tab-title'; -export const CollectionTabPlugin = registerHadronPlugin( - { - name: 'CollectionTab', - component: CollectionTab, - activate: activateCollectionTabPlugin, - }, - { - dataService: dataServiceLocator as DataServiceLocator, - collection: collectionModelLocator, - workspaces: workspacesServiceLocator, - } -); - -export const WorkspaceTab: WorkspaceComponent<'Collection'> = { - name: 'Collection' as const, - component: CollectionTabPlugin, +export const WorkspaceTab: WorkspacePlugin = { + name: CollectionWorkspaceTitle, + provider: registerHadronPlugin( + { + name: CollectionWorkspaceTitle, + component: function CollectionProvider({ children }) { + return React.createElement(React.Fragment, null, children); + }, + activate: activateCollectionTabPlugin, + }, + { + dataService: dataServiceLocator as DataServiceLocator, + collection: collectionModelLocator, + workspaces: workspacesServiceLocator, + } + ), + content: CollectionTab, + header: CollectionPluginTitleComponent, }; -export default CollectionTabPlugin; export type { CollectionTabPluginMetadata } from './modules/collection-tab'; export { CollectionTabsProvider } from './components/collection-tab-provider'; diff --git a/packages/compass-collection/src/plugin-tab-title.tsx b/packages/compass-collection/src/plugin-tab-title.tsx new file mode 100644 index 00000000000..c0e29b8efcd --- /dev/null +++ b/packages/compass-collection/src/plugin-tab-title.tsx @@ -0,0 +1,90 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import toNS from 'mongodb-ns'; +import { + useConnectionInfo, + useConnectionsListRef, + useTabConnectionTheme, +} from '@mongodb-js/compass-connections/provider'; +import { + WorkspaceTab, + type WorkspaceTabCoreProps, +} from '@mongodb-js/compass-components'; +import type { WorkspacePluginProps } from '@mongodb-js/compass-workspaces'; + +import { type CollectionState } from './modules/collection-tab'; + +export const CollectionWorkspaceTitle = 'Collection' as const; + +type PluginTitleProps = { + isTimeSeries?: boolean; + isReadonly?: boolean; + sourceName?: string | null; +} & WorkspaceTabCoreProps & + WorkspacePluginProps; + +function _PluginTitle({ + editViewName, + isNonExistent, + isReadonly, + isTimeSeries, + sourceName, + namespace, + ...tabProps +}: PluginTitleProps) { + const { getThemeOf } = useTabConnectionTheme(); + const { getConnectionById } = useConnectionsListRef(); + const { id: connectionId } = useConnectionInfo(); + + const { database, collection, ns } = toNS(namespace); + const connectionName = getConnectionById(connectionId)?.title || ''; + const collectionType = isTimeSeries + ? 'timeseries' + : isReadonly + ? 'view' + : 'collection'; + // Similar to what we have in the collection breadcrumbs. + const tooltip: [string, string][] = [ + ['Connection', connectionName || ''], + ['Database', database], + ]; + if (sourceName) { + tooltip.push(['View', collection]); + tooltip.push(['Derived from', toNS(sourceName).collection]); + } else if (editViewName) { + tooltip.push(['View', toNS(editViewName).collection]); + tooltip.push(['Derived from', collection]); + } else { + tooltip.push(['Collection', collection]); + } + + return ( + + ); +} + +export const CollectionPluginTitleComponent = connect( + (state: CollectionState) => ({ + isTimeSeries: state.metadata?.isTimeSeries, + isReadonly: state.metadata?.isReadonly, + sourceName: state.metadata?.sourceName, + }) +)(_PluginTitle); diff --git a/packages/compass-components/src/components/workspace-tabs/tab.tsx b/packages/compass-components/src/components/workspace-tabs/tab.tsx index 14892ec71b2..227eee98332 100644 --- a/packages/compass-components/src/components/workspace-tabs/tab.tsx +++ b/packages/compass-components/src/components/workspace-tabs/tab.tsx @@ -5,6 +5,7 @@ import { spacing } from '@leafygreen-ui/tokens'; import type { GlyphName } from '@leafygreen-ui/icon'; import { useSortable } from '@dnd-kit/sortable'; import { CSS as cssDndKit } from '@dnd-kit/utilities'; +import { useId } from '@react-aria/utils'; import { useDarkMode } from '../../hooks/use-theme'; import { Icon, IconButton } from '../leafygreen'; import { mergeProps } from '../../utils/merge-props'; @@ -149,6 +150,10 @@ const draggingTabStyles = css({ cursor: 'grabbing !important', }); +const nonExistentStyles = css({ + color: palette.gray.base, +}); + const tabIconStyles = css({ color: 'currentColor', marginLeft: spacing[300], @@ -185,25 +190,34 @@ const workspaceTabTooltipStyles = css({ textWrap: 'wrap', }); -type TabProps = { +// The plugins provide these essential props use to render the tab. +// The workspace-tabs component provides the other parts of TabProps. +export type WorkspaceTabPluginProps = { connectionName?: string; type: string; - title: string; + title: React.ReactNode; + isNonExistent?: boolean; + iconGlyph: GlyphName | 'Logo' | 'Server'; + tooltip?: [string, string][]; + tabTheme?: Partial; +}; + +export type WorkspaceTabCoreProps = { isSelected: boolean; isDragging: boolean; onSelect: () => void; onClose: () => void; - iconGlyph: GlyphName | 'Logo' | 'Server'; tabContentId: string; - tooltip?: [string, string][]; - tabTheme?: Partial; }; +type TabProps = WorkspaceTabCoreProps & WorkspaceTabPluginProps; + function Tab({ connectionName, type, title, tooltip, + isNonExistent, isSelected, isDragging, onSelect, @@ -213,7 +227,7 @@ function Tab({ tabTheme, className: tabClassName, ...props -}: TabProps & React.HTMLProps) { +}: TabProps & Omit, 'title'>) { const darkMode = useDarkMode(); const defaultActionProps = useDefaultAction(onSelect); const { listeners, setNodeRef, transform, transition } = useSortable({ @@ -240,6 +254,8 @@ function Tab({ cursor: 'grabbing !important', }; + const tabId = useId(); + return ( {iconGlyph === 'Logo' && ( diff --git a/packages/compass-components/src/components/workspace-tabs/workspace-tabs.spec.tsx b/packages/compass-components/src/components/workspace-tabs/workspace-tabs.spec.tsx index ec0868042c2..b882fd1e025 100644 --- a/packages/compass-components/src/components/workspace-tabs/workspace-tabs.spec.tsx +++ b/packages/compass-components/src/components/workspace-tabs/workspace-tabs.spec.tsx @@ -9,14 +9,23 @@ import { expect } from 'chai'; import sinon from 'sinon'; import { WorkspaceTabs } from './workspace-tabs'; -import type { TabProps } from './workspace-tabs'; +import { Tab, type WorkspaceTabCoreProps } from './tab'; -function mockTab(tabId: number): TabProps { +function mockTab(tabId: number): { + id: string; + renderTab: (tabProps: WorkspaceTabCoreProps) => ReturnType; +} { return { - type: 'Documents', - title: `mock-tab-${tabId}`, id: `${tabId}-content`, - iconGlyph: 'Folder', + renderTab: (tabProps: WorkspaceTabCoreProps) => ( + + ), }; } diff --git a/packages/compass-components/src/components/workspace-tabs/workspace-tabs.tsx b/packages/compass-components/src/components/workspace-tabs/workspace-tabs.tsx index d85852a4a04..5d48e3f4388 100644 --- a/packages/compass-components/src/components/workspace-tabs/workspace-tabs.tsx +++ b/packages/compass-components/src/components/workspace-tabs/workspace-tabs.tsx @@ -8,7 +8,6 @@ import React, { import { css, cx } from '@leafygreen-ui/emotion'; import { palette } from '@leafygreen-ui/palette'; import { spacing } from '@leafygreen-ui/tokens'; -import type { GlyphName } from '@leafygreen-ui/icon'; import { rgba } from 'polished'; import { @@ -28,7 +27,8 @@ import { useDarkMode } from '../../hooks/use-theme'; import { FocusState, useFocusState } from '../../hooks/use-focus-hover'; import { Icon, IconButton } from '../leafygreen'; import { mergeProps } from '../../utils/merge-props'; -import { Tab } from './tab'; +import type { Tab } from './tab'; +import type { WorkspaceTabCoreProps } from './tab'; import { useHotkeys } from '../../hooks/use-hotkeys'; export const scrollbarThumbLightTheme = rgba(palette.gray.base, 0.65); @@ -139,8 +139,13 @@ function useTabListKeyboardNavigation({ return [{ onKeyDown }]; } +type TabItem = { + id: string; + renderTab: (props: WorkspaceTabCoreProps) => ReturnType; +}; + type SortableItemProps = { - tab: TabProps; + tab: TabItem; index: number; selectedTabIndex: number; activeId: UniqueIdentifier | null; @@ -149,7 +154,7 @@ type SortableItemProps = { }; type SortableListProps = { - tabs: TabProps[]; + tabs: TabItem[]; selectedTabIndex: number; onMove: (oldTabIndex: number, newTabIndex: number) => void; onSelect: (tabIndex: number) => void; @@ -164,19 +169,10 @@ type WorkspaceTabsProps = { onSelectPrevTab: () => void; onCloseTab: (tabIndex: number) => void; onMoveTab: (oldTabIndex: number, newTabIndex: number) => void; - tabs: TabProps[]; + tabs: TabItem[]; selectedTabIndex: number; }; -export type TabProps = { - id: string; - type: string; - title: string; - tooltip?: [string, string][]; - connectionId?: string; - iconGlyph: GlyphName | 'Logo' | 'Server'; -} & Omit, 'id' | 'title'>; - export function useRovingTabIndex({ currentTabbable, }: { @@ -263,7 +259,7 @@ const SortableList = ({ >
- {tabs.map((tab: TabProps, index: number) => ( + {tabs.map((tab: TabItem, index: number) => ( { - const { id: tabId } = tabProps; - const onTabSelected = useCallback(() => { onSelect(index); }, [onSelect, index]); @@ -305,16 +299,13 @@ const SortableItem = ({ const isDragging = useMemo(() => tabId === activeId, [tabId, activeId]); - return ( - - ); + return renderTab({ + isSelected, + isDragging, + tabContentId: tabId, + onSelect: onTabSelected, + onClose: onTabClosed, + }); }; function WorkspaceTabs({ diff --git a/packages/compass-components/src/index.ts b/packages/compass-components/src/index.ts index d577dd2bf13..5f208e4fdc0 100644 --- a/packages/compass-components/src/index.ts +++ b/packages/compass-components/src/index.ts @@ -38,7 +38,11 @@ export { import { ResizeHandle, ResizeDirection } from './components/resize-handle'; import { Accordion } from './components/accordion'; import { CollapsibleFieldSet } from './components/collapsible-field-set'; -export { type TabTheme } from './components/workspace-tabs/tab'; +export { + Tab as WorkspaceTab, + type TabTheme, + type WorkspaceTabCoreProps, +} from './components/workspace-tabs/tab'; import { WorkspaceTabs } from './components/workspace-tabs/workspace-tabs'; import ResizableSidebar, { defaultSidebarWidth, diff --git a/packages/compass-data-modeling/src/index.spec.tsx b/packages/compass-data-modeling/src/index.spec.tsx index 3c3cf401c05..3aabdc09919 100644 --- a/packages/compass-data-modeling/src/index.spec.tsx +++ b/packages/compass-data-modeling/src/index.spec.tsx @@ -1,12 +1,18 @@ import React from 'react'; import { expect } from 'chai'; import { render } from '@mongodb-js/testing-library-compass'; -import CompassPlugin from './index'; +import { WorkspaceTab } from './index'; describe('Compass Plugin', function () { - const Plugin = CompassPlugin.withMockServices({}); + const Plugin = WorkspaceTab.provider.withMockServices({}); it('renders a Plugin', function () { - expect(() => render()).to.not.throw(); + expect(() => + render( + + + + ) + ).to.not.throw(); }); }); diff --git a/packages/compass-data-modeling/src/index.ts b/packages/compass-data-modeling/src/index.ts index a9f5f0c5fda..25196f4e70e 100644 --- a/packages/compass-data-modeling/src/index.ts +++ b/packages/compass-data-modeling/src/index.ts @@ -1,33 +1,35 @@ +import React from 'react'; import { registerHadronPlugin } from 'hadron-app-registry'; import { preferencesLocator } from 'compass-preferences-model/provider'; import { connectionsLocator } from '@mongodb-js/compass-connections/provider'; import { telemetryLocator } from '@mongodb-js/compass-telemetry/provider'; import { createLoggerLocator } from '@mongodb-js/compass-logging/provider'; -import type { WorkspaceComponent } from '@mongodb-js/compass-workspaces'; +import type { WorkspacePlugin } from '@mongodb-js/compass-workspaces'; import DataModelingComponent from './components/data-modeling'; import { mongoDBInstancesManagerLocator } from '@mongodb-js/compass-app-stores/provider'; import { dataModelStorageServiceLocator } from './provider'; import { activateDataModelingStore } from './store'; +import { PluginTabTitleComponent, WorkspaceName } from './plugin-tab-title'; -const DataModelingPlugin = registerHadronPlugin( - { - name: 'DataModeling', - component: DataModelingComponent, - activate: activateDataModelingStore, - }, - { - preferences: preferencesLocator, - connections: connectionsLocator, - instanceManager: mongoDBInstancesManagerLocator, - dataModelStorage: dataModelStorageServiceLocator, - track: telemetryLocator, - logger: createLoggerLocator('COMPASS-DATA-MODELING'), - } -); - -export const WorkspaceTab: WorkspaceComponent<'Data Modeling'> = { - name: 'Data Modeling', - component: DataModelingPlugin, +export const WorkspaceTab: WorkspacePlugin = { + name: WorkspaceName, + provider: registerHadronPlugin( + { + name: 'DataModeling', + component: function DataModelingProvider({ children }) { + return React.createElement(React.Fragment, null, children); + }, + activate: activateDataModelingStore, + }, + { + preferences: preferencesLocator, + connections: connectionsLocator, + instanceManager: mongoDBInstancesManagerLocator, + dataModelStorage: dataModelStorageServiceLocator, + track: telemetryLocator, + logger: createLoggerLocator('COMPASS-DATA-MODELING'), + } + ), + content: DataModelingComponent, + header: PluginTabTitleComponent, }; - -export default DataModelingPlugin; diff --git a/packages/compass-data-modeling/src/plugin-tab-title.tsx b/packages/compass-data-modeling/src/plugin-tab-title.tsx new file mode 100644 index 00000000000..40aac13afbf --- /dev/null +++ b/packages/compass-data-modeling/src/plugin-tab-title.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { connect } from 'react-redux'; + +import { + WorkspaceTab, + type WorkspaceTabCoreProps, +} from '@mongodb-js/compass-components'; +import type { DataModelingState } from './store/reducer'; +import type { WorkspacePluginProps } from '@mongodb-js/compass-workspaces'; + +export const WorkspaceName = 'Data Modeling' as const; + +type WorkspaceProps = WorkspacePluginProps; +type PluginTabTitleProps = { + tabTitle: string; +} & WorkspaceTabCoreProps & + WorkspaceProps; + +function _TabTitle({ tabTitle, ...props }: PluginTabTitleProps) { + return ( + + ); +} + +export const PluginTabTitleComponent = connect((state: DataModelingState) => { + return { + tabTitle: + state.step === 'NO_DIAGRAM_SELECTED' + ? WorkspaceName + : state.diagram?.name ?? WorkspaceName, + }; +})(_TabTitle); diff --git a/packages/compass-saved-aggregations-queries/src/index.spec.tsx b/packages/compass-saved-aggregations-queries/src/index.spec.tsx index 82ed378a06c..eb1f7d1686d 100644 --- a/packages/compass-saved-aggregations-queries/src/index.spec.tsx +++ b/packages/compass-saved-aggregations-queries/src/index.spec.tsx @@ -2,7 +2,7 @@ import React from 'react'; import Sinon from 'sinon'; import { expect } from 'chai'; import { queries, pipelines } from '../test/fixtures'; -import { MyQueriesPlugin } from '.'; +import { WorkspaceTab } from '.'; import type { PipelineStorage, FavoriteQueryStorage, @@ -81,7 +81,7 @@ describe('AggregationsAndQueriesAndUpdatemanyList', function () { let connectionsStore: RenderWithConnectionsResult['connectionsStore']; const renderPlugin = () => { - const PluginWithMocks = MyQueriesPlugin.withMockServices({ + const PluginWithMocks = WorkspaceTab.provider.withMockServices({ instancesManager, favoriteQueryStorageAccess: { getStorage() { @@ -91,9 +91,17 @@ describe('AggregationsAndQueriesAndUpdatemanyList', function () { pipelineStorage: pipelineStorage, workspaces, }); - const result = renderWithConnections(, { - connections: [connectionOne.connectionInfo, connectionTwo.connectionInfo], - }); + const result = renderWithConnections( + + + , + { + connections: [ + connectionOne.connectionInfo, + connectionTwo.connectionInfo, + ], + } + ); connectionsStore = result.connectionsStore; }; diff --git a/packages/compass-saved-aggregations-queries/src/index.ts b/packages/compass-saved-aggregations-queries/src/index.ts index b760ec05565..71d96b26d27 100644 --- a/packages/compass-saved-aggregations-queries/src/index.ts +++ b/packages/compass-saved-aggregations-queries/src/index.ts @@ -1,10 +1,11 @@ +import React from 'react'; import { registerHadronPlugin } from 'hadron-app-registry'; import { mongoDBInstancesManagerLocator } from '@mongodb-js/compass-app-stores/provider'; import { createLoggerLocator } from '@mongodb-js/compass-logging/provider'; import { telemetryLocator } from '@mongodb-js/compass-telemetry/provider'; import { activatePlugin } from './stores'; import AggregationsQueriesList from './components/aggregations-queries-list'; -import type { WorkspaceComponent } from '@mongodb-js/compass-workspaces'; +import type { WorkspacePlugin } from '@mongodb-js/compass-workspaces'; import { workspacesServiceLocator } from '@mongodb-js/compass-workspaces/provider'; import { pipelineStorageLocator, @@ -12,30 +13,29 @@ import { } from '@mongodb-js/my-queries-storage/provider'; import { preferencesLocator } from 'compass-preferences-model/provider'; import { connectionsLocator } from '@mongodb-js/compass-connections/provider'; +import { PluginTabTitleComponent, WorkspaceName } from './plugin-tab-title'; -const serviceLocators = { - connections: connectionsLocator, - instancesManager: mongoDBInstancesManagerLocator, - preferencesAccess: preferencesLocator, - logger: createLoggerLocator('COMPASS-MY-QUERIES-UI'), - track: telemetryLocator, - workspaces: workspacesServiceLocator, - pipelineStorage: pipelineStorageLocator, - favoriteQueryStorageAccess: favoriteQueryStorageAccessLocator, +export const WorkspaceTab: WorkspacePlugin = { + name: WorkspaceName, + provider: registerHadronPlugin( + { + name: WorkspaceName, + component: function MyQueriesProvider({ children }): any { + return React.createElement(React.Fragment, null, children); + }, + activate: activatePlugin, + }, + { + connections: connectionsLocator, + instancesManager: mongoDBInstancesManagerLocator, + preferencesAccess: preferencesLocator, + logger: createLoggerLocator('COMPASS-MY-QUERIES-UI'), + track: telemetryLocator, + workspaces: workspacesServiceLocator, + pipelineStorage: pipelineStorageLocator, + favoriteQueryStorageAccess: favoriteQueryStorageAccessLocator, + } + ), + content: AggregationsQueriesList, + header: PluginTabTitleComponent, }; - -export const MyQueriesPlugin = registerHadronPlugin( - { - name: 'MyQueries', - component: AggregationsQueriesList, - activate: activatePlugin, - }, - serviceLocators -); - -export const WorkspaceTab: WorkspaceComponent<'My Queries'> = { - name: 'My Queries' as const, - component: MyQueriesPlugin, -}; - -export default MyQueriesPlugin; diff --git a/packages/compass-saved-aggregations-queries/src/plugin-tab-title.tsx b/packages/compass-saved-aggregations-queries/src/plugin-tab-title.tsx new file mode 100644 index 00000000000..53114bee352 --- /dev/null +++ b/packages/compass-saved-aggregations-queries/src/plugin-tab-title.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { + WorkspaceTab, + type WorkspaceTabCoreProps, +} from '@mongodb-js/compass-components'; +import type { WorkspacePluginProps } from '@mongodb-js/compass-workspaces'; + +export const WorkspaceName = 'My Queries' as const; + +type PluginTabTitleProps = WorkspaceTabCoreProps & + WorkspacePluginProps; + +export function PluginTabTitleComponent(props: PluginTabTitleProps) { + return ( + + ); +} diff --git a/packages/compass-saved-aggregations-queries/src/stores/index.ts b/packages/compass-saved-aggregations-queries/src/stores/index.ts index d03e4b7d696..781678c2b3e 100644 --- a/packages/compass-saved-aggregations-queries/src/stores/index.ts +++ b/packages/compass-saved-aggregations-queries/src/stores/index.ts @@ -79,8 +79,12 @@ export type SavedQueryAggregationThunkAction< A extends Action = AnyAction > = ThunkAction; +type SavedQueryAggregationPluginProps = { + children?: React.ReactNode; +}; + export function activatePlugin( - _: Record, + _initialProps: SavedQueryAggregationPluginProps, services: MyQueriesServices ) { const store = configureStore(services); diff --git a/packages/compass-serverstats/src/index.ts b/packages/compass-serverstats/src/index.ts index 5b10bced541..a2de4a71407 100644 --- a/packages/compass-serverstats/src/index.ts +++ b/packages/compass-serverstats/src/index.ts @@ -1,3 +1,4 @@ +import React from 'react'; import { PerformanceComponent } from './components'; import { registerHadronPlugin } from 'hadron-app-registry'; import { @@ -9,38 +10,48 @@ import { mongoDBInstanceLocator } from '@mongodb-js/compass-app-stores/provider' import CurrentOpStore from './stores/current-op-store'; import ServerStatsStore from './stores/server-stats-graphs-store'; import TopStore from './stores/top-store'; -import type { WorkspaceComponent } from '@mongodb-js/compass-workspaces'; +import type { WorkspacePlugin } from '@mongodb-js/compass-workspaces'; +import { + WorkspaceName, + ServerStatsPluginTitleComponent, +} from './plugin-tab-title'; -const PerformancePlugin = registerHadronPlugin( - { - name: 'Performance', - component: PerformanceComponent, - activate(_initialProps: Record, { dataService, instance }) { - CurrentOpStore.onActivated(dataService); - ServerStatsStore.onActivated(dataService); - TopStore.onActivated(dataService, instance); +type PerformancePluginInitialProps = Record; - // TODO(COMPASS-7416): no stores or subscriptions are returned here, we'd - // need to refactor the stores of this package - return { - store: {}, - deactivate() { - // noop - }, - }; - }, - }, - { - dataService: dataServiceLocator as DataServiceLocator, - instance: mongoDBInstanceLocator, - } -); +const WorkspaceTab: WorkspacePlugin = { + name: WorkspaceName, + provider: registerHadronPlugin( + { + name: WorkspaceName, + component: function PerformanceProvider({ children }) { + return React.createElement(React.Fragment, null, children); + }, + activate( + _initialProps: PerformancePluginInitialProps, + { dataService, instance } + ) { + CurrentOpStore.onActivated(dataService); + ServerStatsStore.onActivated(dataService); + TopStore.onActivated(dataService, instance); -const WorkspaceTab: WorkspaceComponent<'Performance'> = { - name: 'Performance' as const, - component: PerformancePlugin, + // TODO(COMPASS-7416): no stores or subscriptions are returned here, we'd + // need to refactor the stores of this package + return { + store: {}, + deactivate() { + // noop + }, + }; + }, + }, + { + dataService: dataServiceLocator as DataServiceLocator, + instance: mongoDBInstanceLocator, + } + ), + content: PerformanceComponent, + header: ServerStatsPluginTitleComponent, }; -export default PerformancePlugin; export { WorkspaceTab }; export { default as d3 } from './d3'; diff --git a/packages/compass-serverstats/src/plugin-tab-title.tsx b/packages/compass-serverstats/src/plugin-tab-title.tsx new file mode 100644 index 00000000000..9c0742b94a9 --- /dev/null +++ b/packages/compass-serverstats/src/plugin-tab-title.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { + useConnectionInfo, + useConnectionsListRef, + useTabConnectionTheme, +} from '@mongodb-js/compass-connections/provider'; +import { + WorkspaceTab, + type WorkspaceTabCoreProps, +} from '@mongodb-js/compass-components'; +import type { WorkspacePluginProps } from '@mongodb-js/compass-workspaces'; + +export const WorkspaceName = 'Performance' as const; + +type PluginTitleComponentProps = WorkspaceTabCoreProps & + WorkspacePluginProps; + +export function ServerStatsPluginTitleComponent( + props: PluginTitleComponentProps +) { + const { getConnectionById } = useConnectionsListRef(); + const { id: connectionId } = useConnectionInfo(); + const connectionName = getConnectionById(connectionId)?.title || ''; + + const { getThemeOf } = useTabConnectionTheme(); + + return ( + + ); +} diff --git a/packages/compass-shell/src/index.ts b/packages/compass-shell/src/index.ts index 2b73e217df9..ac829daea94 100644 --- a/packages/compass-shell/src/index.ts +++ b/packages/compass-shell/src/index.ts @@ -1,32 +1,36 @@ +import React from 'react'; import { createLoggerLocator } from '@mongodb-js/compass-logging/provider'; import { telemetryLocator } from '@mongodb-js/compass-telemetry/provider'; import { ShellPlugin, onActivated } from './plugin'; import { registerHadronPlugin } from 'hadron-app-registry'; import { preferencesLocator } from 'compass-preferences-model/provider'; -import { type WorkspaceComponent } from '@mongodb-js/compass-workspaces'; +import type { WorkspacePlugin } from '@mongodb-js/compass-workspaces'; import { dataServiceLocator, type DataService, connectionInfoRefLocator, type DataServiceLocator, } from '@mongodb-js/compass-connections/provider'; +import { WorkspaceName, ShellPluginTitleComponent } from './plugin-tab-title'; -export const CompassShellPlugin = registerHadronPlugin( - { - name: 'CompassShell', - component: ShellPlugin, - activate: onActivated, - }, - { - logger: createLoggerLocator('COMPASS-SHELL'), - track: telemetryLocator, - dataService: dataServiceLocator as DataServiceLocator, - connectionInfo: connectionInfoRefLocator, - preferences: preferencesLocator, - } -); - -export const WorkspaceTab: WorkspaceComponent<'Shell'> = { - name: 'Shell' as const, - component: CompassShellPlugin, +export const WorkspaceTab: WorkspacePlugin = { + name: WorkspaceName, + provider: registerHadronPlugin( + { + name: WorkspaceName, + component: function ShellProvider({ children }) { + return React.createElement(React.Fragment, null, children); + }, + activate: onActivated, + }, + { + logger: createLoggerLocator('COMPASS-SHELL'), + track: telemetryLocator, + dataService: dataServiceLocator as DataServiceLocator, + connectionInfo: connectionInfoRefLocator, + preferences: preferencesLocator, + } + ), + content: ShellPlugin, + header: ShellPluginTitleComponent, }; diff --git a/packages/compass-shell/src/plugin-tab-title.tsx b/packages/compass-shell/src/plugin-tab-title.tsx new file mode 100644 index 00000000000..4d18ddf7194 --- /dev/null +++ b/packages/compass-shell/src/plugin-tab-title.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { + useConnectionInfo, + useConnectionsListRef, + useTabConnectionTheme, +} from '@mongodb-js/compass-connections/provider'; +import { + WorkspaceTab, + type WorkspaceTabCoreProps, +} from '@mongodb-js/compass-components'; +import type { WorkspacePluginProps } from '@mongodb-js/compass-workspaces'; + +export const WorkspaceName = 'Shell' as const; + +type PluginTitleProps = WorkspaceTabCoreProps & + WorkspacePluginProps; + +export function ShellPluginTitleComponent(tabProps: PluginTitleProps) { + const { getThemeOf } = useTabConnectionTheme(); + const { getConnectionById } = useConnectionsListRef(); + const { id: connectionId } = useConnectionInfo(); + + const connectionName = getConnectionById(connectionId)?.title || ''; + return ( + + ); +} diff --git a/packages/compass-shell/src/plugin.spec.tsx b/packages/compass-shell/src/plugin.spec.tsx index 55913229890..b54c65569c8 100644 --- a/packages/compass-shell/src/plugin.spec.tsx +++ b/packages/compass-shell/src/plugin.spec.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { expect } from 'chai'; import { EventEmitter } from 'events'; -import { CompassShellPlugin } from './index'; +import { WorkspaceTab } from './index'; import { renderWithActiveConnection, screen, @@ -9,7 +9,7 @@ import { } from '@mongodb-js/testing-library-compass'; import { RuntimeMap } from './stores/store'; -describe('CompassShellPlugin', function () { +describe('CompassShellPlugin WorkspaceTab', function () { it('returns a renderable plugin', async function () { RuntimeMap.set('test', { eventEmitter: new EventEmitter(), @@ -19,7 +19,12 @@ describe('CompassShellPlugin', function () { }, } as any); - await renderWithActiveConnection(); + const ShellContentComponent = WorkspaceTab.content; + await renderWithActiveConnection( + + + + ); await waitFor(() => { expect(screen.getByTestId('shell-section')).to.exist; diff --git a/packages/compass-sidebar/src/components/multiple-connections/sidebar.spec.tsx b/packages/compass-sidebar/src/components/multiple-connections/sidebar.spec.tsx index 8b054331613..62f0b0fe7ff 100644 --- a/packages/compass-sidebar/src/components/multiple-connections/sidebar.spec.tsx +++ b/packages/compass-sidebar/src/components/multiple-connections/sidebar.spec.tsx @@ -106,8 +106,18 @@ describe('Multiple Connections Sidebar Component', function () { null }, - { name: 'Performance', component: () => null }, + { + name: 'My Queries', + content: () => null, + header: () => null as any, + provider: (() => null) as any, + }, + { + name: 'Performance', + content: () => null, + header: () => null as any, + provider: (() => null) as any, + }, ]} > diff --git a/packages/compass-welcome/src/index.ts b/packages/compass-welcome/src/index.ts index 21822f46a58..026f8f1bf83 100644 --- a/packages/compass-welcome/src/index.ts +++ b/packages/compass-welcome/src/index.ts @@ -1,10 +1,12 @@ +import React from 'react'; import { registerHadronPlugin } from 'hadron-app-registry'; import { createLoggerLocator } from '@mongodb-js/compass-logging/provider'; import { workspacesServiceLocator } from '@mongodb-js/compass-workspaces/provider'; -import type { WorkspaceComponent } from '@mongodb-js/compass-workspaces'; +import type { WorkspacePlugin } from '@mongodb-js/compass-workspaces'; import { WelcomeModal, DesktopWelcomeTab, WebWelcomeTab } from './components'; import { activatePlugin } from './stores'; import { telemetryLocator } from '@mongodb-js/compass-telemetry/provider'; +import { PluginTabTitleComponent, WorkspaceName } from './plugin-tab-title'; const serviceLocators = { logger: createLoggerLocator('COMPASS-MY-QUERIES-UI'), @@ -12,28 +14,36 @@ const serviceLocators = { workspaces: workspacesServiceLocator, }; -export const DesktopWorkspaceTab: WorkspaceComponent<'Welcome'> = { - name: 'Welcome' as const, - component: registerHadronPlugin( +export const DesktopWorkspaceTab: WorkspacePlugin = { + name: WorkspaceName, + provider: registerHadronPlugin( { - name: 'Welcome', - component: DesktopWelcomeTab, + name: WorkspaceName, + component: function WelcomeProvider({ children }) { + return React.createElement(React.Fragment, null, children); + }, activate: activatePlugin, }, serviceLocators ), + content: DesktopWelcomeTab, + header: PluginTabTitleComponent, }; -export const WebWorkspaceTab: WorkspaceComponent<'Welcome'> = { - name: 'Welcome' as const, - component: registerHadronPlugin( +export const WebWorkspaceTab: WorkspacePlugin = { + name: WorkspaceName, + provider: registerHadronPlugin( { - name: 'Welcome', - component: WebWelcomeTab, + name: WorkspaceName, + component: function WelcomeProvider({ children }) { + return React.createElement(React.Fragment, null, children); + }, activate: activatePlugin, }, serviceLocators ), + content: WebWelcomeTab, + header: PluginTabTitleComponent, }; export { WelcomeModal }; diff --git a/packages/compass-welcome/src/plugin-tab-title.tsx b/packages/compass-welcome/src/plugin-tab-title.tsx new file mode 100644 index 00000000000..ce564c7159b --- /dev/null +++ b/packages/compass-welcome/src/plugin-tab-title.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { + WorkspaceTab, + type WorkspaceTabCoreProps, +} from '@mongodb-js/compass-components'; +import type { WorkspacePluginProps } from '@mongodb-js/compass-workspaces'; + +export const WorkspaceName = 'Welcome' as const; + +type PluginTitleComponentProps = WorkspaceTabCoreProps & + WorkspacePluginProps; + +export function PluginTabTitleComponent(props: PluginTitleComponentProps) { + return ( + + ); +} diff --git a/packages/compass-workspaces/src/components/index.tsx b/packages/compass-workspaces/src/components/index.tsx index 35593bce19f..490192d6135 100644 --- a/packages/compass-workspaces/src/components/index.tsx +++ b/packages/compass-workspaces/src/components/index.tsx @@ -4,9 +4,9 @@ import type { CollectionTabInfo } from '../stores/workspaces'; import { getActiveTab, type OpenWorkspaceOptions, - type WorkspaceTab, type WorkspacesState, } from '../stores/workspaces'; +import type { WorkspaceTab } from '../types'; import Workspaces from './workspaces'; import { connect } from '../stores/context'; import { WorkspacesServiceProvider } from '../provider'; diff --git a/packages/compass-workspaces/src/components/workspace-close-handler.tsx b/packages/compass-workspaces/src/components/workspace-close-handler.tsx index 96b2cd9bcf1..e254cf94aff 100644 --- a/packages/compass-workspaces/src/components/workspace-close-handler.tsx +++ b/packages/compass-workspaces/src/components/workspace-close-handler.tsx @@ -1,5 +1,5 @@ import { useEffect, useRef } from 'react'; -import type { WorkspaceTab } from '../stores/workspaces'; +import type { WorkspaceTab } from '../types'; import { useWorkspaceTabId } from './workspace-tab-state-provider'; export type WorkspaceDestroyHandler = () => boolean; diff --git a/packages/compass-workspaces/src/components/workspace-tab-context-provider.tsx b/packages/compass-workspaces/src/components/workspace-tab-context-provider.tsx index 6a3a1d8dade..587e4822b2b 100644 --- a/packages/compass-workspaces/src/components/workspace-tab-context-provider.tsx +++ b/packages/compass-workspaces/src/components/workspace-tab-context-provider.tsx @@ -1,8 +1,6 @@ import React, { useCallback, useEffect, useRef } from 'react'; -import { - getLocalAppRegistryForTab, - type WorkspaceTab, -} from '../stores/workspaces'; +import { getLocalAppRegistryForTab } from '../stores/workspaces'; +import type { WorkspaceTab } from '../types'; import { NamespaceProvider } from '@mongodb-js/compass-app-stores/provider'; import { ConnectionInfoProvider } from '@mongodb-js/compass-connections/provider'; import { rafraf } from '@mongodb-js/compass-components'; @@ -12,6 +10,7 @@ import { WorkspaceTabStateProvider, } from './workspace-tab-state-provider'; import { AppRegistryProvider } from 'hadron-app-registry'; +import { useWorkspacePlugins } from './workspaces-provider'; function getInitialPropsForWorkspace(tab: WorkspaceTab) { switch (tab.type) { @@ -81,21 +80,42 @@ const TabCloseHandler: React.FunctionComponent = ({ children }) => { ); }; -const WorkspaceTabContextProvider: React.FunctionComponent<{ +type WorkspaceTabContextProviderProps = { tab: WorkspaceTab; - sectionType: 'tab-content' | 'tab-title'; - onNamespaceNotFound?: ( - tab: Extract, - fallbackNamespace: string | null - ) => void; children: React.JSX.Element; -}> = ({ tab, onNamespaceNotFound, sectionType: type, children }) => { +} & ( + | { + sectionType: 'tab-content'; + onNamespaceNotFound: ( + tab: Extract, + fallbackNamespace: string | null + ) => void; + } + | { + sectionType: 'tab-title'; + onNamespaceNotFound?: undefined; + } +); + +const WorkspaceTabContextProvider: React.FunctionComponent< + WorkspaceTabContextProviderProps +> = ({ tab, onNamespaceNotFound, sectionType: type, children }) => { const initialProps = getInitialPropsForWorkspace(tab); + const { getWorkspacePluginByName } = useWorkspacePlugins(); + + const { provider: WorkspaceProvider } = getWorkspacePluginByName(tab.type); if (initialProps) { children = React.cloneElement(children, initialProps); } + // The ordering of the these providers is important, + // the workspace provider needs access to the + // connection info and namespace providers. + children = ( + {children} + ); + if ('namespace' in tab) { children = ( - | WorkspaceComponent<'My Queries'> - | WorkspaceComponent<'Data Modeling'> - | WorkspaceComponent<'Shell'> - | WorkspaceComponent<'Performance'> - | WorkspaceComponent<'Databases'> - | WorkspaceComponent<'Collections'> - | WorkspaceComponent<'Collection'>; +export type AnyWorkspacePlugin = + | WorkspacePlugin<'Welcome'> + | WorkspacePlugin<'My Queries'> + | WorkspacePlugin<'Data Modeling'> + | WorkspacePlugin<'Shell'> + | WorkspacePlugin<'Performance'> + | WorkspacePlugin<'Databases'> + | WorkspacePlugin<'Collections'> + | WorkspacePlugin<'Collection'>; -const WorkspacesContext = React.createContext([]); +const WorkspacesContext = React.createContext([]); export const WorkspacesProvider: React.FunctionComponent<{ - value: AnyWorkspaceComponent[]; + value: AnyWorkspacePlugin[]; }> = ({ value, children }) => { const valueRef = useRef(value); return ( @@ -30,17 +31,17 @@ export const useWorkspacePlugins = () => { hasWorkspacePlugin: (name: T) => { return workspaces.some((ws) => ws.name === name); }, - getWorkspacePluginByName: (name?: T) => { - if (!name) { - return null; - } + getWorkspacePlugins: (): AnyWorkspacePlugin[] => { + return workspaces; + }, + getWorkspacePluginByName: (name: T) => { const plugin = workspaces.find((ws) => ws.name === name); if (!plugin) { throw new Error( `Component for workspace "${name}" is missing in context. Did you forget to set up WorkspacesProvider?` ); } - return plugin.component as unknown as WorkspaceComponent['component']; + return plugin as unknown as WorkspacePlugin; }, }); return workspacePlugins.current; diff --git a/packages/compass-workspaces/src/components/workspaces.tsx b/packages/compass-workspaces/src/components/workspaces.tsx index ad873735703..61a2e5de1e1 100644 --- a/packages/compass-workspaces/src/components/workspaces.tsx +++ b/packages/compass-workspaces/src/components/workspaces.tsx @@ -4,15 +4,14 @@ import { MongoDBLogoMark, WorkspaceTabs, css, - palette, spacing, useDarkMode, + type WorkspaceTabCoreProps, } from '@mongodb-js/compass-components'; import type { CollectionTabInfo, DatabaseTabInfo, OpenWorkspaceOptions, - WorkspaceTab, WorkspacesState, } from '../stores/workspaces'; import { @@ -29,11 +28,8 @@ import { useWorkspacePlugins } from './workspaces-provider'; import toNS from 'mongodb-ns'; import { useLogger } from '@mongodb-js/compass-logging/provider'; import { connect } from '../stores/context'; -import { useTabConnectionTheme } from '@mongodb-js/compass-connections/provider'; -import { useConnectionsListRef } from '@mongodb-js/compass-connections/provider'; import { WorkspaceTabContextProvider } from './workspace-tab-context-provider'; - -type Tooltip = [string, string][]; +import type { WorkspaceTab } from '../types'; const emptyWorkspaceStyles = css({ margin: '0 auto', @@ -86,10 +82,6 @@ type CompassWorkspacesProps = { ): void; }; -const nonExistantStyles = css({ - color: palette.gray.base, -}); - const CompassWorkspaces: React.FunctionComponent = ({ tabs, activeTab, @@ -106,159 +98,98 @@ const CompassWorkspaces: React.FunctionComponent = ({ }) => { const { log, mongoLogId } = useLogger('COMPASS-WORKSPACES'); const { getWorkspacePluginByName } = useWorkspacePlugins(); - const { getThemeOf } = useTabConnectionTheme(); - const { getConnectionById } = useConnectionsListRef(); - - const tabDescriptions = useMemo(() => { - return tabs.map((tab) => { - switch (tab.type) { - case 'Welcome': - return { - id: tab.id, - type: tab.type, - title: tab.type, - iconGlyph: 'Logo', - } as const; - case 'My Queries': - return { - id: tab.id, - type: tab.type, - title: tab.type, - iconGlyph: 'CurlyBraces', - } as const; - case 'Data Modeling': - return { - id: tab.id, - type: tab.type, - title: tab.type, - iconGlyph: 'Diagram' as const, - }; - case 'Shell': { - const connectionName = - getConnectionById(tab.connectionId)?.title || ''; - const tooltip: Tooltip = []; - if (connectionName) { - tooltip.push(['mongosh', connectionName || '']); - } - return { - id: tab.id, - connectionName, - type: tab.type, - title: connectionName - ? `mongosh: ${connectionName}` - : 'MongoDB Shell', - tooltip, - iconGlyph: 'Shell', - tabTheme: getThemeOf(tab.connectionId), - } as const; - } - case 'Databases': { - const connectionName = - getConnectionById(tab.connectionId)?.title || ''; - return { - id: tab.id, - connectionName, - type: tab.type, - title: connectionName, - tooltip: [['Connection', connectionName || '']] as Tooltip, - iconGlyph: 'Server', - tabTheme: getThemeOf(tab.connectionId), - } as const; - } - case 'Performance': { - const connectionName = - getConnectionById(tab.connectionId)?.title || ''; - return { - id: tab.id, - connectionName, - type: tab.type, - title: `Performance: ${connectionName}`, - tooltip: [['Performance', connectionName || '']] as Tooltip, - iconGlyph: 'Gauge', - tabTheme: getThemeOf(tab.connectionId), - } as const; - } - case 'Collections': { - const connectionName = - getConnectionById(tab.connectionId)?.title || ''; - const database = tab.namespace; - const namespaceId = `${tab.connectionId}.${database}`; - const { isNonExistent } = databaseInfo[namespaceId] ?? {}; - return { - id: tab.id, - connectionName, - type: tab.type, - title: database, - tooltip: [ - ['Connection', connectionName || ''], - ['Database', database], - ] as Tooltip, - iconGlyph: isNonExistent ? 'EmptyDatabase' : 'Database', - 'data-namespace': tab.namespace, - tabTheme: getThemeOf(tab.connectionId), - ...(isNonExistent && { - className: nonExistantStyles, - }), - } as const; - } - case 'Collection': { - const { database, collection, ns } = toNS(tab.namespace); - const namespaceId = `${tab.connectionId}.${ns}`; - const info = collectionInfo[namespaceId] ?? {}; - const { isTimeSeries, isReadonly, sourceName, isNonExistent } = info; - const connectionName = - getConnectionById(tab.connectionId)?.title || ''; - const collectionType = isTimeSeries - ? 'timeseries' - : isReadonly - ? 'view' - : 'collection'; - // Similar to what we have in the collection breadcrumbs. - const tooltip: Tooltip = [ - ['Connection', connectionName || ''], - ['Database', database], - ]; - if (sourceName) { - tooltip.push(['View', collection]); - tooltip.push(['Derived from', toNS(sourceName).collection]); - } else if (tab.editViewName) { - tooltip.push(['View', toNS(tab.editViewName).collection]); - tooltip.push(['Derived from', collection]); - } else { - tooltip.push(['Collection', collection]); - } - return { - id: tab.id, - connectionName, - type: tab.type, - title: collection, - tooltip, - iconGlyph: - collectionType === 'view' - ? 'Visibility' - : collectionType === 'timeseries' - ? 'TimeSeries' - : isNonExistent - ? 'EmptyFolder' - : 'Folder', - 'data-namespace': ns, - tabTheme: getThemeOf(tab.connectionId), - ...(isNonExistent && { - className: nonExistantStyles, - }), - } as const; - } - } - }); - }, [tabs, collectionInfo, databaseInfo, getThemeOf, getConnectionById]); const activeTabIndex = tabs.findIndex((tab) => tab === activeTab); - const WorkspaceComponent = getWorkspacePluginByName(activeTab?.type); const onCreateNewTab = useCallback(() => { onCreateTab(openOnEmptyWorkspace); }, [onCreateTab, openOnEmptyWorkspace]); + const workspaceTabs = useMemo(() => { + return tabs.map((tab) => { + const plugin = getWorkspacePluginByName(tab.type); + if (!plugin) { + throw new Error( + `Content component for workspace "${tab.type}" is missing in context. Did you forget to set up WorkspacesProvider?` + ); + } + const { content: WorkspaceTabContent, header: WorkspaceTabTitle } = + plugin; + + let isNonExistent: boolean | undefined; + if (tab.type === 'Collections') { + // TODO(COMPASS-9456): Move this logic and `isNonExistent` setting to the plugin. + const database = tab.namespace; + const namespaceId = `${tab.connectionId}.${database}`; + const { isNonExistent: databaseDoesNotExist } = + databaseInfo[namespaceId] ?? {}; + isNonExistent = databaseDoesNotExist; + } else if (tab.type === 'Collection') { + // TODO(COMPASS-9456): Move this logic and `isNonExistent` setting to the plugin. + const { ns } = toNS(tab.namespace); + const namespaceId = `${tab.connectionId}.${ns}`; + const { isNonExistent: collectionDoesNotExist } = + collectionInfo[namespaceId] ?? {}; + isNonExistent = collectionDoesNotExist; + } + + return { + id: tab.id, + renderTab: (workspaceTabCoreProps: WorkspaceTabCoreProps) => ( + { + log.error( + mongoLogId(1_001_000_360), + 'Workspace', + 'Rendering workspace tab header failed', + { name: tab.type, error: error.message, errorInfo } + ); + }} + > + + + + + ), + content: ( + { + log.error( + mongoLogId(1_001_000_277), + 'Workspace', + 'Rendering workspace tab content failed', + { name: tab.type, error: error.message, errorInfo } + ); + }} + > + + + + + ), + }; + }); + }, [ + getWorkspacePluginByName, + tabs, + log, + collectionInfo, + databaseInfo, + mongoLogId, + onNamespaceNotFound, + ]); + + const workspaceTabContent = workspaceTabs[activeTabIndex]?.content ?? null; + return (
= ({ onMoveTab={onMoveTab} onCreateNewTab={onCreateNewTab} onCloseTab={onCloseTab} - tabs={tabDescriptions} + tabs={workspaceTabs} selectedTabIndex={activeTabIndex} >
- {activeTab && WorkspaceComponent ? ( - { - log.error( - mongoLogId(1_001_000_277), - 'Workspace', - 'Rendering workspace tab failed', - { name: activeTab.type, error: error.message, errorInfo } - ); - }} - > - - - - + {activeTab && workspaceTabContent ? ( + workspaceTabContent ) : ( )} diff --git a/packages/compass-workspaces/src/index.spec.tsx b/packages/compass-workspaces/src/index.spec.tsx index 7ee10abb955..7bc542d8fc7 100644 --- a/packages/compass-workspaces/src/index.spec.tsx +++ b/packages/compass-workspaces/src/index.spec.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { expect } from 'chai'; import WorkspacesPlugin, { WorkspacesProvider } from './index'; import Sinon from 'sinon'; -import type { AnyWorkspaceComponent } from './components/workspaces-provider'; +import type { AnyWorkspacePlugin } from './components/workspaces-provider'; import { useOpenWorkspace } from './provider'; import { renderWithConnections, @@ -12,14 +12,35 @@ import { userEvent, } from '@mongodb-js/testing-library-compass'; import { TestMongoDBInstanceManager } from '@mongodb-js/compass-app-stores/provider'; +import { WorkspaceTab } from '@mongodb-js/compass-components'; function mockWorkspace(name: string) { return { name, - component: function Component() { + provider: function Component({ + children, + props, + }: { + children?: React.ReactNode; + props?: any; + }) { + return
{children}
; + }, + content: function Component() { return <>{name}; }, - } as unknown as AnyWorkspaceComponent; + header: function Component(props: any) { + return ( + + ); + }, + } as unknown as AnyWorkspacePlugin; } const TEST_CONNECTION_INFO = { @@ -71,10 +92,33 @@ describe('WorkspacesPlugin', function () { connectFn() { return { listDatabases() { - return Promise.resolve([]); + return Promise.resolve([ + // Mock the databases and collections so we don't trigger the onNamespaceNotFound + // fallback handler which redirects collections to the databases view. + { + _id: 'db', + name: 'db', + is_non_existent: false, + collection_count: 0, + document_count: 0, + index_count: 0, + storage_size: 0, + data_size: 0, + index_size: 0, + }, + ]); }, listCollections() { - return Promise.resolve([]); + return Promise.resolve( + Array.from({ + length: 3, + }).map((_, index) => ({ + _id: `db.coll${index}`, + name: `coll${index}`, + database: 'db', + type: 'collection', + })) as any + ); }, }; }, @@ -90,16 +134,12 @@ describe('WorkspacesPlugin', function () { cleanup(); }); - const connectionName = TEST_CONNECTION_INFO.favorite.name; const tabs = [ ['My Queries', () => openFns.openMyQueriesWorkspace()], - [connectionName, () => openFns.openDatabasesWorkspace('1')], // Databases - [ - `Performance: ${connectionName}`, - () => openFns.openPerformanceWorkspace('1'), - ], + ['Databases', () => openFns.openDatabasesWorkspace('1')], + ['Performance', () => openFns.openPerformanceWorkspace('1')], ['db', () => openFns.openCollectionsWorkspace('1', 'db')], - ['coll', () => openFns.openCollectionWorkspace('1', 'db.coll')], + ['db.coll0', () => openFns.openCollectionWorkspace('1', 'db.coll0')], ] as const; for (const suite of tabs) { @@ -118,23 +158,25 @@ describe('WorkspacesPlugin', function () { expect(onTabChangeSpy).to.have.been.calledWith(null); + openFns.openCollectionWorkspace('1', 'db.coll0', { newTab: true }); openFns.openCollectionWorkspace('1', 'db.coll1', { newTab: true }); openFns.openCollectionWorkspace('1', 'db.coll2', { newTab: true }); - openFns.openCollectionWorkspace('1', 'db.coll3', { newTab: true }); - expect(screen.getByRole('tab', { name: 'coll3' })).to.have.attribute( - 'aria-selected', - 'true' - ); + await waitFor(() => { + expect(screen.getByRole('tab', { name: 'db.coll2' })).to.have.attribute( + 'aria-selected', + 'true' + ); + }); - userEvent.click(screen.getByRole('tab', { name: 'coll1' })); + userEvent.click(screen.getByRole('tab', { name: 'db.coll0' })); await waitFor(() => { - expect(screen.getByRole('tab', { name: /coll3/i })).to.have.attribute( + expect(screen.getByRole('tab', { name: 'db.coll2' })).to.have.attribute( 'aria-selected', 'false' ); - expect(screen.getByRole('tab', { name: /coll1/i })).to.have.attribute( + expect(screen.getByRole('tab', { name: 'db.coll0' })).to.have.attribute( 'aria-selected', 'true' ); diff --git a/packages/compass-workspaces/src/index.ts b/packages/compass-workspaces/src/index.ts index 92a90f7b2be..61450e48a0c 100644 --- a/packages/compass-workspaces/src/index.ts +++ b/packages/compass-workspaces/src/index.ts @@ -3,7 +3,6 @@ import type { ActivateHelpers } from 'hadron-app-registry'; import { registerHadronPlugin } from 'hadron-app-registry'; import type { OpenWorkspaceOptions, - WorkspaceTab, CollectionTabInfo, } from './stores/workspaces'; import workspacesReducer, { @@ -238,7 +237,7 @@ const WorkspacesPlugin = registerHadronPlugin( export default WorkspacesPlugin; export { WorkspacesProvider } from './components/workspaces-provider'; -export type { OpenWorkspaceOptions, WorkspaceTab, CollectionTabInfo }; +export type { OpenWorkspaceOptions, CollectionTabInfo }; export type { WelcomeWorkspace, MyQueriesWorkspace, @@ -250,7 +249,8 @@ export type { CollectionWorkspace, AnyWorkspace, Workspace, - WorkspacePluginProps, - WorkspaceComponent, + WorkspacePlugin, + WorkspaceTab, CollectionSubtab, + WorkspacePluginProps, } from './types'; diff --git a/packages/compass-workspaces/src/provider.tsx b/packages/compass-workspaces/src/provider.tsx index edd627abb84..a8f8354dc78 100644 --- a/packages/compass-workspaces/src/provider.tsx +++ b/packages/compass-workspaces/src/provider.tsx @@ -1,17 +1,13 @@ import React, { useContext, useRef } from 'react'; import { useSelector, useStore } from './stores/context'; -import type { - OpenWorkspaceOptions, - TabOptions, - WorkspaceTab, -} from './stores/workspaces'; +import type { OpenWorkspaceOptions, TabOptions } from './stores/workspaces'; import { collectionSubtabSelected, getActiveTab, openWorkspace as openWorkspaceAction, } from './stores/workspaces'; import { createServiceLocator } from 'hadron-app-registry'; -import type { CollectionSubtab } from './types'; +import type { CollectionSubtab, WorkspaceTab } from './types'; import type { WorkspaceDestroyHandler } from './components/workspace-close-handler'; import { useRegisterTabDestroyHandler } from './components/workspace-close-handler'; diff --git a/packages/compass-workspaces/src/stores/workspaces.spec.ts b/packages/compass-workspaces/src/stores/workspaces.spec.ts index 86bc9c4f0aa..86105e40f28 100644 --- a/packages/compass-workspaces/src/stores/workspaces.spec.ts +++ b/packages/compass-workspaces/src/stores/workspaces.spec.ts @@ -6,7 +6,7 @@ import * as workspacesSlice from './workspaces'; import { _bulkTabsClose } from './workspaces'; import { TestMongoDBInstanceManager } from '@mongodb-js/compass-app-stores/provider'; import type { ConnectionInfo } from '../../../connection-info/dist'; -import type { WorkspaceTab } from '../stores/workspaces'; +import type { WorkspaceTab } from '../types'; import { setTabDestroyHandler } from '../components/workspace-close-handler'; import { createPluginTestHelpers } from '@mongodb-js/testing-library-compass'; diff --git a/packages/compass-workspaces/src/stores/workspaces.ts b/packages/compass-workspaces/src/stores/workspaces.ts index 214165c51ae..bbfcb438f80 100644 --- a/packages/compass-workspaces/src/stores/workspaces.ts +++ b/packages/compass-workspaces/src/stores/workspaces.ts @@ -3,19 +3,8 @@ import type { ThunkAction } from 'redux-thunk'; import { ObjectId } from 'bson'; import AppRegistry from 'hadron-app-registry'; import toNS from 'mongodb-ns'; -import type { - CollectionWorkspace, - CollectionsWorkspace, - DatabasesWorkspace, - MyQueriesWorkspace, - DataModelingWorkspace, - ShellWorkspace, - ServerStatsWorkspace, - WelcomeWorkspace, - Workspace, - WorkspacesServices, - CollectionSubtab, -} from '..'; +import type { Workspace, WorkspacesServices, CollectionSubtab } from '..'; +import type { WorkspaceTab, WorkspaceTabProps } from '../types'; import { isEqual } from 'lodash'; import { cleanupTabState } from '../components/workspace-tab-state-provider'; import { @@ -83,22 +72,6 @@ function isAction( return action.type === type; } -type WorkspaceTabProps = - | Omit - | Omit - | Omit - | Omit - | Omit - | Omit - | Omit - | (Omit & { - subTab: CollectionSubtab; - }); - -export type WorkspaceTab = { - id: string; -} & WorkspaceTabProps; - export type CollectionTabInfo = { isTimeSeries: boolean; isReadonly: boolean; diff --git a/packages/compass-workspaces/src/types.ts b/packages/compass-workspaces/src/types.ts index a744c060e61..f3bf71aff2b 100644 --- a/packages/compass-workspaces/src/types.ts +++ b/packages/compass-workspaces/src/types.ts @@ -1,3 +1,6 @@ +import type { HadronPluginComponent } from 'hadron-app-registry'; +import type { WorkspaceTabCoreProps } from '@mongodb-js/compass-components'; + export type CollectionSubtab = | 'Documents' | 'Aggregations' @@ -39,6 +42,8 @@ export type CollectionsWorkspace = { type: 'Collections'; connectionId: string; namespace: string; + // TODO(COMPASS-9456): Remove the `isNonExistent` field here. + isNonExistent?: boolean; }; export type CollectionWorkspace = { @@ -56,8 +61,26 @@ export type CollectionWorkspace = { initialPipelineText?: string; initialAggregation?: unknown; editViewName?: string; + // TODO(COMPASS-9456): Remove the `isNonExistent` field here. + isNonExistent?: boolean; }; +export type WorkspaceTabProps = + | WelcomeWorkspace + | MyQueriesWorkspace + | DataModelingWorkspace + | ShellWorkspace + | ServerStatsWorkspace + | DatabasesWorkspace + | CollectionsWorkspace + | (Omit & { + subTab: CollectionSubtab; + }); + +export type WorkspaceTab = { + id: string; +} & WorkspaceTabProps; + export type AnyWorkspace = | WelcomeWorkspace | MyQueriesWorkspace @@ -78,9 +101,12 @@ export type WorkspacePluginProps = Omit< 'type' | 'connectionId' >; -export type WorkspaceComponent = { +export type PluginHeaderProps = + WorkspaceTabCoreProps & WorkspacePluginProps; + +export type WorkspacePlugin = { name: T; - component: - | React.ComponentClass> - | ((props: WorkspacePluginProps) => React.ReactElement | null); + provider: HadronPluginComponent; + content: (props: WorkspacePluginProps) => React.ReactElement | null; + header: (props: PluginHeaderProps) => React.ReactElement | null; }; diff --git a/packages/databases-collections/src/collections-plugin-title.tsx b/packages/databases-collections/src/collections-plugin-title.tsx new file mode 100644 index 00000000000..4b4bdb6f854 --- /dev/null +++ b/packages/databases-collections/src/collections-plugin-title.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { + useConnectionInfo, + useConnectionsListRef, + useTabConnectionTheme, +} from '@mongodb-js/compass-connections/provider'; +import { + WorkspaceTab, + type WorkspaceTabCoreProps, +} from '@mongodb-js/compass-components'; +import type { WorkspacePluginProps } from '@mongodb-js/compass-workspaces'; + +import { CollectionsWorkspaceName } from './collections-plugin'; + +type PluginTitleProps = WorkspaceTabCoreProps & + WorkspacePluginProps; + +export function CollectionsPluginTitleComponent(props: PluginTitleProps) { + const { id: connectionId } = useConnectionInfo(); + const { getConnectionById } = useConnectionsListRef(); + const { getThemeOf } = useTabConnectionTheme(); + + const connectionName = getConnectionById(connectionId)?.title || ''; + const database = props.namespace; + + return ( + + ); +} diff --git a/packages/databases-collections/src/collections-plugin.spec.tsx b/packages/databases-collections/src/collections-plugin.spec.tsx index 703516a844d..ce4eb6bc1f9 100644 --- a/packages/databases-collections/src/collections-plugin.spec.tsx +++ b/packages/databases-collections/src/collections-plugin.spec.tsx @@ -9,7 +9,7 @@ import { userEvent, } from '@mongodb-js/testing-library-compass'; import { expect } from 'chai'; -import { CollectionsPlugin } from './collections-plugin'; +import { CollectionsWorkspaceTab } from './'; import Sinon from 'sinon'; import { type PreferencesAccess, @@ -61,13 +61,17 @@ describe('Collections [Plugin]', function () { describe('with loaded collections', function () { beforeEach(async function () { - const Plugin = CollectionsPlugin.withMockServices({ + const Plugin = CollectionsWorkspaceTab.provider.withMockServices({ instance: mongodbInstance, database: mongodbInstance.databases.get('foo'), dataService, }); - const { globalAppRegistry } = render(); + const { globalAppRegistry } = render( + + + + ); appRegistry = Sinon.spy(globalAppRegistry); await waitFor(() => { diff --git a/packages/databases-collections/src/collections-plugin.tsx b/packages/databases-collections/src/collections-plugin.tsx index b0d5e172032..e88ac113913 100644 --- a/packages/databases-collections/src/collections-plugin.tsx +++ b/packages/databases-collections/src/collections-plugin.tsx @@ -1,8 +1,8 @@ +import React from 'react'; import { databaseModelLocator, mongoDBInstanceLocator, } from '@mongodb-js/compass-app-stores/provider'; -import CollectionsList from './components/collections'; import { activatePlugin as activateCollectionsTabPlugin } from './stores/collections-store'; import { registerHadronPlugin } from 'hadron-app-registry'; import { @@ -11,10 +11,14 @@ import { type DataService, } from '@mongodb-js/compass-connections/provider'; +export const CollectionsWorkspaceName = 'Collections' as const; + export const CollectionsPlugin = registerHadronPlugin( { - name: 'Collections', - component: CollectionsList, + name: 'Collections' as const, + component: function CollectionsProvider({ children }) { + return React.createElement(React.Fragment, null, children); + }, activate: activateCollectionsTabPlugin, }, { diff --git a/packages/databases-collections/src/databases-plugin-title.tsx b/packages/databases-collections/src/databases-plugin-title.tsx new file mode 100644 index 00000000000..062faa7049c --- /dev/null +++ b/packages/databases-collections/src/databases-plugin-title.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { + WorkspaceTab, + type WorkspaceTabCoreProps, +} from '@mongodb-js/compass-components'; +import { + useConnectionInfo, + useConnectionsListRef, + useTabConnectionTheme, +} from '@mongodb-js/compass-connections/provider'; +import type { WorkspacePluginProps } from '@mongodb-js/compass-workspaces'; + +import { DatabasesWorkspaceName } from './databases-plugin'; + +type PluginTitleProps = WorkspaceTabCoreProps & + WorkspacePluginProps; + +export function DatabasesPluginTitleComponent(props: PluginTitleProps) { + const { id: connectionId } = useConnectionInfo(); + const { getConnectionById } = useConnectionsListRef(); + const { getThemeOf } = useTabConnectionTheme(); + + const connectionName = getConnectionById(connectionId)?.title || ''; + return ( + + ); +} diff --git a/packages/databases-collections/src/databases-plugin.spec.tsx b/packages/databases-collections/src/databases-plugin.spec.tsx index fd9c07c1c57..32fcf190833 100644 --- a/packages/databases-collections/src/databases-plugin.spec.tsx +++ b/packages/databases-collections/src/databases-plugin.spec.tsx @@ -9,7 +9,7 @@ import { userEvent, } from '@mongodb-js/testing-library-compass'; import { expect } from 'chai'; -import { DatabasesPlugin } from './databases-plugin'; +import { DatabasesWorkspaceTab } from './'; import Sinon from 'sinon'; import { createSandboxFromDefaultPreferences, @@ -49,12 +49,16 @@ describe('Databasees [Plugin]', function () { }, }; - const Plugin = DatabasesPlugin.withMockServices({ + const Plugin = DatabasesWorkspaceTab.provider.withMockServices({ instance: mongodbInstance, dataService, }); - const { globalAppRegistry } = render(); + const { globalAppRegistry } = render( + + + + ); appRegistry = Sinon.spy(globalAppRegistry); diff --git a/packages/databases-collections/src/databases-plugin.tsx b/packages/databases-collections/src/databases-plugin.tsx index 23ab01032a7..a44875c72fc 100644 --- a/packages/databases-collections/src/databases-plugin.tsx +++ b/packages/databases-collections/src/databases-plugin.tsx @@ -1,5 +1,5 @@ +import React from 'react'; import { mongoDBInstanceLocator } from '@mongodb-js/compass-app-stores/provider'; -import Databases from './components/databases'; import { activatePlugin as activateDatabasesTabPlugin } from './stores/databases-store'; import { registerHadronPlugin } from 'hadron-app-registry'; import { @@ -8,10 +8,14 @@ import { type DataService, } from '@mongodb-js/compass-connections/provider'; +export const DatabasesWorkspaceName = 'Databases' as const; + export const DatabasesPlugin = registerHadronPlugin( { - name: 'Databases', - component: Databases, + name: 'Databases' as const, + component: function DatabasesProvider({ children }) { + return React.createElement(React.Fragment, null, children); + }, activate: activateDatabasesTabPlugin, }, { diff --git a/packages/databases-collections/src/index.ts b/packages/databases-collections/src/index.ts index e45a71de468..9ed127b5f5b 100644 --- a/packages/databases-collections/src/index.ts +++ b/packages/databases-collections/src/index.ts @@ -3,31 +3,46 @@ import { createLoggerLocator } from '@mongodb-js/compass-logging/provider'; import { telemetryLocator } from '@mongodb-js/compass-telemetry/provider'; import { connectionsLocator } from '@mongodb-js/compass-connections/provider'; import { mongoDBInstancesManagerLocator } from '@mongodb-js/compass-app-stores/provider'; -import { CollectionsPlugin } from './collections-plugin'; +import { + CollectionsPlugin, + CollectionsWorkspaceName, +} from './collections-plugin'; import { DropNamespaceComponent, activatePlugin as activateDropNamespacePlugin, } from './stores/drop-namespace'; import CreateNamespaceModal from './components/create-namespace-modal'; import { activatePlugin as activateCreateNamespacePlugin } from './stores/create-namespace'; -import { DatabasesPlugin } from './databases-plugin'; +import { DatabasesPlugin, DatabasesWorkspaceName } from './databases-plugin'; import MappedRenameCollectionModal from './components/rename-collection-modal/rename-collection-modal'; import { activateRenameCollectionPlugin } from './stores/rename-collection'; -import type { WorkspaceComponent } from '@mongodb-js/compass-workspaces'; +import type { WorkspacePlugin } from '@mongodb-js/compass-workspaces'; import { workspacesServiceLocator } from '@mongodb-js/compass-workspaces/provider'; import { favoriteQueryStorageAccessLocator, pipelineStorageLocator, } from '@mongodb-js/my-queries-storage/provider'; +import Databases from './components/databases'; +import CollectionsList from './components/collections'; +import { DatabasesPluginTitleComponent } from './databases-plugin-title'; +import { CollectionsPluginTitleComponent } from './collections-plugin-title'; -export const CollectionsWorkspaceTab: WorkspaceComponent<'Collections'> = { - name: 'Collections' as const, - component: CollectionsPlugin, +export const CollectionsWorkspaceTab: WorkspacePlugin< + typeof CollectionsWorkspaceName +> = { + name: CollectionsWorkspaceName, + provider: CollectionsPlugin, + content: CollectionsList, + header: CollectionsPluginTitleComponent, }; -export const DatabasesWorkspaceTab: WorkspaceComponent<'Databases'> = { - name: 'Databases' as const, - component: DatabasesPlugin, +export const DatabasesWorkspaceTab: WorkspacePlugin< + typeof DatabasesWorkspaceName +> = { + name: DatabasesWorkspaceName, + provider: DatabasesPlugin, + content: Databases, + header: DatabasesPluginTitleComponent, }; export const CreateNamespacePlugin = registerHadronPlugin( diff --git a/packages/databases-collections/src/stores/databases-store.ts b/packages/databases-collections/src/stores/databases-store.ts index 7cca099fd12..73d515fdbcd 100644 --- a/packages/databases-collections/src/stores/databases-store.ts +++ b/packages/databases-collections/src/stores/databases-store.ts @@ -16,8 +16,11 @@ type DatabasesTabServices = { dataService: DataService; }; +type DatabasesPluginInitialProps = { + children?: React.ReactNode; +}; export function activatePlugin( - _initialProps: Record, + _initialProps: DatabasesPluginInitialProps, { globalAppRegistry, instance, dataService }: DatabasesTabServices, { on, cleanup, addCleanup }: ActivateHelpers ) { From 8e9c13f02dcc59b2a9c322cd3a4275196b3e44d0 Mon Sep 17 00:00:00 2001 From: Basit <1305718+mabaasit@users.noreply.github.com> Date: Tue, 17 Jun 2025 17:42:51 +0200 Subject: [PATCH 57/76] fix(test): deflake diagram-editor tests COMPASS-9462 (#7030) * fix test flakes * nullable * wait for container animations --- .../src/components/diagram-editor.tsx | 18 ++++++++++++++- .../compass-e2e-tests/helpers/selectors.ts | 2 -- .../tests/data-modeling-tab.test.ts | 23 +++++++++++++++---- 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/packages/compass-data-modeling/src/components/diagram-editor.tsx b/packages/compass-data-modeling/src/components/diagram-editor.tsx index bddf93a84ea..39a77bc9654 100644 --- a/packages/compass-data-modeling/src/components/diagram-editor.tsx +++ b/packages/compass-data-modeling/src/components/diagram-editor.tsx @@ -1,4 +1,4 @@ -import React, { useMemo, useState } from 'react'; +import React, { useCallback, useMemo, useRef, useState } from 'react'; import { connect } from 'react-redux'; import type { DataModelingState } from '../store/reducer'; import { @@ -27,6 +27,7 @@ import { Diagram, type NodeProps, type EdgeProps, + useDiagram, } from '@mongodb-js/diagramming'; import type { Edit, StaticModel } from '../services/data-model-storage'; import { UUID } from 'bson'; @@ -132,6 +133,20 @@ const DiagramEditor: React.FunctionComponent<{ onApplyClick, }) => { const isDarkMode = useDarkMode(); + const diagramContainerRef = useRef(null); + const diagram = useDiagram(); + + const setDiagramContainerRef = useCallback( + (ref: HTMLDivElement | null) => { + if (ref) { + // For debugging purposes, we attach the diagram to the ref. + (ref as any)._diagram = diagram; + } + diagramContainerRef.current = ref; + }, + [diagram] + ); + const [applyInput, setApplyInput] = useState('{}'); const isEditValid = useMemo(() => { @@ -265,6 +280,7 @@ const DiagramEditor: React.FunctionComponent<{ if (step === 'EDITING') { content = (
diff --git a/packages/compass-e2e-tests/helpers/selectors.ts b/packages/compass-e2e-tests/helpers/selectors.ts index f129f846383..0f66ed83304 100644 --- a/packages/compass-e2e-tests/helpers/selectors.ts +++ b/packages/compass-e2e-tests/helpers/selectors.ts @@ -1451,5 +1451,3 @@ export const DataModelsListItem = (diagramName: string) => export const DataModelsListItemActions = (diagramName: string) => `${DataModelsListItem(diagramName)} [aria-label="Show actions"]`; export const DataModelsListItemDeleteButton = `[data-action="delete"]`; -export const DataModelingDiagram = '.react-flow'; -export const DataModelingDiagramNode = '.react-flow__node > div'; diff --git a/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts b/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts index 3ded8ff12d0..da4361584e0 100644 --- a/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts +++ b/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts @@ -14,11 +14,23 @@ import { createNumbersCollection, } from '../helpers/insert-data'; +type DiagramInstance = { + getNodes: () => Array<{ + id: string; + }>; +}; + async function getDiagramNodes(browser: CompassBrowser): Promise { - await browser.waitForAnimations(Selectors.DataModelingDiagram); - return await browser - .$$(Selectors.DataModelingDiagramNode) - .map((element) => element.getAttribute('title')); + const nodes = await browser.execute(function (selector) { + const node = document.querySelector(selector); + if (!node) { + throw new Error(`Element with selector ${selector} not found`); + } + return ( + node as Element & { _diagram: DiagramInstance } + )._diagram.getNodes(); + }, Selectors.DataModelEditor); + return nodes.map((x) => x.id); } describe('Data Modeling tab', function () { @@ -107,6 +119,7 @@ describe('Data Modeling tab', function () { JSON.stringify(newModel) ); await browser.clickVisible(Selectors.DataModelEditorApplyButton); + await browser.waitForAnimations(dataModelEditor); // Verify that the model is updated nodes = await getDiagramNodes(browser); @@ -114,6 +127,7 @@ describe('Data Modeling tab', function () { // Undo the change await browser.clickVisible(Selectors.DataModelUndoButton); + await browser.waitForAnimations(dataModelEditor); nodes = await getDiagramNodes(browser); expect(nodes).to.have.lengthOf(2); expect(nodes).to.deep.equal([ @@ -124,6 +138,7 @@ describe('Data Modeling tab', function () { // Redo the change await browser.waitForAriaDisabled(Selectors.DataModelRedoButton, false); await browser.clickVisible(Selectors.DataModelRedoButton); + await browser.waitForAnimations(dataModelEditor); nodes = await getDiagramNodes(browser); expect(nodes).to.have.lengthOf(0); From 720a05afd106499a9e3911e385af08ac00ef9189 Mon Sep 17 00:00:00 2001 From: Sergey Petushkov Date: Wed, 18 Jun 2025 09:47:29 +0200 Subject: [PATCH 58/76] chore(deps): disable custom dependabot rules (#7034) --- .github/{dependabot.yml => _dependabot.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{dependabot.yml => _dependabot.yml} (100%) diff --git a/.github/dependabot.yml b/.github/_dependabot.yml similarity index 100% rename from .github/dependabot.yml rename to .github/_dependabot.yml From 58a1873dd1bc5ba2bb60333aa039c151c0a37097 Mon Sep 17 00:00:00 2001 From: "mongodb-devtools-bot[bot]" <189715634+mongodb-devtools-bot[bot]@users.noreply.github.com> Date: Wed, 18 Jun 2025 07:55:04 +0000 Subject: [PATCH 59/76] chore: update AUTHORS, THIRD-PARTY-NOTICES, Security Test Summary --- THIRD-PARTY-NOTICES.md | 2 +- docs/tracking-plan.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/THIRD-PARTY-NOTICES.md b/THIRD-PARTY-NOTICES.md index bfc20bedfcc..8b01b2c54b2 100644 --- a/THIRD-PARTY-NOTICES.md +++ b/THIRD-PARTY-NOTICES.md @@ -1,5 +1,5 @@ The following third-party software is used by and included in **Mongodb Compass**. -This document was automatically generated on Tue Jun 17 2025. +This document was automatically generated on Wed Jun 18 2025. ## List of dependencies diff --git a/docs/tracking-plan.md b/docs/tracking-plan.md index 2cc169954c0..bce23c72e62 100644 --- a/docs/tracking-plan.md +++ b/docs/tracking-plan.md @@ -6,7 +6,7 @@ > the tracking plan for the specific Compass version you can use the following > URL: `https://github.com/mongodb-js/compass/blob//docs/tracking-plan.md` -Generated on Tue, Jun 17, 2025 +Generated on Wed, Jun 18, 2025 ## Table of Contents From 73bfc317be5a35fae11e583ae40619c360a5ed36 Mon Sep 17 00:00:00 2001 From: gagik Date: Wed, 18 Jun 2025 11:15:05 +0200 Subject: [PATCH 60/76] wip --- .../src/components/document-list/element.tsx | 37 +------- .../src/hooks/use-element-context-menu.ts | 62 ++++++++++++ packages/compass-components/src/index.ts | 1 + .../src/context-menu-provider.tsx | 12 ++- packages/compass-context-menu/src/index.ts | 5 +- packages/compass-context-menu/src/types.ts | 2 +- .../src/use-context-menu.tsx | 4 +- .../components/table-view/cell-renderer.tsx | 95 ++++--------------- .../table-view/full-width-cell-renderer.tsx | 51 +++++----- 9 files changed, 127 insertions(+), 142 deletions(-) create mode 100644 packages/compass-components/src/hooks/use-element-context-menu.ts diff --git a/packages/compass-components/src/components/document-list/element.tsx b/packages/compass-components/src/components/document-list/element.tsx index 5f5c7de1a28..7c3879ecba9 100644 --- a/packages/compass-components/src/components/document-list/element.tsx +++ b/packages/compass-components/src/components/document-list/element.tsx @@ -29,7 +29,7 @@ import { palette } from '@leafygreen-ui/palette'; import { Icon } from '../leafygreen'; import { useDarkMode } from '../../hooks/use-theme'; import VisibleFieldsToggle from './visible-field-toggle'; -import { useContextMenuItems } from '../context-menu'; +import { useFieldContextMenu } from '../../hooks/use-element-context-menu'; function getEditorByType(type: HadronElementType['type']) { switch (type) { @@ -411,16 +411,6 @@ export const calculateShowMoreToggleOffset = ({ return spacerWidth + editableOffset + expandIconSize; }; -// Helper function to check if a string is a URL -const isValidUrl = (str: string): boolean => { - try { - const url = new URL(str); - return url.protocol === 'http:' || url.protocol === 'https:'; - } catch { - return false; - } -}; - export const HadronElement: React.FunctionComponent<{ value: HadronElementType; editable: boolean; @@ -460,27 +450,10 @@ export const HadronElement: React.FunctionComponent<{ } = useHadronElement(element); // Add context menu hook for the field - const fieldContextMenuRef = useContextMenuItems([ - { - label: 'Copy field & value', - onAction: () => { - const fieldStr = `${key.value}: ${objectToIdiomaticEJSON( - value.originalValue - )}`; - void navigator.clipboard.writeText(fieldStr); - }, - }, - ...(type.value === 'String' && isValidUrl(value.value) - ? [ - { - label: 'Open URL in browser', - onAction: () => { - window.open(value.value, '_blank', 'noopener'); - }, - }, - ] - : []), - ]); + const fieldContextMenuRef = useFieldContextMenu({ + element, + fieldName: key.value, + }); const toggleExpanded = () => { if (expanded) { diff --git a/packages/compass-components/src/hooks/use-element-context-menu.ts b/packages/compass-components/src/hooks/use-element-context-menu.ts new file mode 100644 index 00000000000..4e0948d8ddc --- /dev/null +++ b/packages/compass-components/src/hooks/use-element-context-menu.ts @@ -0,0 +1,62 @@ +import { useContextMenuItems } from '../components/context-menu'; +import { Element } from 'hadron-document'; +import { objectToIdiomaticEJSON } from 'hadron-document'; + +// Helper function to check if a string is a URL +export const isValidUrl = (str: string): boolean => { + try { + const url = new URL(str); + return url.protocol === 'http:' || url.protocol === 'https:'; + } catch { + return false; + } +}; + +export interface useFieldContextMenuProps { + element: Element | undefined | null; + fieldName: string; +} + +export function useFieldContextMenu({ + element, + fieldName, +}: useFieldContextMenuProps) { + return useContextMenuItems([ + ...(element + ? [ + { + label: 'Copy field & value', + onAction: () => { + const fieldStr = `${fieldName}: ${objectToIdiomaticEJSON( + element.currentValue + )}`; + void navigator.clipboard.writeText(fieldStr); + }, + }, + { + label: 'Copy value', + onAction: () => { + const valueStr = objectToIdiomaticEJSON(element.currentValue); + void navigator.clipboard.writeText(valueStr); + }, + }, + ...(element.currentType === 'String' && + typeof element.currentValue === 'string' && + isValidUrl(element.currentValue) + ? [ + { + label: 'Open URL in browser', + onAction: () => { + window.open( + element.currentValue as string, + '_blank', + 'noopener' + ); + }, + }, + ] + : []), + ] + : []), + ]); +} diff --git a/packages/compass-components/src/index.ts b/packages/compass-components/src/index.ts index 69ce000a74f..cfe2a7494ca 100644 --- a/packages/compass-components/src/index.ts +++ b/packages/compass-components/src/index.ts @@ -215,3 +215,4 @@ export { export { SelectList } from './components/select-list'; export { ParagraphSkeleton } from '@leafygreen-ui/skeleton-loader'; export { InsightsChip } from './components/insights-chip'; +export { useFieldContextMenu } from './hooks/use-element-context-menu'; diff --git a/packages/compass-context-menu/src/context-menu-provider.tsx b/packages/compass-context-menu/src/context-menu-provider.tsx index 0ae7c5f0e06..1bdb82ed48c 100644 --- a/packages/compass-context-menu/src/context-menu-provider.tsx +++ b/packages/compass-context-menu/src/context-menu-provider.tsx @@ -6,11 +6,13 @@ import React, { createContext, useContext, } from 'react'; -import type { ContextMenuContext, ContextMenuState } from './types'; +import type { ContextMenuContextType, ContextMenuState } from './types'; import type { EnhancedMouseEvent } from './context-menu-content'; import { getContextMenuContent } from './context-menu-content'; -export const Context = createContext(null); +export const ContextMenuContext = createContext( + null +); export function ContextMenuProvider({ children, @@ -22,7 +24,7 @@ export function ContextMenuProvider({ }>; }) { // Check if there's already a parent context menu provider - const parentContext = useContext(Context); + const parentContext = useContext(ContextMenuContext); const [menu, setMenu] = useState({ isOpen: false, @@ -92,9 +94,9 @@ export function ContextMenuProvider({ const Wrapper = wrapper ?? React.Fragment; return ( - + {children} - + ); } diff --git a/packages/compass-context-menu/src/index.ts b/packages/compass-context-menu/src/index.ts index 75d933ef767..7a0e27824fb 100644 --- a/packages/compass-context-menu/src/index.ts +++ b/packages/compass-context-menu/src/index.ts @@ -1,5 +1,8 @@ export { useContextMenu } from './use-context-menu'; -export { ContextMenuProvider } from './context-menu-provider'; +export { + ContextMenuProvider, + ContextMenuContext, +} from './context-menu-provider'; export type { ContextMenuItem, ContextMenuItemGroup, diff --git a/packages/compass-context-menu/src/types.ts b/packages/compass-context-menu/src/types.ts index 91e8d65cdcc..163abe56132 100644 --- a/packages/compass-context-menu/src/types.ts +++ b/packages/compass-context-menu/src/types.ts @@ -16,7 +16,7 @@ export type ContextMenuWrapperProps = { menu: ContextMenuState & { close: () => void }; }; -export type ContextMenuContext = { +export type ContextMenuContextType = { close(): void; }; diff --git a/packages/compass-context-menu/src/use-context-menu.tsx b/packages/compass-context-menu/src/use-context-menu.tsx index a60aeba4c69..a0b874b656c 100644 --- a/packages/compass-context-menu/src/use-context-menu.tsx +++ b/packages/compass-context-menu/src/use-context-menu.tsx @@ -1,6 +1,6 @@ import type { RefCallback } from 'react'; import { useContext, useMemo, useRef } from 'react'; -import { Context } from './context-menu-provider'; +import { ContextMenuContext } from './context-menu-provider'; import { appendContextMenuContent } from './context-menu-content'; import type { ContextMenuItem } from './types'; @@ -19,7 +19,7 @@ export type ContextMenuMethods = { export function useContextMenu< T extends ContextMenuItem = ContextMenuItem >(): ContextMenuMethods { - const context = useContext(Context); + const context = useContext(ContextMenuContext); const previous = useRef void]>( null ); diff --git a/packages/compass-crud/src/components/table-view/cell-renderer.tsx b/packages/compass-crud/src/components/table-view/cell-renderer.tsx index dd423f889f3..0ae6e755e7d 100644 --- a/packages/compass-crud/src/components/table-view/cell-renderer.tsx +++ b/packages/compass-crud/src/components/table-view/cell-renderer.tsx @@ -10,13 +10,12 @@ import { css, Icon, IconButton, - LeafyGreenProvider, spacing, withDarkMode, - useContextMenuItems, + useFieldContextMenu, + LeafyGreenProvider, } from '@mongodb-js/compass-components'; import { type Document, Element } from 'hadron-document'; -import { objectToIdiomaticEJSON } from 'hadron-document'; import type { ICellRendererParams } from 'ag-grid-community'; import type { GridActions, TableHeaderType } from '../../stores/grid-store'; import type { CrudActions } from '../../stores/crud-store'; @@ -94,6 +93,7 @@ const decrypdedIconStyles = css({ interface CellContentProps { element: Element | undefined | null; + fieldName: string; cellState: | typeof UNEDITABLE | typeof EMPTY @@ -111,6 +111,7 @@ const CellContent: React.FC = ({ cellState, onUndo, onExpand, + fieldName, }) => { const [, forceUpdate] = useReducer((x: number) => x + 1, 0); const isEmpty = element === undefined || element === null; @@ -118,6 +119,12 @@ const CellContent: React.FC = ({ forceUpdate(); }, []); + // Context menu functionality + const cellContextMenuRef = useFieldContextMenu({ + element, + fieldName, + }); + // Subscribe to element events useEffect(() => { if (!isEmpty && element) { @@ -148,7 +155,7 @@ const CellContent: React.FC = ({ } }, [element]); - const renderContent = useCallback(() => { + const renderContent = useMemo(() => { if (cellState === EMPTY || !element) { return 'No field'; } @@ -192,7 +199,7 @@ const CellContent: React.FC = ({ return (
-
+
{element.decrypted && ( = ({
); - }, [element, elementLength, cellState]); + }, [element, elementLength, cellState, cellContextMenuRef]); const canUndo = cellState === ADDED || @@ -222,7 +229,7 @@ const CellContent: React.FC = ({ <> {canUndo && } {canExpand && } - {renderContent()} + {renderContent} ); }; @@ -256,75 +263,8 @@ const CellRenderer: React.FC = ({ }) => { const element = value as Element | undefined | null; - const isEmpty = element === undefined || element === null; const [isDeleted, setIsDeleted] = useState(false); - // Helper function to check if a string is a URL - const isValidUrl = useCallback((str: string): boolean => { - try { - const url = new URL(str); - return url.protocol === 'http:' || url.protocol === 'https:'; - } catch { - return false; - } - }, []); - - // Add context menu functionality - const contextMenuRef = useContextMenuItems([ - ...(element && !isEmpty - ? [ - { - label: 'Copy field & value', - onAction: () => { - const fieldName = column.getColId(); - const fieldStr = `${fieldName}: ${objectToIdiomaticEJSON( - element.currentValue - )}`; - void navigator.clipboard.writeText(fieldStr); - }, - }, - ] - : []), - ...(element && - element.currentType === 'String' && - isValidUrl(element.currentValue) - ? [ - { - label: 'Open URL in browser', - onAction: () => { - window.open(element.currentValue, '_blank', 'noopener'); - }, - }, - ] - : []), - ...(element && - (element.currentType === 'Object' || element.currentType === 'Array') - ? [ - { - label: 'Expand field', - onAction: () => { - handleDrillDown({ - stopPropagation: () => {}, - } as React.MouseEvent); - }, - }, - ] - : []), - ...(cellState === ADDED || - cellState === EDITED || - cellState === INVALID || - cellState === DELETED - ? [ - { - label: 'Undo changes', - onAction: () => { - handleUndo({ stopPropagation: () => {} } as React.MouseEvent); - }, - }, - ] - : []), - ]); - const isEditable = useMemo(() => { /* Can't get the editable() function from here, so have to reevaluate */ let editable = true; @@ -357,7 +297,7 @@ const CellRenderer: React.FC = ({ if (!isEditable) { cellState = UNEDITABLE; - } else if (isEmpty || isDeleted) { + } else if (!element || isDeleted) { cellState = EMPTY; } else if (!element.isCurrentTypeValid()) { cellState = INVALID; @@ -393,10 +333,10 @@ const CellRenderer: React.FC = ({ [ element, node.data.hadronDocument, + cellState, elementRemoved, elementAdded, elementTypeChanged, - cellState, ] ); @@ -424,7 +364,7 @@ const CellRenderer: React.FC = ({ // `ag-grid` renders this component outside of the context chain // so we re-supply the dark mode theme here. -
+
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/interactive-supports-focus*/}
= ({ > - { - this.props.api.stopEditing(); - if (force) { - void this.props.replaceDocument(this.doc); - } else { - void this.props.updateDocument(this.doc); - } - }} - onDelete={() => { - this.props.api.stopEditing(); - void this.props.removeDocument(this.doc); - }} - onCancel={() => { - if (this.state.mode === 'editing') { - this.handleCancelUpdate(); - } else { - this.handleCancelRemove(); - } - }} - /> + + { + this.props.api.stopEditing(); + if (force) { + void this.props.replaceDocument(this.doc); + } else { + void this.props.updateDocument(this.doc); + } + }} + onDelete={() => { + this.props.api.stopEditing(); + void this.props.removeDocument(this.doc); + }} + onCancel={() => { + if (this.state.mode === 'editing') { + this.handleCancelUpdate(); + } else { + this.handleCancelRemove(); + } + }} + /> + ); } From 6b6e3981b77d32e36ff46b319dfdae3f1babdf1f Mon Sep 17 00:00:00 2001 From: gagik Date: Wed, 18 Jun 2025 11:22:45 +0200 Subject: [PATCH 61/76] fix: throw early on --- .../src/context-menu-provider.tsx | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/packages/compass-context-menu/src/context-menu-provider.tsx b/packages/compass-context-menu/src/context-menu-provider.tsx index 0ae7c5f0e06..4c90da83f63 100644 --- a/packages/compass-context-menu/src/context-menu-provider.tsx +++ b/packages/compass-context-menu/src/context-menu-provider.tsx @@ -24,6 +24,13 @@ export function ContextMenuProvider({ // Check if there's already a parent context menu provider const parentContext = useContext(Context); + // Prevent accidental nested providers + if (parentContext) { + throw new Error( + 'Duplicated ContextMenuProvider found. Please remove the nested provider.' + ); + } + const [menu, setMenu] = useState({ isOpen: false, itemGroups: [], @@ -41,11 +48,6 @@ export function ContextMenuProvider({ ); useEffect(() => { - // If there's a parent provider, don't add event listeners - if (parentContext) { - return; - } - function handleContextMenu(event: MouseEvent) { event.preventDefault(); @@ -73,7 +75,7 @@ export function ContextMenuProvider({ document.removeEventListener('contextmenu', handleContextMenu); window.removeEventListener('resize', handleClosingEvent); }; - }, [handleClosingEvent, parentContext]); + }, [handleClosingEvent]); const value = useMemo( () => ({ @@ -82,13 +84,6 @@ export function ContextMenuProvider({ [close] ); - // Prevent accidental nested providers - if (parentContext) { - throw new Error( - 'Duplicated ContextMenuProvider found. Please remove the nested provider.' - ); - } - const Wrapper = wrapper ?? React.Fragment; return ( From e709f7b39ca6833b2e6ddf70928ea3d18777e5f5 Mon Sep 17 00:00:00 2001 From: Sergey Petushkov Date: Wed, 18 Jun 2025 13:26:18 +0200 Subject: [PATCH 62/76] chore(ci): bump timeouts for e2e tests in test installers tasks COMPASS-9464 (#7037) chore(ci): bump timeouts for e2e tests in test installers tasks --- .github/workflows/test-installers.yml | 4 ++++ packages/compass-e2e-tests/helpers/test-runner-context.ts | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-installers.yml b/.github/workflows/test-installers.yml index 4de63cc6403..4d1540c9518 100644 --- a/.github/workflows/test-installers.yml +++ b/.github/workflows/test-installers.yml @@ -178,6 +178,10 @@ jobs: container: ${{ matrix.container }} env: DEBUG: compass:smoketests:*,compass-e2e-tests:* + # Similar to total task timeout, setting these higher than the default + # value to account for very slow windows machines + COMPASS_E2E_MOCHA_TIMEOUT: 720000 # 12min + COMPASS_E2E_WEBDRIVER_WAITFOR_TIMEOUT: 360000 # 6min steps: - name: Checkout uses: actions/checkout@v2 diff --git a/packages/compass-e2e-tests/helpers/test-runner-context.ts b/packages/compass-e2e-tests/helpers/test-runner-context.ts index b05e059ba32..b0ca880726b 100644 --- a/packages/compass-e2e-tests/helpers/test-runner-context.ts +++ b/packages/compass-e2e-tests/helpers/test-runner-context.ts @@ -49,9 +49,9 @@ function buildCommonArgs(yargs: Argv) { .option('mocha-timeout', { type: 'number', description: 'Set a custom default mocha timeout', - // Kinda arbitrary, but longer than webdriver-waitfor-timeout so the test - // can fail before Mocha times out - default: 240_000, + // 4min, kinda arbitrary, but longer than webdriver-waitfor-timeout so the + // test can fail before Mocha times out + default: 1000 * 60 * 4, }) .option('mocha-bail', { type: 'boolean', From d5cce0d7122e6c9da3f92793c23cc3b26c412384 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Wed, 18 Jun 2025 13:52:48 +0200 Subject: [PATCH 63/76] chore(intercom): Add request to the update server to determine if Intercom integration is allowed COMPASS-9371 (#6982) * Add request to the update server to determine if Intercom integration is allowed * Turn toggleEnableFeedbackPanel async and check isIntercomAllowed * Remove premature optimization of memoizing integrations response * Restore fetch mock correctly * Add debug calls * Fix tests * Fix lint * Use a different domain in proxy tests * Incorporated feedback * Cache response from update server --- .../compass-e2e-tests/tests/proxy.test.ts | 12 ++- .../src/setup-intercom.spec.ts | 67 +++++++++++-- .../compass-intercom/src/setup-intercom.ts | 97 ++++++++++++++++--- packages/compass/src/app/utils/csp.ts | 2 +- 4 files changed, 150 insertions(+), 28 deletions(-) diff --git a/packages/compass-e2e-tests/tests/proxy.test.ts b/packages/compass-e2e-tests/tests/proxy.test.ts index 059e678cf9d..0e9450b3f02 100644 --- a/packages/compass-e2e-tests/tests/proxy.test.ts +++ b/packages/compass-e2e-tests/tests/proxy.test.ts @@ -65,10 +65,12 @@ describe('Proxy support', function () { browser = compass.browser; const result = await browser.execute(async function () { - const response = await fetch('http://compass.mongodb.com/'); + const response = await fetch('http://proxy-test-compass.mongodb.com/'); return await response.text(); }); - expect(result).to.equal('hello, http://compass.mongodb.com/ (proxy1)'); + expect(result).to.equal( + 'hello, http://proxy-test-compass.mongodb.com/ (proxy1)' + ); }); it('can change the proxy option dynamically', async function () { @@ -80,10 +82,12 @@ describe('Proxy support', function () { `http://localhost:${port(httpProxyServer2)}` ); const result = await browser.execute(async function () { - const response = await fetch('http://compass.mongodb.com/'); + const response = await fetch('http://proxy-test-compass.mongodb.com/'); return await response.text(); }); - expect(result).to.equal('hello, http://compass.mongodb.com/ (proxy2)'); + expect(result).to.equal( + 'hello, http://proxy-test-compass.mongodb.com/ (proxy2)' + ); }); context('when connecting to a cluster', function () { diff --git a/packages/compass-intercom/src/setup-intercom.spec.ts b/packages/compass-intercom/src/setup-intercom.spec.ts index 8e0133ad280..2a69c55063e 100644 --- a/packages/compass-intercom/src/setup-intercom.spec.ts +++ b/packages/compass-intercom/src/setup-intercom.spec.ts @@ -2,7 +2,7 @@ import type { SinonStub } from 'sinon'; import sinon from 'sinon'; -import { setupIntercom } from './setup-intercom'; +import { setupIntercom, resetIntercomAllowedCache } from './setup-intercom'; import { expect } from 'chai'; import type { IntercomScript } from './intercom-script'; import type { PreferencesAccess } from 'compass-preferences-model'; @@ -11,6 +11,36 @@ import { type User, } from 'compass-preferences-model'; +// Picking something which won't be blocked by CORS +const FAKE_HADRON_AUTO_UPDATE_ENDPOINT = 'https://compass.mongodb.com'; + +function createMockFetch({ + integrations, +}: { + integrations: Record; +}): typeof globalThis.fetch { + return (url) => { + if (typeof url !== 'string') { + throw new Error('Expected url to be a string'); + } + if (url.startsWith(FAKE_HADRON_AUTO_UPDATE_ENDPOINT)) { + if (url === `${FAKE_HADRON_AUTO_UPDATE_ENDPOINT}/api/v2/integrations`) { + return Promise.resolve({ + ok: true, + json() { + return Promise.resolve(integrations); + }, + } as Response); + } + } else if (url === 'https://widget.intercom.io/widget/appid123') { + // NOTE: we use 301 since intercom will redirects + // to the actual location of the widget script + return Promise.resolve({ status: 301 } as Response); + } + throw new Error(`Unexpected URL called on the fake update server: ${url}`); + }; +} + const mockUser: User = { id: 'user-123', createdAt: new Date(1649432549945), @@ -19,7 +49,10 @@ const mockUser: User = { describe('setupIntercom', function () { let backupEnv: Partial; - let fetchMock: SinonStub; + let fetchMock: SinonStub< + Parameters, + ReturnType + >; let preferences: PreferencesAccess; async function testRunSetupIntercom() { @@ -36,6 +69,7 @@ describe('setupIntercom', function () { beforeEach(async function () { backupEnv = { + HADRON_AUTO_UPDATE_ENDPOINT: process.env.HADRON_AUTO_UPDATE_ENDPOINT, HADRON_METRICS_INTERCOM_APP_ID: process.env.HADRON_METRICS_INTERCOM_APP_ID, HADRON_PRODUCT_NAME: process.env.HADRON_PRODUCT_NAME, @@ -43,15 +77,12 @@ describe('setupIntercom', function () { NODE_ENV: process.env.NODE_ENV, }; + process.env.HADRON_AUTO_UPDATE_ENDPOINT = FAKE_HADRON_AUTO_UPDATE_ENDPOINT; process.env.HADRON_PRODUCT_NAME = 'My App Name' as any; process.env.HADRON_APP_VERSION = 'v0.0.0-test.123'; process.env.NODE_ENV = 'test'; process.env.HADRON_METRICS_INTERCOM_APP_ID = 'appid123'; - fetchMock = sinon.stub(); - window.fetch = fetchMock; - // NOTE: we use 301 since intercom will redirects - // to the actual location of the widget script - fetchMock.resolves({ status: 301 } as Response); + fetchMock = sinon.stub(globalThis, 'fetch'); preferences = await createSandboxFromDefaultPreferences(); await preferences.savePreferences({ enableFeedbackPanel: true, @@ -61,16 +92,23 @@ describe('setupIntercom', function () { }); afterEach(function () { + process.env.HADRON_AUTO_UPDATE_ENDPOINT = + backupEnv.HADRON_AUTO_UPDATE_ENDPOINT; process.env.HADRON_METRICS_INTERCOM_APP_ID = backupEnv.HADRON_METRICS_INTERCOM_APP_ID; process.env.HADRON_PRODUCT_NAME = backupEnv.HADRON_PRODUCT_NAME as any; process.env.HADRON_APP_VERSION = backupEnv.HADRON_APP_VERSION as any; process.env.NODE_ENV = backupEnv.NODE_ENV; - fetchMock.reset(); + fetchMock.restore(); + resetIntercomAllowedCache(); }); describe('when it can be enabled', function () { it('calls intercomScript.load when feedback gets enabled and intercomScript.unload when feedback gets disabled', async function () { + fetchMock.callsFake( + createMockFetch({ integrations: { intercom: true } }) + ); + await preferences.savePreferences({ enableFeedbackPanel: true, }); @@ -100,6 +138,19 @@ describe('setupIntercom', function () { expect(intercomScript.load).not.to.have.been.called; expect(intercomScript.unload).to.have.been.called; }); + + it('calls intercomScript.unload when the update server disables the integration', async function () { + fetchMock.callsFake( + createMockFetch({ integrations: { intercom: false } }) + ); + + await preferences.savePreferences({ + enableFeedbackPanel: true, + }); + const { intercomScript } = await testRunSetupIntercom(); + expect(intercomScript.load).not.to.have.been.called; + expect(intercomScript.unload).to.have.been.called; + }); }); describe('when cannot be enabled', function () { diff --git a/packages/compass-intercom/src/setup-intercom.ts b/packages/compass-intercom/src/setup-intercom.ts index ddc78a213d1..7d537b4fa93 100644 --- a/packages/compass-intercom/src/setup-intercom.ts +++ b/packages/compass-intercom/src/setup-intercom.ts @@ -36,14 +36,26 @@ export async function setupIntercom( app_stage: process.env.NODE_ENV, }; - if (enableFeedbackPanel) { + async function toggleEnableFeedbackPanel(enableFeedbackPanel: boolean) { + if (enableFeedbackPanel && (await isIntercomAllowed())) { + debug('loading intercom script'); + intercomScript.load(metadata); + } else { + debug('unloading intercom script'); + intercomScript.unload(); + } + } + + const shouldLoad = enableFeedbackPanel && (await isIntercomAllowed()); + + if (shouldLoad) { // In some environment the network can be firewalled, this is a safeguard to avoid // uncaught errors when injecting the script. debug('testing intercom availability'); const intercomWidgetUrl = buildIntercomScriptUrl(metadata.app_id); - const response = await window.fetch(intercomWidgetUrl).catch((e) => { + const response = await fetch(intercomWidgetUrl).catch((e) => { debug('fetch failed', e); return null; }); @@ -56,27 +68,82 @@ export async function setupIntercom( debug('intercom is reachable, proceeding with the setup'); } else { debug( - 'not testing intercom connectivity because enableFeedbackPanel == false' + 'not testing intercom connectivity because enableFeedbackPanel == false || isAllowed == false' ); } - const toggleEnableFeedbackPanel = (enableFeedbackPanel: boolean) => { - if (enableFeedbackPanel) { - debug('loading intercom script'); - intercomScript.load(metadata); - } else { - debug('unloading intercom script'); - intercomScript.unload(); - } - }; - - toggleEnableFeedbackPanel(!!enableFeedbackPanel); + try { + await toggleEnableFeedbackPanel(shouldLoad); + } catch (error) { + debug('initial toggle failed', { + error, + }); + } preferences.onPreferenceValueChanged( 'enableFeedbackPanel', (enableFeedbackPanel) => { debug('enableFeedbackPanel changed'); - toggleEnableFeedbackPanel(enableFeedbackPanel); + void toggleEnableFeedbackPanel(enableFeedbackPanel); } ); } + +let isIntercomAllowedPromise: Promise | null = null; + +function isIntercomAllowed(): Promise { + if (!isIntercomAllowedPromise) { + isIntercomAllowedPromise = fetchIntegrations().then( + ({ intercom }) => intercom, + (error) => { + debug( + 'Failed to fetch intercom integration status, defaulting to false', + { error } + ); + return false; + } + ); + } + return isIntercomAllowedPromise; +} + +export function resetIntercomAllowedCache(): void { + isIntercomAllowedPromise = null; +} + +/** + * TODO: Move this to a shared package if we start using it to toggle other integrations. + */ +function getAutoUpdateEndpoint() { + const { HADRON_AUTO_UPDATE_ENDPOINT, HADRON_AUTO_UPDATE_ENDPOINT_OVERRIDE } = + process.env; + const result = + HADRON_AUTO_UPDATE_ENDPOINT_OVERRIDE || HADRON_AUTO_UPDATE_ENDPOINT; + if (!result) { + throw new Error( + 'Expected HADRON_AUTO_UPDATE_ENDPOINT or HADRON_AUTO_UPDATE_ENDPOINT_OVERRIDE to be set' + ); + } + return result; +} + +/** + * Fetches the integrations configuration from the update server. + * TODO: Move this to a shared package if we start using it to toggle other integrations. + */ +async function fetchIntegrations(): Promise<{ intercom: boolean }> { + const url = `${getAutoUpdateEndpoint()}/api/v2/integrations`; + debug('requesting integrations status', { url }); + const response = await fetch(url); + if (!response.ok) { + throw new Error( + `Expected an OK response, got ${response.status} '${response.statusText}'` + ); + } + const result = await response.json(); + debug('got integrations response', { result }); + if (typeof result.intercom !== 'boolean') { + throw new Error(`Expected 'intercom' to be a boolean`); + } + return result; +} diff --git a/packages/compass/src/app/utils/csp.ts b/packages/compass/src/app/utils/csp.ts index 7678c5a52b7..939d81991a3 100644 --- a/packages/compass/src/app/utils/csp.ts +++ b/packages/compass/src/app/utils/csp.ts @@ -89,7 +89,7 @@ export function injectCSP() { extraAllowed.push('ws://localhost:*'); // Used by proxy tests, since Chrome does not like proxying localhost // (this does not result in actual outgoing HTTP requests) - extraAllowed.push('http://compass.mongodb.com/'); + extraAllowed.push('http://proxy-test-compass.mongodb.com/'); } const cspContent = Object.entries(defaultCSP) From c58db3c36e92b2078f27ddd1cf55c8e2b9850206 Mon Sep 17 00:00:00 2001 From: Ruby Dong Date: Wed, 18 Jun 2025 13:11:37 -0400 Subject: [PATCH 64/76] feat: Fix the state management for the two flows (disabled state, displaying covered queries) CLOUDP-325467 (#7036) * fix disabled state of suggested index button when switching between tabs * maintain covered queries in btwn tabs * working version of the state management * do not render suggested index everytime initial query changes * skipping failing tests * combine coveredqueries data into an obj * fix broken tests * updated the state to hold coveredQueriesArr instead * fix error clearing, remove suggestions from start (need to fix), move query + initialQuery to redux * fixed tests and automatically generate suggestions when applicable * clear error when query is updated * default query = null for create index modal * added return for the createIndexOpened call * remove irrelevant comment * update tests, move logic to reducer for hasQueryChanges --- .../create-index-form/create-index-form.tsx | 7 - .../index-flow-section.spec.tsx | 35 +++- .../create-index-form/index-flow-section.tsx | 80 +++++----- .../query-flow-section.spec.tsx | 3 - .../create-index-form/query-flow-section.tsx | 74 ++++----- .../create-index-modal/create-index-modal.tsx | 7 +- .../src/components/indexes/indexes.tsx | 2 +- .../src/modules/create-index.spec.ts | 8 +- .../src/modules/create-index.tsx | 151 +++++++++++++++--- packages/compass-indexes/src/stores/store.ts | 2 +- 10 files changed, 241 insertions(+), 128 deletions(-) diff --git a/packages/compass-indexes/src/components/create-index-form/create-index-form.tsx b/packages/compass-indexes/src/components/create-index-form/create-index-form.tsx index fbe8f45a32c..bdc9d8541e9 100644 --- a/packages/compass-indexes/src/components/create-index-form/create-index-form.tsx +++ b/packages/compass-indexes/src/components/create-index-form/create-index-form.tsx @@ -65,7 +65,6 @@ function CreateIndexForm({ onRemoveFieldClick, onTabClick, showIndexesGuidanceVariant, - query, }: CreateIndexFormProps) { const { id: connectionId } = useConnectionInfo(); const rollingIndexesFeatureEnabled = !!usePreference('enableRollingIndexes'); @@ -93,9 +92,6 @@ function CreateIndexForm({ showIndexesGuidanceVariant && currentTab === 'IndexFlow'; const showIndexesGuidanceQueryFlow = showIndexesGuidanceVariant && currentTab === 'QueryFlow'; - const [inputQuery, setInputQuery] = React.useState( - query ? JSON.stringify(query, null, 2) : '' - ); const { database: dbName, collection: collectionName } = toNS(namespace); @@ -180,9 +176,6 @@ function CreateIndexForm({ serverVersion={serverVersion} dbName={dbName} collectionName={collectionName} - initialQuery={query} - inputQuery={inputQuery} - setInputQuery={setInputQuery} /> )} diff --git a/packages/compass-indexes/src/components/create-index-form/index-flow-section.spec.tsx b/packages/compass-indexes/src/components/create-index-form/index-flow-section.spec.tsx index 8e4c9f15e22..283d10d49be 100644 --- a/packages/compass-indexes/src/components/create-index-form/index-flow-section.spec.tsx +++ b/packages/compass-indexes/src/components/create-index-form/index-flow-section.spec.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { render, screen } from '@mongodb-js/testing-library-compass'; import IndexFlowSection from './index-flow-section'; import { expect } from 'chai'; -import type { Field } from '../../modules/create-index'; +import { ActionTypes, type Field } from '../../modules/create-index'; import { Provider } from 'react-redux'; import { setupStore } from '../../../test/setup-store'; @@ -85,7 +85,15 @@ describe('IndexFlowSection', () => { beforeEach(() => { renderComponent({ fields }); + screen.getByTestId('index-flow-section-covered-queries-button').click(); + store.dispatch({ + type: ActionTypes.FieldsChanged, + fields, + }); + store.dispatch({ + type: ActionTypes.CoveredQueriesFetched, + }); }); it('renders the covered queries examples', () => { @@ -133,7 +141,16 @@ describe('IndexFlowSection', () => { beforeEach(() => { renderComponent({ fields }); + screen.getByTestId('index-flow-section-covered-queries-button').click(); + + store.dispatch({ + type: ActionTypes.FieldsChanged, + fields, + }); + store.dispatch({ + type: ActionTypes.CoveredQueriesFetched, + }); }); it('renders the covered queries examples', () => { @@ -179,7 +196,15 @@ describe('IndexFlowSection', () => { beforeEach(() => { renderComponent({ fields }); + screen.getByTestId('index-flow-section-covered-queries-button').click(); + store.dispatch({ + type: ActionTypes.FieldsChanged, + fields, + }); + store.dispatch({ + type: ActionTypes.CoveredQueriesFetched, + }); }); it('renders the covered queries examples', () => { @@ -208,7 +233,15 @@ describe('IndexFlowSection', () => { beforeEach(() => { renderComponent({ fields }); + screen.getByTestId('index-flow-section-covered-queries-button').click(); + store.dispatch({ + type: ActionTypes.FieldsChanged, + fields, + }); + store.dispatch({ + type: ActionTypes.CoveredQueriesFetched, + }); }); it('renders the covered queries examples', () => { diff --git a/packages/compass-indexes/src/components/create-index-form/index-flow-section.tsx b/packages/compass-indexes/src/components/create-index-form/index-flow-section.tsx index 94e04337a16..1391fc01dbe 100644 --- a/packages/compass-indexes/src/components/create-index-form/index-flow-section.tsx +++ b/packages/compass-indexes/src/components/create-index-form/index-flow-section.tsx @@ -13,10 +13,10 @@ import { Tooltip, useDarkMode, } from '@mongodb-js/compass-components'; -import React, { useState, useCallback, useEffect } from 'react'; +import React, { useState, useCallback } from 'react'; import { - errorCleared, errorEncountered, + fetchCoveredQueries, type Field, } from '../../modules/create-index'; import MDBCodeViewer from './mdb-code-viewer'; @@ -24,6 +24,7 @@ import { areAllFieldsFilledIn } from '../../utils/create-index-modal-validation' import { connect } from 'react-redux'; import type { TrackFunction } from '@mongodb-js/compass-telemetry/provider'; import { useTelemetry } from '@mongodb-js/compass-telemetry/provider'; +import type { RootState } from '../../modules'; const flexContainerStyles = css({ display: 'flex', @@ -106,13 +107,18 @@ export type IndexFlowSectionProps = { dbName: string; collectionName: string; onErrorEncountered: (error: string) => void; - onErrorCleared: () => void; + onCoveredQueriesFetched: () => void; + coveredQueriesArr: Array> | null; + hasIndexFieldChanges: boolean; }; -const generateCoveredQueries = ( - coveredQueriesArr: Array>, +export const generateCoveredQueries = ( + coveredQueriesArr: Array> | null, track: TrackFunction ) => { + if (!coveredQueriesArr) { + return; + } const rows = []; for (let i = 0; i < coveredQueriesArr.length; i++) { const currentRow = Object.assign({}, ...coveredQueriesArr.slice(0, i + 1)); @@ -135,9 +141,12 @@ const generateCoveredQueries = ( return <>{rows}; }; -const generateOptimalQueries = ( - coveredQueriesArr: Array> +export const generateOptimalQueries = ( + coveredQueriesArr: Array> | null ) => { + if (!coveredQueriesArr) { + return; + } const numOfFields = coveredQueriesArr.length; // Do not show for 1 field or less @@ -187,18 +196,25 @@ const generateOptimalQueries = ( ); }; +export const generateCoveredQueriesArr = (fields: Field[]) => { + return fields.map((field, index) => { + return { [field.name]: index + 1 }; + }); +}; + const IndexFlowSection = ({ createIndexFieldsComponent, fields, dbName, collectionName, onErrorEncountered, - onErrorCleared, + onCoveredQueriesFetched, + coveredQueriesArr, + hasIndexFieldChanges, }: IndexFlowSectionProps) => { const darkMode = useDarkMode(); const [isCodeEquivalentToggleChecked, setIsCodeEquivalentToggleChecked] = useState(false); - const [hasFieldChanges, setHasFieldChanges] = useState(false); const hasUnsupportedQueryTypes = fields.some((field) => { return field.type === '2dsphere' || field.type === 'text'; @@ -208,7 +224,7 @@ const IndexFlowSection = ({ const isCoveredQueriesButtonDisabled = !areAllFieldsFilledIn(fields) || hasUnsupportedQueryTypes || - !hasFieldChanges; + !hasIndexFieldChanges; const indexNameTypeMap = fields.reduce>( (accumulator, currentValue) => { @@ -220,45 +236,21 @@ const IndexFlowSection = ({ {} ); - const [coveredQueriesObj, setCoveredQueriesObj] = useState<{ - coveredQueries: JSX.Element; - optimalQueries: string | JSX.Element; - showCoveredQueries: boolean; - }>({ - coveredQueries: <>, - optimalQueries: '', - showCoveredQueries: false, - }); - const onCoveredQueriesButtonClick = useCallback(() => { - const coveredQueriesArr = fields.map((field, index) => { - return { [field.name]: index + 1 }; - }); - track('Covered Queries Button Clicked', { context: 'Create Index Modal', }); try { - setCoveredQueriesObj({ - coveredQueries: generateCoveredQueries(coveredQueriesArr, track), - optimalQueries: generateOptimalQueries(coveredQueriesArr), - showCoveredQueries: true, - }); + onCoveredQueriesFetched(); } catch (e) { onErrorEncountered(e instanceof Error ? e.message : String(e)); } + }, [onCoveredQueriesFetched, onErrorEncountered, track]); - setHasFieldChanges(false); - }, [fields, onErrorEncountered, track]); - - useEffect(() => { - setHasFieldChanges(true); - onErrorCleared(); - }, [fields, onErrorCleared]); - - const { coveredQueries, optimalQueries, showCoveredQueries } = - coveredQueriesObj; + const coveredQueries = generateCoveredQueries(coveredQueriesArr, track); + const optimalQueries = generateOptimalQueries(coveredQueriesArr); + const showCoveredQueries = coveredQueriesArr !== null; return (
@@ -422,13 +414,17 @@ const IndexFlowSection = ({ ); }; -const mapState = () => { - return {}; +const mapState = ({ createIndex }: RootState) => { + const { coveredQueriesArr, hasIndexFieldChanges } = createIndex; + return { + coveredQueriesArr, + hasIndexFieldChanges, + }; }; const mapDispatch = { onErrorEncountered: errorEncountered, - onErrorCleared: errorCleared, + onCoveredQueriesFetched: fetchCoveredQueries, }; export default connect(mapState, mapDispatch)(IndexFlowSection); diff --git a/packages/compass-indexes/src/components/create-index-form/query-flow-section.spec.tsx b/packages/compass-indexes/src/components/create-index-form/query-flow-section.spec.tsx index 8957cc87b63..d151f7b0520 100644 --- a/packages/compass-indexes/src/components/create-index-form/query-flow-section.spec.tsx +++ b/packages/compass-indexes/src/components/create-index-form/query-flow-section.spec.tsx @@ -18,9 +18,6 @@ describe('QueryFlowSection', () => { serverVersion="5.0.0" dbName={dbName} collectionName={collectionName} - initialQuery={null} - inputQuery={''} - setInputQuery={() => {}} /> ); diff --git a/packages/compass-indexes/src/components/create-index-form/query-flow-section.tsx b/packages/compass-indexes/src/components/create-index-form/query-flow-section.tsx index 0d5dfd4d7d1..f07081d3d77 100644 --- a/packages/compass-indexes/src/components/create-index-form/query-flow-section.tsx +++ b/packages/compass-indexes/src/components/create-index-form/query-flow-section.tsx @@ -8,8 +8,7 @@ import { ParagraphSkeleton, useDarkMode, } from '@mongodb-js/compass-components'; -import type { Document } from 'mongodb'; -import React, { useMemo, useCallback, useEffect } from 'react'; +import React, { useMemo, useCallback } from 'react'; import { css, spacing } from '@mongodb-js/compass-components'; import { CodemirrorMultilineEditor, @@ -20,8 +19,10 @@ import type { RootState } from '../../modules'; import { fetchIndexSuggestions } from '../../modules/create-index'; import type { IndexSuggestionState, + QueryUpdatedProps, SuggestedIndexFetchedProps, } from '../../modules/create-index'; +import { queryUpdated } from '../../modules/create-index'; import { connect } from 'react-redux'; import { parseFilter } from 'mongodb-query-parser'; import { useTelemetry } from '@mongodb-js/compass-telemetry/provider'; @@ -105,30 +106,27 @@ const QueryFlowSection = ({ indexSuggestions, fetchingSuggestionsState, initialQuery, - inputQuery, - setInputQuery, + query, + hasQueryChanges, + onQueryUpdated, }: { schemaFields: { name: string; description?: string }[]; serverVersion: string; dbName: string; collectionName: string; onSuggestedIndexButtonClick: ({ - dbName, - collectionName, - inputQuery, + query, }: SuggestedIndexFetchedProps) => Promise; indexSuggestions: Record | null; fetchingSuggestionsState: IndexSuggestionState; - initialQuery: Document | null; - inputQuery: string; - setInputQuery: (query: string) => void; + initialQuery: string | null; + query: string; + hasQueryChanges: boolean; + onQueryUpdated: ({ query }: QueryUpdatedProps) => void; }) => { const track = useTelemetry(); const darkMode = useDarkMode(); - const [hasNewChanges, setHasNewChanges] = React.useState( - initialQuery !== null - ); const [isShowSuggestionsButtonDisabled, setIsShowSuggestionsButtonDisabled] = React.useState(true); @@ -149,15 +147,12 @@ const QueryFlowSection = ({ }); const generateSuggestedIndexes = useCallback(() => { - const sanitizedInputQuery = inputQuery.trim(); + const sanitizedInputQuery = query.trim(); void onSuggestedIndexButtonClick({ - dbName, - collectionName, - inputQuery: sanitizedInputQuery, + query: sanitizedInputQuery, }); - setHasNewChanges(false); - }, [inputQuery, dbName, collectionName, onSuggestedIndexButtonClick]); + }, [query, onSuggestedIndexButtonClick]); const handleSuggestedIndexButtonClick = () => { generateSuggestedIndexes(); @@ -166,20 +161,21 @@ const QueryFlowSection = ({ }); }; - const handleQueryInputChange = useCallback((text: string) => { - setInputQuery(text); - setHasNewChanges(true); - }, []); + const handleQueryInputChange = useCallback( + (text: string) => { + onQueryUpdated({ query: text }); + }, + [onQueryUpdated] + ); const isFetchingIndexSuggestions = fetchingSuggestionsState === 'fetching'; - // Validate query upon typing useMemo(() => { - let _isShowSuggestionsButtonDisabled = !hasNewChanges; + let _isShowSuggestionsButtonDisabled = !hasQueryChanges; try { - parseFilter(inputQuery); + parseFilter(query); - if (!inputQuery.startsWith('{') || !inputQuery.endsWith('}')) { + if (!query.startsWith('{') || !query.endsWith('}')) { _isShowSuggestionsButtonDisabled = true; } } catch { @@ -187,13 +183,7 @@ const QueryFlowSection = ({ } finally { setIsShowSuggestionsButtonDisabled(_isShowSuggestionsButtonDisabled); } - }, [hasNewChanges, inputQuery]); - - useEffect(() => { - if (initialQuery !== null) { - generateSuggestedIndexes(); - } - }, [initialQuery]); + }, [hasQueryChanges, query]); return ( <> @@ -221,7 +211,7 @@ const QueryFlowSection = ({ showAnnotationsGutter={false} copyable={false} formattable={false} - text={inputQuery} + text={query} onChangeText={(text) => handleQueryInputChange(text)} placeholder="Type a query: { field: 'value' }" completer={completer} @@ -282,17 +272,27 @@ const QueryFlowSection = ({ }; const mapState = ({ createIndex }: RootState) => { - const { indexSuggestions, sampleDocs, fetchingSuggestionsState } = - createIndex; + const { + indexSuggestions, + sampleDocs, + fetchingSuggestionsState, + query, + initialQuery, + hasQueryChanges, + } = createIndex; return { indexSuggestions, sampleDocs, fetchingSuggestionsState, + query, + initialQuery, + hasQueryChanges, }; }; const mapDispatch = { onSuggestedIndexButtonClick: fetchIndexSuggestions, + onQueryUpdated: queryUpdated, }; export default connect(mapState, mapDispatch)(QueryFlowSection); diff --git a/packages/compass-indexes/src/components/create-index-modal/create-index-modal.tsx b/packages/compass-indexes/src/components/create-index-modal/create-index-modal.tsx index fc9a8842cd5..b49fe7b8ae7 100644 --- a/packages/compass-indexes/src/components/create-index-modal/create-index-modal.tsx +++ b/packages/compass-indexes/src/components/create-index-modal/create-index-modal.tsx @@ -30,14 +30,12 @@ import { import { useConnectionInfoRef } from '@mongodb-js/compass-connections/provider'; import { usePreference } from 'compass-preferences-model/provider'; import CreateIndexModalHeader from './create-index-modal-header'; -import type { Document } from 'mongodb'; type CreateIndexModalProps = React.ComponentProps & { isVisible: boolean; namespace: string; error: string | null; currentTab: Tab; - query: Document | null; onErrorBannerCloseClick: () => void; onCreateIndexClick: () => void; onCancelCreateIndexClick: () => void; @@ -52,7 +50,6 @@ function CreateIndexModal({ onErrorBannerCloseClick, onCreateIndexClick, onCancelCreateIndexClick, - query, ...props }: CreateIndexModalProps) { const connectionInfoRef = useConnectionInfoRef(); @@ -117,7 +114,6 @@ function CreateIndexModal({ namespace={namespace} showIndexesGuidanceVariant={showIndexesGuidanceVariant} currentTab={currentTab} - query={query} /> @@ -134,7 +130,7 @@ function CreateIndexModal({ } const mapState = ({ namespace, serverVersion, createIndex }: RootState) => { - const { fields, error, isVisible, currentTab, query } = createIndex; + const { fields, error, isVisible, currentTab } = createIndex; return { fields, error, @@ -142,7 +138,6 @@ const mapState = ({ namespace, serverVersion, createIndex }: RootState) => { namespace, serverVersion, currentTab, - query, }; }; diff --git a/packages/compass-indexes/src/components/indexes/indexes.tsx b/packages/compass-indexes/src/components/indexes/indexes.tsx index 285f90aa86d..0e512e13ce2 100644 --- a/packages/compass-indexes/src/components/indexes/indexes.tsx +++ b/packages/compass-indexes/src/components/indexes/indexes.tsx @@ -181,7 +181,7 @@ export function Indexes({ - +
); } diff --git a/packages/compass-indexes/src/modules/create-index.spec.ts b/packages/compass-indexes/src/modules/create-index.spec.ts index ce88ec3ec2c..b0cb75a9c27 100644 --- a/packages/compass-indexes/src/modules/create-index.spec.ts +++ b/packages/compass-indexes/src/modules/create-index.spec.ts @@ -209,25 +209,25 @@ describe('create-index module', function () { describe('createIndexOpened', function () { const query = EJSON.serialize({}); it('sets isVisible=true', function () { - store.dispatch(createIndexOpened()); + void store.dispatch(createIndexOpened()); expect(store.getState().createIndex.isVisible).to.equal(true); }); it('sets isVisible=true with a query', function () { - store.dispatch(createIndexOpened({ query })); + void store.dispatch(createIndexOpened({ query })); expect(store.getState().createIndex.isVisible).to.equal(true); }); it('sets currentTab=IndexFlow if no query is provided', function () { - store.dispatch(createIndexOpened()); + void store.dispatch(createIndexOpened()); expect(store.getState().createIndex.currentTab).to.equal('IndexFlow'); }); it('sets currentTab=QueryFlow if a query is provided', function () { - store.dispatch(createIndexOpened({ query })); + void store.dispatch(createIndexOpened({ query })); expect(store.getState().createIndex.currentTab).to.equal('QueryFlow'); }); diff --git a/packages/compass-indexes/src/modules/create-index.tsx b/packages/compass-indexes/src/modules/create-index.tsx index 4e239d7f8e0..061c567150e 100644 --- a/packages/compass-indexes/src/modules/create-index.tsx +++ b/packages/compass-indexes/src/modules/create-index.tsx @@ -10,6 +10,7 @@ import type { IndexesThunkAction, RootState } from '.'; import { createRegularIndex } from './regular-indexes'; import * as mql from 'mongodb-mql-engines'; import _parseShellBSON, { ParseMode } from '@mongodb-js/shell-bson-parser'; +import toNS from 'mongodb-ns'; export enum ActionTypes { FieldAdded = 'compass-indexes/create-index/fields/field-added', @@ -30,8 +31,13 @@ export enum ActionTypes { TabUpdated = 'compass-indexes/create-index/tab-updated', + // Query Flow SuggestedIndexesRequested = 'compass-indexes/create-index/suggested-indexes-requested', SuggestedIndexesFetched = 'compass-indexes/create-index/suggested-indexes-fetched', + QueryUpdatedAction = 'compass-indexes/create-index/clear-has-query-changes', + + // Index Flow + CoveredQueriesFetched = 'compass-indexes/create-index/covered-queries-fetched', } // fields @@ -76,7 +82,7 @@ type ErrorClearedAction = { export type CreateIndexOpenedAction = { type: ActionTypes.CreateIndexOpened; - query?: Document; + initialQuery?: Document; }; type CreateIndexClosedAction = { @@ -313,7 +319,19 @@ export type State = { sampleDocs: Array | null; // base query to be used for query flow index creation - query: Document | null; + query: string; + + // the initial query that user had prefilled from the insights nudge in the documents tab + initialQuery: string; + + // to determine whether there has been new query changes since user last pressed the button + hasQueryChanges: boolean; + + // covered queries array for the index flow to keep track of what the user last sees after pressing the covered queries button + coveredQueriesArr: Array> | null; + + // to determine whether there has been new index field changes since user last pressed the button + hasIndexFieldChanges: boolean; }; export const INITIAL_STATE: State = { @@ -323,10 +341,18 @@ export const INITIAL_STATE: State = { fields: INITIAL_FIELDS_STATE, options: INITIAL_OPTIONS_STATE, currentTab: 'IndexFlow', + + // Query flow fetchingSuggestionsState: 'initial', indexSuggestions: null, sampleDocs: null, - query: null, + query: '', + initialQuery: '', + hasQueryChanges: false, + + // Index flow + coveredQueriesArr: null, + hasIndexFieldChanges: false, }; function getInitialState(): State { @@ -338,10 +364,27 @@ function getInitialState(): State { //------- -export const createIndexOpened = (query?: Document) => ({ - type: ActionTypes.CreateIndexOpened, - query, -}); +export const createIndexOpened = ( + initialQuery?: Document +): IndexesThunkAction< + Promise, + SuggestedIndexFetchedAction | CreateIndexOpenedAction +> => { + return async (dispatch) => { + dispatch({ + type: ActionTypes.CreateIndexOpened, + initialQuery, + }); + + if (initialQuery) { + await dispatch( + fetchIndexSuggestions({ + query: JSON.stringify(initialQuery, null, 2) || '', + }) + ); + } + }; +}; export const createIndexClosed = () => ({ type: ActionTypes.CreateIndexClosed, @@ -373,20 +416,25 @@ export type SuggestedIndexFetchedAction = { }; export type SuggestedIndexFetchedProps = { - dbName: string; - collectionName: string; - inputQuery: string; + query: string; +}; + +export type CoveredQueriesFetchedAction = { + type: ActionTypes.CoveredQueriesFetched; +}; + +export type QueryUpdatedProps = { + query: string; +}; + +export type QueryUpdatedAction = { + type: ActionTypes.QueryUpdatedAction; + query: string; }; export const fetchIndexSuggestions = ({ - dbName, - collectionName, - inputQuery, -}: { - dbName: string; - collectionName: string; - inputQuery: string; -}): IndexesThunkAction< + query, +}: SuggestedIndexFetchedProps): IndexesThunkAction< Promise, SuggestedIndexFetchedAction | SuggestedIndexesRequestedAction > => { @@ -394,12 +442,11 @@ export const fetchIndexSuggestions = ({ dispatch({ type: ActionTypes.SuggestedIndexesRequested, }); - const namespace = `${dbName}.${collectionName}`; + const namespace = getState().namespace; // Get sample documents from state if it's already there, otherwise fetch it let sampleDocuments: Array | null = getState().createIndex.sampleDocs || null; - // If it's null, that means it has not been fetched before if (sampleDocuments === null) { try { @@ -426,16 +473,17 @@ export const fetchIndexSuggestions = ({ // Analyze namespace and fetch suggestions try { + const { database, collection } = toNS(getState().namespace); const analyzedNamespace = mql.analyzeNamespace( - { database: dbName, collection: collectionName }, + { database, collection }, sampleDocuments ); - const query = mql.parseQuery( - _parseShellBSON(inputQuery, { mode: ParseMode.Loose }), + const parsedQuery = mql.parseQuery( + _parseShellBSON(query, { mode: ParseMode.Loose }), analyzedNamespace ); - const results = await mql.suggestIndex([query]); + const results = await mql.suggestIndex([parsedQuery]); const indexSuggestions = results?.index; if ( @@ -461,6 +509,17 @@ export const fetchIndexSuggestions = ({ }; }; +export const fetchCoveredQueries = (): CoveredQueriesFetchedAction => ({ + type: ActionTypes.CoveredQueriesFetched, +}); + +export const queryUpdated = ({ + query, +}: QueryUpdatedProps): QueryUpdatedAction => ({ + type: ActionTypes.QueryUpdatedAction, + query, +}); + function isEmptyValue(value: unknown) { if (value === '') { return true; @@ -639,6 +698,8 @@ const reducer: Reducer = (state = INITIAL_STATE, action) => { return { ...state, fields: [...state.fields, { name: '', type: '' }], + hasIndexFieldChanges: true, + error: null, }; } @@ -648,6 +709,8 @@ const reducer: Reducer = (state = INITIAL_STATE, action) => { return { ...state, fields, + hasIndexFieldChanges: true, + error: null, }; } @@ -661,6 +724,8 @@ const reducer: Reducer = (state = INITIAL_STATE, action) => { return { ...state, fields, + hasIndexFieldChanges: true, + error: null, }; } @@ -668,6 +733,8 @@ const reducer: Reducer = (state = INITIAL_STATE, action) => { return { ...state, fields: action.fields, + hasIndexFieldChanges: true, + error: null, }; } @@ -705,11 +772,17 @@ const reducer: Reducer = (state = INITIAL_STATE, action) => { if ( isAction(action, ActionTypes.CreateIndexOpened) ) { + const parsedInitialQuery = action.initialQuery + ? JSON.stringify(action.initialQuery, null, 2) + : ''; + return { ...getInitialState(), isVisible: true, - query: action.query ?? null, - currentTab: action.query ? 'QueryFlow' : 'IndexFlow', + // get it from the current query or initial query from insights nudge + query: state.query || parsedInitialQuery, + initialQuery: parsedInitialQuery, + currentTab: action.initialQuery ? 'QueryFlow' : 'IndexFlow', }; } @@ -733,6 +806,7 @@ const reducer: Reducer = (state = INITIAL_STATE, action) => { return { ...state, isVisible: false, + query: '', }; } @@ -781,6 +855,31 @@ const reducer: Reducer = (state = INITIAL_STATE, action) => { error: action.error, indexSuggestions: action.indexSuggestions, sampleDocs: action.sampleDocs, + hasQueryChanges: false, + }; + } + + if ( + isAction( + action, + ActionTypes.CoveredQueriesFetched + ) + ) { + return { + ...state, + coveredQueriesArr: state.fields.map((field, index) => { + return { [field.name]: index + 1 }; + }), + hasIndexFieldChanges: false, + }; + } + + if (isAction(action, ActionTypes.QueryUpdatedAction)) { + return { + ...state, + query: action.query, + hasQueryChanges: true, + error: null, }; } diff --git a/packages/compass-indexes/src/stores/store.ts b/packages/compass-indexes/src/stores/store.ts index ddfd17d042b..0a0a8ee0628 100644 --- a/packages/compass-indexes/src/stores/store.ts +++ b/packages/compass-indexes/src/stores/store.ts @@ -132,7 +132,7 @@ export function activateIndexesPlugin( localAppRegistry, 'open-create-index-modal', (openCreateModalRequest?: { query: Document }) => { - store.dispatch(createIndexOpened(openCreateModalRequest?.query)); + void store.dispatch(createIndexOpened(openCreateModalRequest?.query)); } ); From bd10dc4f68c998227065bbee1459d91534593b27 Mon Sep 17 00:00:00 2001 From: "mongodb-devtools-bot[bot]" <189715634+mongodb-devtools-bot[bot]@users.noreply.github.com> Date: Wed, 18 Jun 2025 13:26:58 -0400 Subject: [PATCH 65/76] chore(release): bump package versions (#7032) --- package-lock.json | 1030 ++++++++--------- packages/atlas-service/package.json | 6 +- packages/compass-aggregations/package.json | 28 +- packages/compass-app-stores/package.json | 10 +- packages/compass-collection/package.json | 12 +- packages/compass-components/package.json | 2 +- .../package.json | 10 +- .../package.json | 12 +- packages/compass-connections/package.json | 10 +- packages/compass-crud/package.json | 24 +- packages/compass-data-modeling/package.json | 14 +- packages/compass-e2e-tests/package.json | 4 +- packages/compass-editor/package.json | 4 +- packages/compass-explain-plan/package.json | 12 +- .../compass-export-to-language/package.json | 14 +- packages/compass-field-store/package.json | 4 +- packages/compass-find-in-page/package.json | 4 +- packages/compass-generative-ai/package.json | 12 +- packages/compass-global-writes/package.json | 12 +- packages/compass-import-export/package.json | 12 +- packages/compass-indexes/package.json | 18 +- packages/compass-intercom/package.json | 4 +- .../package.json | 4 +- .../compass-preferences-model/package.json | 4 +- packages/compass-query-bar/package.json | 24 +- .../package.json | 16 +- .../compass-schema-validation/package.json | 24 +- packages/compass-schema/package.json | 20 +- packages/compass-serverstats/package.json | 10 +- packages/compass-settings/package.json | 10 +- packages/compass-shell/package.json | 12 +- packages/compass-sidebar/package.json | 20 +- packages/compass-smoke-tests/package.json | 4 +- packages/compass-web/package.json | 48 +- packages/compass-welcome/package.json | 10 +- packages/compass-workspaces/package.json | 10 +- packages/compass/package.json | 64 +- packages/connection-form/package.json | 6 +- packages/connection-storage/package.json | 4 +- .../databases-collections-list/package.json | 10 +- packages/databases-collections/package.json | 20 +- packages/instance-model/package.json | 4 +- packages/my-queries-storage/package.json | 4 +- 43 files changed, 793 insertions(+), 793 deletions(-) diff --git a/package-lock.json b/package-lock.json index 33e294fdb7a..2fcbb642825 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42869,10 +42869,10 @@ }, "packages/atlas-service": { "name": "@mongodb-js/atlas-service", - "version": "0.45.0", + "version": "0.46.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-components": "^1.38.1", + "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", "@mongodb-js/compass-user-data": "^0.7.2", @@ -42881,7 +42881,7 @@ "@mongodb-js/devtools-connect": "^3.7.2", "@mongodb-js/devtools-proxy-support": "^0.4.4", "@mongodb-js/oidc-plugin": "^1.1.7", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "electron": "^36.4.0", "hadron-app-registry": "^9.4.11", "hadron-ipc": "^3.5.2", @@ -43007,46 +43007,46 @@ "devDependencies": { "@electron/rebuild": "^4.0.1", "@electron/remote": "^2.1.2", - "@mongodb-js/atlas-service": "^0.45.0", - "@mongodb-js/compass-aggregations": "^9.62.0", - "@mongodb-js/compass-app-stores": "^7.46.0", - "@mongodb-js/compass-collection": "^4.59.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connection-import-export": "^0.56.0", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-crud": "^13.60.0", - "@mongodb-js/compass-data-modeling": "^1.11.0", - "@mongodb-js/compass-databases-collections": "^1.59.0", - "@mongodb-js/compass-explain-plan": "^6.60.0", - "@mongodb-js/compass-export-to-language": "^9.36.0", - "@mongodb-js/compass-field-store": "^9.35.0", - "@mongodb-js/compass-find-in-page": "^4.39.2", - "@mongodb-js/compass-generative-ai": "^0.40.0", - "@mongodb-js/compass-global-writes": "^1.19.0", - "@mongodb-js/compass-import-export": "^7.59.0", - "@mongodb-js/compass-indexes": "^5.59.0", - "@mongodb-js/compass-intercom": "^0.24.2", + "@mongodb-js/atlas-service": "^0.46.0", + "@mongodb-js/compass-aggregations": "^9.63.0", + "@mongodb-js/compass-app-stores": "^7.47.0", + "@mongodb-js/compass-collection": "^4.60.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connection-import-export": "^0.57.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-crud": "^13.61.0", + "@mongodb-js/compass-data-modeling": "^1.12.0", + "@mongodb-js/compass-databases-collections": "^1.60.0", + "@mongodb-js/compass-explain-plan": "^6.61.0", + "@mongodb-js/compass-export-to-language": "^9.37.0", + "@mongodb-js/compass-field-store": "^9.36.0", + "@mongodb-js/compass-find-in-page": "^4.40.0", + "@mongodb-js/compass-generative-ai": "^0.41.0", + "@mongodb-js/compass-global-writes": "^1.20.0", + "@mongodb-js/compass-import-export": "^7.60.0", + "@mongodb-js/compass-indexes": "^5.60.0", + "@mongodb-js/compass-intercom": "^0.25.0", "@mongodb-js/compass-logging": "^1.7.2", - "@mongodb-js/compass-query-bar": "^8.61.0", - "@mongodb-js/compass-saved-aggregations-queries": "^1.60.0", - "@mongodb-js/compass-schema": "^6.61.0", - "@mongodb-js/compass-schema-validation": "^6.60.0", - "@mongodb-js/compass-serverstats": "^16.59.0", - "@mongodb-js/compass-settings": "^0.58.0", - "@mongodb-js/compass-shell": "^3.59.0", - "@mongodb-js/compass-sidebar": "^5.60.0", + "@mongodb-js/compass-query-bar": "^8.62.0", + "@mongodb-js/compass-saved-aggregations-queries": "^1.61.0", + "@mongodb-js/compass-schema": "^6.62.0", + "@mongodb-js/compass-schema-validation": "^6.61.0", + "@mongodb-js/compass-serverstats": "^16.60.0", + "@mongodb-js/compass-settings": "^0.59.0", + "@mongodb-js/compass-shell": "^3.60.0", + "@mongodb-js/compass-sidebar": "^5.61.0", "@mongodb-js/compass-telemetry": "^1.10.0", "@mongodb-js/compass-utils": "^0.9.2", - "@mongodb-js/compass-welcome": "^0.58.0", - "@mongodb-js/compass-workspaces": "^0.41.0", + "@mongodb-js/compass-welcome": "^0.59.0", + "@mongodb-js/compass-workspaces": "^0.42.0", "@mongodb-js/connection-info": "^0.15.2", - "@mongodb-js/connection-storage": "^0.35.0", + "@mongodb-js/connection-storage": "^0.36.0", "@mongodb-js/devtools-proxy-support": "^0.4.4", "@mongodb-js/eslint-config-compass": "^1.3.10", "@mongodb-js/get-os-info": "^0.4.0", "@mongodb-js/mocha-config-compass": "^1.6.8", "@mongodb-js/mongodb-downloader": "^0.3.7", - "@mongodb-js/my-queries-storage": "^0.27.3", + "@mongodb-js/my-queries-storage": "^0.28.0", "@mongodb-js/prettier-config-compass": "^1.2.8", "@mongodb-js/sbom-tools": "^0.7.2", "@mongodb-js/signing-utils": "^0.3.8", @@ -43059,7 +43059,7 @@ "chai": "^4.3.4", "chalk": "^4.1.2", "clean-stack": "^2.0.0", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "cross-spawn": "^7.0.5", "debug": "^4.3.4", "depcheck": "^1.4.1", @@ -43098,7 +43098,7 @@ }, "packages/compass-aggregations": { "name": "@mongodb-js/compass-aggregations", - "version": "9.62.0", + "version": "9.63.0", "license": "SSPL", "dependencies": { "@babel/generator": "^7.19.5", @@ -43107,25 +43107,25 @@ "@dnd-kit/core": "^6.0.7", "@dnd-kit/sortable": "^7.0.2", "@dnd-kit/utilities": "^3.2.1", - "@mongodb-js/atlas-service": "^0.45.0", - "@mongodb-js/compass-app-stores": "^7.46.0", - "@mongodb-js/compass-collection": "^4.59.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-crud": "^13.60.0", - "@mongodb-js/compass-editor": "^0.40.2", - "@mongodb-js/compass-field-store": "^9.35.0", - "@mongodb-js/compass-generative-ai": "^0.40.0", + "@mongodb-js/atlas-service": "^0.46.0", + "@mongodb-js/compass-app-stores": "^7.47.0", + "@mongodb-js/compass-collection": "^4.60.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-crud": "^13.61.0", + "@mongodb-js/compass-editor": "^0.41.0", + "@mongodb-js/compass-field-store": "^9.36.0", + "@mongodb-js/compass-generative-ai": "^0.41.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", "@mongodb-js/compass-utils": "^0.9.2", - "@mongodb-js/compass-workspaces": "^0.41.0", + "@mongodb-js/compass-workspaces": "^0.42.0", "@mongodb-js/explain-plan-helper": "^1.4.10", "@mongodb-js/mongodb-constants": "^0.11.0", - "@mongodb-js/my-queries-storage": "^0.27.3", + "@mongodb-js/my-queries-storage": "^0.28.0", "@mongodb-js/shell-bson-parser": "^1.2.0", "bson": "^6.10.3", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "hadron-app-registry": "^9.4.11", "hadron-document": "^8.8.12", "hadron-type-checker": "^7.4.10", @@ -43134,7 +43134,7 @@ "mongodb-collection-model": "^5.29.2", "mongodb-data-service": "^22.28.2", "mongodb-database-model": "^2.29.2", - "mongodb-instance-model": "^12.32.2", + "mongodb-instance-model": "^12.33.0", "mongodb-ns": "^2.4.2", "mongodb-query-parser": "^4.3.0", "mongodb-schema": "^12.6.2", @@ -43331,18 +43331,18 @@ }, "packages/compass-app-stores": { "name": "@mongodb-js/compass-app-stores", - "version": "7.46.0", + "version": "7.47.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/connection-info": "^0.15.2", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "hadron-app-registry": "^9.4.11", "mongodb-collection-model": "^5.29.2", "mongodb-database-model": "^2.29.2", - "mongodb-instance-model": "^12.32.2", + "mongodb-instance-model": "^12.33.0", "mongodb-ns": "^2.4.2", "react": "^17.0.2" }, @@ -43394,18 +43394,18 @@ }, "packages/compass-collection": { "name": "@mongodb-js/compass-collection", - "version": "4.59.0", + "version": "4.60.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-app-stores": "^7.46.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", + "@mongodb-js/compass-app-stores": "^7.47.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", - "@mongodb-js/compass-workspaces": "^0.41.0", + "@mongodb-js/compass-workspaces": "^0.42.0", "@mongodb-js/connection-info": "^0.15.2", "@mongodb-js/mongodb-constants": "^0.11.0", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "hadron-app-registry": "^9.4.11", "mongodb-collection-model": "^5.29.2", "mongodb-ns": "^2.4.2", @@ -43475,7 +43475,7 @@ }, "packages/compass-components": { "name": "@mongodb-js/compass-components", - "version": "1.38.1", + "version": "1.39.0", "license": "SSPL", "dependencies": { "@dnd-kit/core": "^6.0.7", @@ -43721,13 +43721,13 @@ }, "packages/compass-connection-import-export": { "name": "@mongodb-js/compass-connection-import-export", - "version": "0.56.0", + "version": "0.57.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/connection-storage": "^0.35.0", - "compass-preferences-model": "^2.40.2", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/connection-storage": "^0.36.0", + "compass-preferences-model": "^2.41.0", "hadron-ipc": "^3.5.2", "react": "^17.0.2" }, @@ -43780,18 +43780,18 @@ }, "packages/compass-connections": { "name": "@mongodb-js/compass-connections", - "version": "1.60.0", + "version": "1.61.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-components": "^1.38.1", + "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", "@mongodb-js/compass-utils": "^0.9.2", - "@mongodb-js/connection-form": "^1.52.3", + "@mongodb-js/connection-form": "^1.53.0", "@mongodb-js/connection-info": "^0.15.2", - "@mongodb-js/connection-storage": "^0.35.0", + "@mongodb-js/connection-storage": "^0.36.0", "bson": "^6.10.3", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "hadron-app-registry": "^9.4.11", "lodash": "^4.17.21", "mongodb": "^6.16.0", @@ -43829,15 +43829,15 @@ }, "packages/compass-connections-navigation": { "name": "@mongodb-js/compass-connections-navigation", - "version": "1.59.0", + "version": "1.60.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-workspaces": "^0.41.0", - "@mongodb-js/connection-form": "^1.52.3", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-workspaces": "^0.42.0", + "@mongodb-js/connection-form": "^1.53.0", "@mongodb-js/connection-info": "^0.15.2", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "mongodb-build-info": "^1.7.2", "react": "^17.0.2", "react-virtualized-auto-sizer": "^1.0.6", @@ -43922,27 +43922,27 @@ }, "packages/compass-crud": { "name": "@mongodb-js/compass-crud", - "version": "13.60.0", + "version": "13.61.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-app-stores": "^7.46.0", - "@mongodb-js/compass-collection": "^4.59.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-editor": "^0.40.2", - "@mongodb-js/compass-field-store": "^9.35.0", + "@mongodb-js/compass-app-stores": "^7.47.0", + "@mongodb-js/compass-collection": "^4.60.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-editor": "^0.41.0", + "@mongodb-js/compass-field-store": "^9.36.0", "@mongodb-js/compass-logging": "^1.7.2", - "@mongodb-js/compass-query-bar": "^8.61.0", + "@mongodb-js/compass-query-bar": "^8.62.0", "@mongodb-js/compass-telemetry": "^1.10.0", - "@mongodb-js/compass-workspaces": "^0.41.0", + "@mongodb-js/compass-workspaces": "^0.42.0", "@mongodb-js/explain-plan-helper": "^1.4.10", - "@mongodb-js/my-queries-storage": "^0.27.3", + "@mongodb-js/my-queries-storage": "^0.28.0", "@mongodb-js/reflux-state-mixin": "^1.2.10", "@mongodb-js/shell-bson-parser": "^1.2.0", "ag-grid-community": "^20.2.0", "ag-grid-react": "^20.2.0", "bson": "^6.10.3", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "hadron-app-registry": "^9.4.11", "hadron-document": "^8.8.12", "hadron-type-checker": "^7.4.10", @@ -43974,7 +43974,7 @@ "electron-mocha": "^12.2.0", "enzyme": "^3.11.0", "mocha": "^10.2.0", - "mongodb-instance-model": "^12.32.2", + "mongodb-instance-model": "^12.33.0", "nyc": "^15.1.0", "react-dom": "^17.0.2", "sinon": "^8.1.1", @@ -44018,20 +44018,20 @@ }, "packages/compass-data-modeling": { "name": "@mongodb-js/compass-data-modeling", - "version": "1.11.0", + "version": "1.12.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-app-stores": "^7.46.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-editor": "^0.40.2", + "@mongodb-js/compass-app-stores": "^7.47.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-editor": "^0.41.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", "@mongodb-js/compass-user-data": "^0.7.2", - "@mongodb-js/compass-workspaces": "^0.41.0", + "@mongodb-js/compass-workspaces": "^0.42.0", "@mongodb-js/diagramming": "^1.0.2", "bson": "^6.10.3", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "hadron-app-registry": "^9.4.11", "lodash": "^4.17.21", "mongodb": "^6.14.1", @@ -44350,7 +44350,7 @@ } }, "packages/compass-e2e-tests": { - "version": "1.33.0", + "version": "1.33.1", "devDependencies": { "@electron/rebuild": "^4.0.1", "@mongodb-js/compass-test-server": "^0.3.10", @@ -44367,7 +44367,7 @@ "chai": "^4.3.4", "chai-as-promised": "^7.1.1", "clipboardy": "^2.3.0", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "cross-spawn": "^7.0.5", "debug": "^4.3.4", "depcheck": "^1.4.1", @@ -44786,7 +44786,7 @@ }, "packages/compass-editor": { "name": "@mongodb-js/compass-editor", - "version": "0.40.2", + "version": "0.41.0", "license": "SSPL", "dependencies": { "@codemirror/autocomplete": "^6.17.0", @@ -44798,7 +44798,7 @@ "@codemirror/state": "^6.1.4", "@codemirror/view": "^6.7.1", "@lezer/highlight": "^1.2.0", - "@mongodb-js/compass-components": "^1.38.1", + "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/mongodb-constants": "^0.11.0", "mongodb-query-parser": "^4.3.0", "polished": "^4.2.2", @@ -44887,17 +44887,17 @@ }, "packages/compass-explain-plan": { "name": "@mongodb-js/compass-explain-plan", - "version": "6.60.0", + "version": "6.61.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-collection": "^4.59.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-editor": "^0.40.2", + "@mongodb-js/compass-collection": "^4.60.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-editor": "^0.41.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", "@mongodb-js/explain-plan-helper": "^1.4.10", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "d3": "^3.5.17", "d3-flextree": "^2.1.2", "d3-hierarchy": "^3.1.2", @@ -44965,18 +44965,18 @@ }, "packages/compass-export-to-language": { "name": "@mongodb-js/compass-export-to-language", - "version": "9.36.0", + "version": "9.37.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-collection": "^4.59.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-editor": "^0.40.2", - "@mongodb-js/compass-maybe-protect-connection-string": "^0.38.2", + "@mongodb-js/compass-collection": "^4.60.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-editor": "^0.41.0", + "@mongodb-js/compass-maybe-protect-connection-string": "^0.39.0", "@mongodb-js/compass-telemetry": "^1.10.0", "@mongodb-js/shell-bson-parser": "^1.2.0", "bson-transpilers": "^3.2.10", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "hadron-app-registry": "^9.4.11", "mongodb-ns": "^2.4.2", "react": "^17.0.2", @@ -45039,10 +45039,10 @@ }, "packages/compass-field-store": { "name": "@mongodb-js/compass-field-store", - "version": "9.35.0", + "version": "9.36.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-connections": "^1.60.0", + "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-logging": "^1.7.2", "hadron-app-registry": "^9.4.11", "lodash": "^4.17.21", @@ -45200,10 +45200,10 @@ }, "packages/compass-find-in-page": { "name": "@mongodb-js/compass-find-in-page", - "version": "4.39.2", + "version": "4.40.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-components": "^1.38.1", + "@mongodb-js/compass-components": "^1.39.0", "hadron-app-registry": "^9.4.11", "hadron-ipc": "^3.5.2", "react": "^17.0.2", @@ -45264,18 +45264,18 @@ }, "packages/compass-generative-ai": { "name": "@mongodb-js/compass-generative-ai", - "version": "0.40.0", + "version": "0.41.0", "license": "SSPL", "dependencies": { - "@mongodb-js/atlas-service": "^0.45.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-intercom": "^0.24.2", + "@mongodb-js/atlas-service": "^0.46.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-intercom": "^0.25.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", "@mongodb-js/compass-utils": "^0.9.2", "bson": "^6.10.3", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "hadron-app-registry": "^9.4.11", "mongodb": "^6.16.0", "mongodb-schema": "^12.6.2", @@ -45470,14 +45470,14 @@ }, "packages/compass-global-writes": { "name": "@mongodb-js/compass-global-writes", - "version": "1.19.0", + "version": "1.20.0", "license": "SSPL", "dependencies": { - "@mongodb-js/atlas-service": "^0.45.0", - "@mongodb-js/compass-collection": "^4.59.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-field-store": "^9.35.0", + "@mongodb-js/atlas-service": "^0.46.0", + "@mongodb-js/compass-collection": "^4.60.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-field-store": "^9.36.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", "hadron-app-registry": "^9.4.11", @@ -45662,19 +45662,19 @@ }, "packages/compass-import-export": { "name": "@mongodb-js/compass-import-export", - "version": "7.59.0", + "version": "7.60.0", "license": "SSPL", "dependencies": { "@electron/remote": "^2.1.2", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-editor": "^0.40.2", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-editor": "^0.41.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", "@mongodb-js/compass-utils": "^0.9.2", - "@mongodb-js/compass-workspaces": "^0.41.0", + "@mongodb-js/compass-workspaces": "^0.42.0", "bson": "^6.10.3", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "debug": "^4.3.4", "electron": "^36.4.0", "hadron-app-registry": "^9.4.11", @@ -45881,22 +45881,22 @@ }, "packages/compass-indexes": { "name": "@mongodb-js/compass-indexes", - "version": "5.59.0", + "version": "5.60.0", "license": "SSPL", "dependencies": { - "@mongodb-js/atlas-service": "^0.45.0", - "@mongodb-js/compass-app-stores": "^7.46.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-editor": "^0.40.2", - "@mongodb-js/compass-field-store": "^9.35.0", + "@mongodb-js/atlas-service": "^0.46.0", + "@mongodb-js/compass-app-stores": "^7.47.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-editor": "^0.41.0", + "@mongodb-js/compass-field-store": "^9.36.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", - "@mongodb-js/compass-workspaces": "^0.41.0", + "@mongodb-js/compass-workspaces": "^0.42.0", "@mongodb-js/mongodb-constants": "^0.11.0", "@mongodb-js/shell-bson-parser": "^1.2.0", "bson": "^6.10.3", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "hadron-app-registry": "^9.4.11", "lodash": "^4.17.21", "mongodb": "^6.16.0", @@ -46046,11 +46046,11 @@ }, "packages/compass-intercom": { "name": "@mongodb-js/compass-intercom", - "version": "0.24.2", + "version": "0.25.0", "license": "SSPL", "dependencies": { "@mongodb-js/compass-logging": "^1.7.2", - "compass-preferences-model": "^2.40.2" + "compass-preferences-model": "^2.41.0" }, "devDependencies": { "@mongodb-js/eslint-config-compass": "^1.3.10", @@ -46207,10 +46207,10 @@ }, "packages/compass-maybe-protect-connection-string": { "name": "@mongodb-js/compass-maybe-protect-connection-string", - "version": "0.38.2", + "version": "0.39.0", "license": "SSPL", "dependencies": { - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "mongodb-connection-string-url": "^3.0.1" }, "devDependencies": { @@ -46258,10 +46258,10 @@ } }, "packages/compass-preferences-model": { - "version": "2.40.2", + "version": "2.41.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-components": "^1.38.1", + "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-user-data": "^0.7.2", "@mongodb-js/devtools-proxy-support": "^0.4.4", @@ -46323,27 +46323,27 @@ }, "packages/compass-query-bar": { "name": "@mongodb-js/compass-query-bar", - "version": "8.61.0", + "version": "8.62.0", "license": "SSPL", "dependencies": { - "@mongodb-js/atlas-service": "^0.45.0", - "@mongodb-js/compass-app-stores": "^7.46.0", - "@mongodb-js/compass-collection": "^4.59.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-editor": "^0.40.2", - "@mongodb-js/compass-field-store": "^9.35.0", - "@mongodb-js/compass-generative-ai": "^0.40.0", + "@mongodb-js/atlas-service": "^0.46.0", + "@mongodb-js/compass-app-stores": "^7.47.0", + "@mongodb-js/compass-collection": "^4.60.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-editor": "^0.41.0", + "@mongodb-js/compass-field-store": "^9.36.0", + "@mongodb-js/compass-generative-ai": "^0.41.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", "@mongodb-js/mongodb-constants": "^0.11.0", - "@mongodb-js/my-queries-storage": "^0.27.3", + "@mongodb-js/my-queries-storage": "^0.28.0", "bson": "^6.10.3", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "hadron-app-registry": "^9.4.11", "lodash": "^4.17.21", "mongodb": "^6.16.0", - "mongodb-instance-model": "^12.32.2", + "mongodb-instance-model": "^12.33.0", "mongodb-ns": "^2.4.2", "mongodb-query-parser": "^4.3.0", "mongodb-query-util": "^2.4.10", @@ -46536,20 +46536,20 @@ }, "packages/compass-saved-aggregations-queries": { "name": "@mongodb-js/compass-saved-aggregations-queries", - "version": "1.60.0", + "version": "1.61.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-app-stores": "^7.46.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", + "@mongodb-js/compass-app-stores": "^7.47.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", - "@mongodb-js/compass-workspaces": "^0.41.0", - "@mongodb-js/connection-form": "^1.52.3", + "@mongodb-js/compass-workspaces": "^0.42.0", + "@mongodb-js/connection-form": "^1.53.0", "@mongodb-js/connection-info": "^0.15.2", - "@mongodb-js/my-queries-storage": "^0.27.3", + "@mongodb-js/my-queries-storage": "^0.28.0", "bson": "^6.10.3", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "fuse.js": "^6.5.3", "hadron-app-registry": "^9.4.11", "mongodb-ns": "^2.4.2", @@ -46610,20 +46610,20 @@ }, "packages/compass-schema": { "name": "@mongodb-js/compass-schema", - "version": "6.61.0", + "version": "6.62.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-collection": "^4.59.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-editor": "^0.40.2", - "@mongodb-js/compass-field-store": "^9.35.0", + "@mongodb-js/compass-collection": "^4.60.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-editor": "^0.41.0", + "@mongodb-js/compass-field-store": "^9.36.0", "@mongodb-js/compass-logging": "^1.7.2", - "@mongodb-js/compass-query-bar": "^8.61.0", + "@mongodb-js/compass-query-bar": "^8.62.0", "@mongodb-js/compass-telemetry": "^1.10.0", - "@mongodb-js/connection-storage": "^0.35.0", + "@mongodb-js/connection-storage": "^0.36.0", "bson": "^6.10.3", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "d3": "^3.5.17", "hadron-app-registry": "^9.4.11", "hadron-document": "^8.8.12", @@ -46646,7 +46646,7 @@ "devDependencies": { "@mongodb-js/eslint-config-compass": "^1.3.10", "@mongodb-js/mocha-config-compass": "^1.6.8", - "@mongodb-js/my-queries-storage": "^0.27.3", + "@mongodb-js/my-queries-storage": "^0.28.0", "@mongodb-js/prettier-config-compass": "^1.2.8", "@mongodb-js/testing-library-compass": "^1.3.2", "@mongodb-js/tsconfig-compass": "^1.2.8", @@ -46669,23 +46669,23 @@ }, "packages/compass-schema-validation": { "name": "@mongodb-js/compass-schema-validation", - "version": "6.60.0", + "version": "6.61.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-app-stores": "^7.46.0", - "@mongodb-js/compass-collection": "^4.59.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-crud": "^13.60.0", - "@mongodb-js/compass-editor": "^0.40.2", - "@mongodb-js/compass-field-store": "^9.35.0", + "@mongodb-js/compass-app-stores": "^7.47.0", + "@mongodb-js/compass-collection": "^4.60.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-crud": "^13.61.0", + "@mongodb-js/compass-editor": "^0.41.0", + "@mongodb-js/compass-field-store": "^9.36.0", "@mongodb-js/compass-logging": "^1.7.2", - "@mongodb-js/compass-schema": "^6.61.0", + "@mongodb-js/compass-schema": "^6.62.0", "@mongodb-js/compass-telemetry": "^1.10.0", - "@mongodb-js/compass-workspaces": "^0.41.0", + "@mongodb-js/compass-workspaces": "^0.42.0", "@mongodb-js/mongodb-constants": "^0.11.0", "bson": "^6.10.3", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "hadron-app-registry": "^9.4.11", "javascript-stringify": "^2.0.1", "lodash": "^4.17.21", @@ -46710,7 +46710,7 @@ "electron-mocha": "^12.2.0", "hadron-ipc": "^3.5.2", "mocha": "^10.2.0", - "mongodb-instance-model": "^12.32.2", + "mongodb-instance-model": "^12.33.0", "nyc": "^15.1.0", "react-dom": "^17.0.2", "sinon": "^8.1.1", @@ -46882,14 +46882,14 @@ }, "packages/compass-serverstats": { "name": "@mongodb-js/compass-serverstats", - "version": "16.59.0", + "version": "16.60.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-app-stores": "^7.46.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", + "@mongodb-js/compass-app-stores": "^7.47.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-telemetry": "^1.10.0", - "@mongodb-js/compass-workspaces": "^0.41.0", + "@mongodb-js/compass-workspaces": "^0.42.0", "d3": "^3.5.17", "d3-timer": "^1.0.3", "debug": "^4.3.4", @@ -46930,14 +46930,14 @@ }, "packages/compass-settings": { "name": "@mongodb-js/compass-settings", - "version": "0.58.0", + "version": "0.59.0", "license": "SSPL", "dependencies": { - "@mongodb-js/atlas-service": "^0.45.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-generative-ai": "^0.40.0", + "@mongodb-js/atlas-service": "^0.46.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-generative-ai": "^0.41.0", "@mongodb-js/compass-logging": "^1.7.2", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "hadron-app-registry": "^9.4.11", "hadron-ipc": "^3.5.2", "react": "^17.0.2", @@ -46997,22 +46997,22 @@ }, "packages/compass-shell": { "name": "@mongodb-js/compass-shell", - "version": "3.59.0", + "version": "3.60.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-editor": "^0.40.2", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-editor": "^0.41.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", "@mongodb-js/compass-user-data": "^0.7.2", "@mongodb-js/compass-utils": "^0.9.2", - "@mongodb-js/compass-workspaces": "^0.41.0", + "@mongodb-js/compass-workspaces": "^0.42.0", "@mongosh/browser-repl": "^3.12.0", "@mongosh/logging": "^3.8.0", "@mongosh/node-runtime-worker-thread": "^3.3.10", "bson": "^6.10.3", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "hadron-app-registry": "^9.4.11", "react": "^17.0.2", "react-redux": "^8.1.3", @@ -47318,24 +47318,24 @@ }, "packages/compass-sidebar": { "name": "@mongodb-js/compass-sidebar", - "version": "5.60.0", + "version": "5.61.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-app-stores": "^7.46.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connection-import-export": "^0.56.0", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-connections-navigation": "^1.59.0", + "@mongodb-js/compass-app-stores": "^7.47.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connection-import-export": "^0.57.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-connections-navigation": "^1.60.0", "@mongodb-js/compass-logging": "^1.7.2", - "@mongodb-js/compass-maybe-protect-connection-string": "^0.38.2", + "@mongodb-js/compass-maybe-protect-connection-string": "^0.39.0", "@mongodb-js/compass-telemetry": "^1.10.0", - "@mongodb-js/compass-workspaces": "^0.41.0", + "@mongodb-js/compass-workspaces": "^0.42.0", "@mongodb-js/connection-info": "^0.15.2", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "hadron-app-registry": "^9.4.11", "lodash": "^4.17.21", "mongodb": "^6.16.0", - "mongodb-instance-model": "^12.32.2", + "mongodb-instance-model": "^12.33.0", "mongodb-ns": "^2.4.2", "react": "^17.0.2", "react-redux": "^8.1.3", @@ -47395,7 +47395,7 @@ }, "packages/compass-smoke-tests": { "name": "@mongodb-js/compass-smoke-tests", - "version": "1.1.19", + "version": "1.1.20", "license": "SSPL", "devDependencies": { "@actions/github": "^6.0.0", @@ -47403,7 +47403,7 @@ "@mongodb-js/prettier-config-compass": "^1.2.8", "@mongodb-js/tsconfig-compass": "^1.2.8", "@types/node": "^20", - "compass-e2e-tests": "^1.33.0", + "compass-e2e-tests": "^1.33.1", "debug": "^4.3.4", "depcheck": "^1.4.1", "hadron-build": "^25.8.2", @@ -47777,33 +47777,33 @@ }, "packages/compass-web": { "name": "@mongodb-js/compass-web", - "version": "0.17.3", + "version": "0.17.4", "license": "SSPL", "devDependencies": { - "@mongodb-js/atlas-service": "^0.45.0", - "@mongodb-js/compass-aggregations": "^9.62.0", - "@mongodb-js/compass-app-stores": "^7.46.0", - "@mongodb-js/compass-collection": "^4.59.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-crud": "^13.60.0", - "@mongodb-js/compass-data-modeling": "^1.11.0", - "@mongodb-js/compass-databases-collections": "^1.59.0", - "@mongodb-js/compass-explain-plan": "^6.60.0", - "@mongodb-js/compass-export-to-language": "^9.36.0", - "@mongodb-js/compass-field-store": "^9.35.0", - "@mongodb-js/compass-generative-ai": "^0.40.0", - "@mongodb-js/compass-global-writes": "^1.19.0", - "@mongodb-js/compass-indexes": "^5.59.0", + "@mongodb-js/atlas-service": "^0.46.0", + "@mongodb-js/compass-aggregations": "^9.63.0", + "@mongodb-js/compass-app-stores": "^7.47.0", + "@mongodb-js/compass-collection": "^4.60.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-crud": "^13.61.0", + "@mongodb-js/compass-data-modeling": "^1.12.0", + "@mongodb-js/compass-databases-collections": "^1.60.0", + "@mongodb-js/compass-explain-plan": "^6.61.0", + "@mongodb-js/compass-export-to-language": "^9.37.0", + "@mongodb-js/compass-field-store": "^9.36.0", + "@mongodb-js/compass-generative-ai": "^0.41.0", + "@mongodb-js/compass-global-writes": "^1.20.0", + "@mongodb-js/compass-indexes": "^5.60.0", "@mongodb-js/compass-logging": "^1.7.2", - "@mongodb-js/compass-query-bar": "^8.61.0", - "@mongodb-js/compass-schema": "^6.61.0", - "@mongodb-js/compass-schema-validation": "^6.60.0", - "@mongodb-js/compass-sidebar": "^5.60.0", + "@mongodb-js/compass-query-bar": "^8.62.0", + "@mongodb-js/compass-schema": "^6.62.0", + "@mongodb-js/compass-schema-validation": "^6.61.0", + "@mongodb-js/compass-sidebar": "^5.61.0", "@mongodb-js/compass-telemetry": "^1.10.0", - "@mongodb-js/compass-welcome": "^0.58.0", - "@mongodb-js/compass-workspaces": "^0.41.0", - "@mongodb-js/connection-storage": "^0.35.0", + "@mongodb-js/compass-welcome": "^0.59.0", + "@mongodb-js/compass-workspaces": "^0.42.0", + "@mongodb-js/connection-storage": "^0.36.0", "@mongodb-js/devtools-proxy-support": "^0.4.4", "@mongodb-js/eslint-config-compass": "^1.3.10", "@mongodb-js/mocha-config-compass": "^1.6.8", @@ -47822,7 +47822,7 @@ "bson": "^6.2.0", "buffer": "^6.0.3", "chai": "^4.3.6", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "crypto-browserify": "^3.12.0", "debug": "^4.3.4", "depcheck": "^1.4.1", @@ -48034,15 +48034,15 @@ }, "packages/compass-welcome": { "name": "@mongodb-js/compass-welcome", - "version": "0.58.0", + "version": "0.59.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", - "@mongodb-js/compass-workspaces": "^0.41.0", - "compass-preferences-model": "^2.40.2", + "@mongodb-js/compass-workspaces": "^0.42.0", + "compass-preferences-model": "^2.41.0", "hadron-app-registry": "^9.4.11", "react": "^17.0.2", "redux": "^4.2.1", @@ -48098,15 +48098,15 @@ }, "packages/compass-workspaces": { "name": "@mongodb-js/compass-workspaces", - "version": "0.41.0", + "version": "0.42.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-app-stores": "^7.46.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", + "@mongodb-js/compass-app-stores": "^7.47.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-logging": "^1.7.2", "bson": "^6.10.3", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "hadron-app-registry": "^9.4.11", "lodash": "^4.17.21", "mongodb-collection-model": "^5.29.2", @@ -48423,11 +48423,11 @@ }, "packages/connection-form": { "name": "@mongodb-js/connection-form", - "version": "1.52.3", + "version": "1.53.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-editor": "^0.40.2", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-editor": "^0.41.0", "@mongodb-js/connection-info": "^0.15.2", "@mongodb-js/shell-bson-parser": "^1.2.0", "lodash": "^4.17.21", @@ -48639,7 +48639,7 @@ }, "packages/connection-storage": { "name": "@mongodb-js/connection-storage", - "version": "0.35.0", + "version": "0.36.0", "license": "SSPL", "dependencies": { "@mongodb-js/compass-logging": "^1.7.2", @@ -48648,7 +48648,7 @@ "@mongodb-js/compass-utils": "^0.9.2", "@mongodb-js/connection-info": "^0.15.2", "bson": "^6.10.3", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "electron": "^36.4.0", "hadron-app-registry": "^9.4.11", "hadron-ipc": "^3.5.2", @@ -48950,24 +48950,24 @@ }, "packages/databases-collections": { "name": "@mongodb-js/compass-databases-collections", - "version": "1.59.0", + "version": "1.60.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-app-stores": "^7.46.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-editor": "^0.40.2", + "@mongodb-js/compass-app-stores": "^7.47.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-editor": "^0.41.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", - "@mongodb-js/compass-workspaces": "^0.41.0", - "@mongodb-js/databases-collections-list": "^1.57.0", - "@mongodb-js/my-queries-storage": "^0.27.3", - "compass-preferences-model": "^2.40.2", + "@mongodb-js/compass-workspaces": "^0.42.0", + "@mongodb-js/databases-collections-list": "^1.58.0", + "@mongodb-js/my-queries-storage": "^0.28.0", + "compass-preferences-model": "^2.41.0", "hadron-app-registry": "^9.4.11", "lodash": "^4.17.21", "mongodb-collection-model": "^5.29.2", "mongodb-database-model": "^2.29.2", - "mongodb-instance-model": "^12.32.2", + "mongodb-instance-model": "^12.33.0", "mongodb-ns": "^2.4.2", "mongodb-query-parser": "^4.3.0", "prop-types": "^15.7.2", @@ -48996,15 +48996,15 @@ }, "packages/databases-collections-list": { "name": "@mongodb-js/databases-collections-list", - "version": "1.57.0", + "version": "1.58.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-telemetry": "^1.10.0", - "@mongodb-js/compass-workspaces": "^0.41.0", + "@mongodb-js/compass-workspaces": "^0.42.0", "@mongodb-js/connection-info": "^0.15.2", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "mongodb-collection-model": "^5.29.2", "mongodb-database-model": "^2.29.2", "mongodb-ns": "^2.4.2", @@ -50224,11 +50224,11 @@ }, "packages/instance-model": { "name": "mongodb-instance-model", - "version": "12.32.2", + "version": "12.33.0", "license": "SSPL", "dependencies": { "ampersand-model": "^8.0.1", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "mongodb-collection-model": "^5.29.2", "mongodb-data-service": "^22.28.2", "mongodb-database-model": "^2.29.2" @@ -50304,10 +50304,10 @@ }, "packages/my-queries-storage": { "name": "@mongodb-js/my-queries-storage", - "version": "0.27.3", + "version": "0.28.0", "license": "SSPL", "dependencies": { - "@mongodb-js/compass-editor": "^0.40.2", + "@mongodb-js/compass-editor": "^0.41.0", "@mongodb-js/compass-user-data": "^0.7.2", "bson": "^6.10.3", "hadron-app-registry": "^9.4.11", @@ -56228,7 +56228,7 @@ "@mongodb-js/atlas-service": { "version": "file:packages/atlas-service", "requires": { - "@mongodb-js/compass-components": "^1.38.1", + "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", "@mongodb-js/compass-user-data": "^0.7.2", @@ -56246,7 +56246,7 @@ "@types/mocha": "^9.0.0", "@types/sinon-chai": "^3.2.5", "chai": "^4.3.6", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "depcheck": "^1.4.1", "electron": "^36.4.0", "hadron-app-registry": "^9.4.11", @@ -56292,24 +56292,24 @@ "@dnd-kit/core": "^6.0.7", "@dnd-kit/sortable": "^7.0.2", "@dnd-kit/utilities": "^3.2.1", - "@mongodb-js/atlas-service": "^0.45.0", - "@mongodb-js/compass-app-stores": "^7.46.0", - "@mongodb-js/compass-collection": "^4.59.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-crud": "^13.60.0", - "@mongodb-js/compass-editor": "^0.40.2", - "@mongodb-js/compass-field-store": "^9.35.0", - "@mongodb-js/compass-generative-ai": "^0.40.0", + "@mongodb-js/atlas-service": "^0.46.0", + "@mongodb-js/compass-app-stores": "^7.47.0", + "@mongodb-js/compass-collection": "^4.60.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-crud": "^13.61.0", + "@mongodb-js/compass-editor": "^0.41.0", + "@mongodb-js/compass-field-store": "^9.36.0", + "@mongodb-js/compass-generative-ai": "^0.41.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", "@mongodb-js/compass-utils": "^0.9.2", - "@mongodb-js/compass-workspaces": "^0.41.0", + "@mongodb-js/compass-workspaces": "^0.42.0", "@mongodb-js/eslint-config-compass": "^1.3.10", "@mongodb-js/explain-plan-helper": "^1.4.10", "@mongodb-js/mocha-config-compass": "^1.6.8", "@mongodb-js/mongodb-constants": "^0.11.0", - "@mongodb-js/my-queries-storage": "^0.27.3", + "@mongodb-js/my-queries-storage": "^0.28.0", "@mongodb-js/prettier-config-compass": "^1.2.8", "@mongodb-js/shell-bson-parser": "^1.2.0", "@mongodb-js/testing-library-compass": "^1.3.2", @@ -56319,7 +56319,7 @@ "@types/semver": "^7.3.9", "bson": "^6.10.3", "chai": "^4.3.6", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "depcheck": "^1.4.1", "electron-mocha": "^12.2.0", "hadron-app-registry": "^9.4.11", @@ -56331,7 +56331,7 @@ "mongodb-collection-model": "^5.29.2", "mongodb-data-service": "^22.28.2", "mongodb-database-model": "^2.29.2", - "mongodb-instance-model": "^12.32.2", + "mongodb-instance-model": "^12.33.0", "mongodb-ns": "^2.4.2", "mongodb-query-parser": "^4.3.0", "mongodb-schema": "^12.6.2", @@ -56471,8 +56471,8 @@ "@mongodb-js/compass-app-stores": { "version": "file:packages/compass-app-stores", "requires": { - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/connection-info": "^0.15.2", "@mongodb-js/eslint-config-compass": "^1.3.10", @@ -56484,14 +56484,14 @@ "@types/mocha": "^9.0.0", "@types/sinon-chai": "^3.2.5", "chai": "^4.3.6", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "depcheck": "^1.4.1", "electron-mocha": "^12.2.0", "hadron-app-registry": "^9.4.11", "mocha": "^10.2.0", "mongodb-collection-model": "^5.29.2", "mongodb-database-model": "^2.29.2", - "mongodb-instance-model": "^12.32.2", + "mongodb-instance-model": "^12.33.0", "mongodb-ns": "^2.4.2", "nyc": "^15.1.0", "react": "^17.0.2", @@ -56525,12 +56525,12 @@ "@mongodb-js/compass-collection": { "version": "file:packages/compass-collection", "requires": { - "@mongodb-js/compass-app-stores": "^7.46.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", + "@mongodb-js/compass-app-stores": "^7.47.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", - "@mongodb-js/compass-workspaces": "^0.41.0", + "@mongodb-js/compass-workspaces": "^0.42.0", "@mongodb-js/connection-info": "^0.15.2", "@mongodb-js/eslint-config-compass": "^1.3.10", "@mongodb-js/mocha-config-compass": "^1.6.8", @@ -56545,7 +56545,7 @@ "@types/react-dom": "^17.0.10", "@types/sinon-chai": "^3.2.5", "chai": "^4.3.6", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "depcheck": "^1.4.1", "electron-mocha": "^12.2.0", "hadron-app-registry": "^9.4.11", @@ -56809,9 +56809,9 @@ "@mongodb-js/compass-connection-import-export": { "version": "file:packages/compass-connection-import-export", "requires": { - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/connection-storage": "^0.35.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/connection-storage": "^0.36.0", "@mongodb-js/eslint-config-compass": "^1.3.10", "@mongodb-js/mocha-config-compass": "^1.6.8", "@mongodb-js/prettier-config-compass": "^1.2.8", @@ -56823,7 +56823,7 @@ "@types/react": "^17.0.5", "@types/sinon-chai": "^3.2.5", "chai": "^4.3.6", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "depcheck": "^1.4.1", "gen-esm-wrapper": "^1.1.0", "hadron-ipc": "^3.5.2", @@ -56861,13 +56861,13 @@ "@mongodb-js/compass-connections": { "version": "file:packages/compass-connections", "requires": { - "@mongodb-js/compass-components": "^1.38.1", + "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", "@mongodb-js/compass-utils": "^0.9.2", - "@mongodb-js/connection-form": "^1.52.3", + "@mongodb-js/connection-form": "^1.53.0", "@mongodb-js/connection-info": "^0.15.2", - "@mongodb-js/connection-storage": "^0.35.0", + "@mongodb-js/connection-storage": "^0.36.0", "@mongodb-js/eslint-config-compass": "^1.3.10", "@mongodb-js/mocha-config-compass": "^1.6.8", "@mongodb-js/prettier-config-compass": "^1.2.8", @@ -56882,7 +56882,7 @@ "@types/sinon-chai": "^3.2.5", "bson": "^6.10.3", "chai": "^4.3.4", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "depcheck": "^1.4.1", "electron-mocha": "^12.2.0", "hadron-app-registry": "^9.4.11", @@ -56930,10 +56930,10 @@ "@mongodb-js/compass-connections-navigation": { "version": "file:packages/compass-connections-navigation", "requires": { - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-workspaces": "^0.41.0", - "@mongodb-js/connection-form": "^1.52.3", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-workspaces": "^0.42.0", + "@mongodb-js/connection-form": "^1.53.0", "@mongodb-js/connection-info": "^0.15.2", "@mongodb-js/eslint-config-compass": "^1.3.10", "@mongodb-js/mocha-config-compass": "^1.6.8", @@ -56949,7 +56949,7 @@ "@types/react-window": "^1.8.5", "@types/sinon-chai": "^3.2.5", "chai": "^4.3.4", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "depcheck": "^1.4.1", "mocha": "^10.2.0", "mongodb-build-info": "^1.7.2", @@ -56989,21 +56989,21 @@ "@mongodb-js/compass-crud": { "version": "file:packages/compass-crud", "requires": { - "@mongodb-js/compass-app-stores": "^7.46.0", - "@mongodb-js/compass-collection": "^4.59.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-editor": "^0.40.2", - "@mongodb-js/compass-field-store": "^9.35.0", + "@mongodb-js/compass-app-stores": "^7.47.0", + "@mongodb-js/compass-collection": "^4.60.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-editor": "^0.41.0", + "@mongodb-js/compass-field-store": "^9.36.0", "@mongodb-js/compass-logging": "^1.7.2", - "@mongodb-js/compass-query-bar": "^8.61.0", + "@mongodb-js/compass-query-bar": "^8.62.0", "@mongodb-js/compass-telemetry": "^1.10.0", "@mongodb-js/compass-test-server": "^0.3.10", - "@mongodb-js/compass-workspaces": "^0.41.0", + "@mongodb-js/compass-workspaces": "^0.42.0", "@mongodb-js/eslint-config-compass": "^1.3.10", "@mongodb-js/explain-plan-helper": "^1.4.10", "@mongodb-js/mocha-config-compass": "^1.6.8", - "@mongodb-js/my-queries-storage": "^0.27.3", + "@mongodb-js/my-queries-storage": "^0.28.0", "@mongodb-js/prettier-config-compass": "^1.2.8", "@mongodb-js/reflux-state-mixin": "^1.2.10", "@mongodb-js/shell-bson-parser": "^1.2.0", @@ -57016,7 +57016,7 @@ "bson": "^6.10.3", "chai": "^4.1.2", "chai-as-promised": "^7.1.1", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "depcheck": "^1.4.1", "electron": "^36.4.0", "electron-mocha": "^12.2.0", @@ -57029,7 +57029,7 @@ "mocha": "^10.2.0", "mongodb": "^6.16.0", "mongodb-data-service": "^22.28.2", - "mongodb-instance-model": "^12.32.2", + "mongodb-instance-model": "^12.33.0", "mongodb-ns": "^2.4.2", "mongodb-query-parser": "^4.3.0", "numeral": "^2.0.6", @@ -57072,14 +57072,14 @@ "@mongodb-js/compass-data-modeling": { "version": "file:packages/compass-data-modeling", "requires": { - "@mongodb-js/compass-app-stores": "^7.46.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-editor": "^0.40.2", + "@mongodb-js/compass-app-stores": "^7.47.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-editor": "^0.41.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", "@mongodb-js/compass-user-data": "^0.7.2", - "@mongodb-js/compass-workspaces": "^0.41.0", + "@mongodb-js/compass-workspaces": "^0.42.0", "@mongodb-js/diagramming": "^1.0.2", "@mongodb-js/eslint-config-compass": "^1.3.10", "@mongodb-js/mocha-config-compass": "^1.6.8", @@ -57094,7 +57094,7 @@ "@types/sinon-chai": "^3.2.5", "bson": "^6.10.3", "chai": "^4.3.6", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "depcheck": "^1.4.1", "hadron-app-registry": "^9.4.11", "lodash": "^4.17.21", @@ -57318,23 +57318,23 @@ "@mongodb-js/compass-databases-collections": { "version": "file:packages/databases-collections", "requires": { - "@mongodb-js/compass-app-stores": "^7.46.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-editor": "^0.40.2", + "@mongodb-js/compass-app-stores": "^7.47.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-editor": "^0.41.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", - "@mongodb-js/compass-workspaces": "^0.41.0", - "@mongodb-js/databases-collections-list": "^1.57.0", + "@mongodb-js/compass-workspaces": "^0.42.0", + "@mongodb-js/databases-collections-list": "^1.58.0", "@mongodb-js/eslint-config-compass": "^1.3.10", "@mongodb-js/mocha-config-compass": "^1.6.8", - "@mongodb-js/my-queries-storage": "^0.27.3", + "@mongodb-js/my-queries-storage": "^0.28.0", "@mongodb-js/prettier-config-compass": "^1.2.8", "@mongodb-js/testing-library-compass": "^1.3.2", "@mongodb-js/tsconfig-compass": "^1.2.8", "bson": "^6.10.3", "chai": "^4.2.0", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "depcheck": "^1.4.1", "enzyme": "^3.11.0", "hadron-app-registry": "^9.4.11", @@ -57342,7 +57342,7 @@ "mocha": "^10.2.0", "mongodb-collection-model": "^5.29.2", "mongodb-database-model": "^2.29.2", - "mongodb-instance-model": "^12.32.2", + "mongodb-instance-model": "^12.33.0", "mongodb-ns": "^2.4.2", "mongodb-query-parser": "^4.3.0", "nyc": "^15.1.0", @@ -57410,7 +57410,7 @@ "@codemirror/state": "^6.1.4", "@codemirror/view": "^6.7.1", "@lezer/highlight": "^1.2.0", - "@mongodb-js/compass-components": "^1.38.1", + "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/eslint-config-compass": "^1.3.10", "@mongodb-js/mocha-config-compass": "^1.6.8", "@mongodb-js/mongodb-constants": "^0.11.0", @@ -57486,10 +57486,10 @@ "@mongodb-js/compass-explain-plan": { "version": "file:packages/compass-explain-plan", "requires": { - "@mongodb-js/compass-collection": "^4.59.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-editor": "^0.40.2", + "@mongodb-js/compass-collection": "^4.60.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-editor": "^0.41.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", "@mongodb-js/eslint-config-compass": "^1.3.10", @@ -57502,7 +57502,7 @@ "@types/d3-flextree": "^2.1.0", "@types/d3-hierarchy": "^3.1.2", "chai": "^4.2.0", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "d3": "^3.5.17", "d3-flextree": "^2.1.2", "d3-hierarchy": "^3.1.2", @@ -57555,11 +57555,11 @@ "@mongodb-js/compass-export-to-language": { "version": "file:packages/compass-export-to-language", "requires": { - "@mongodb-js/compass-collection": "^4.59.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-editor": "^0.40.2", - "@mongodb-js/compass-maybe-protect-connection-string": "^0.38.2", + "@mongodb-js/compass-collection": "^4.60.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-editor": "^0.41.0", + "@mongodb-js/compass-maybe-protect-connection-string": "^0.39.0", "@mongodb-js/compass-telemetry": "^1.10.0", "@mongodb-js/eslint-config-compass": "^1.3.10", "@mongodb-js/mocha-config-compass": "^1.6.8", @@ -57569,7 +57569,7 @@ "@mongodb-js/tsconfig-compass": "^1.2.8", "bson-transpilers": "^3.2.10", "chai": "^4.3.6", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "depcheck": "^1.4.1", "hadron-app-registry": "^9.4.11", "mocha": "^10.2.0", @@ -57618,7 +57618,7 @@ "@mongodb-js/compass-field-store": { "version": "file:packages/compass-field-store", "requires": { - "@mongodb-js/compass-connections": "^1.60.0", + "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/eslint-config-compass": "^1.3.10", "@mongodb-js/mocha-config-compass": "^1.6.8", @@ -57740,7 +57740,7 @@ "@mongodb-js/compass-find-in-page": { "version": "file:packages/compass-find-in-page", "requires": { - "@mongodb-js/compass-components": "^1.38.1", + "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/eslint-config-compass": "^1.3.10", "@mongodb-js/mocha-config-compass": "^1.6.8", "@mongodb-js/prettier-config-compass": "^1.2.8", @@ -57797,10 +57797,10 @@ "@mongodb-js/compass-generative-ai": { "version": "file:packages/compass-generative-ai", "requires": { - "@mongodb-js/atlas-service": "^0.45.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-intercom": "^0.24.2", + "@mongodb-js/atlas-service": "^0.46.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-intercom": "^0.25.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", "@mongodb-js/compass-utils": "^0.9.2", @@ -57817,7 +57817,7 @@ "@types/sinon-chai": "^3.2.5", "bson": "^6.10.3", "chai": "^4.3.6", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "depcheck": "^1.4.1", "electron-mocha": "^12.2.0", "hadron-app-registry": "^9.4.11", @@ -57952,11 +57952,11 @@ "@mongodb-js/compass-global-writes": { "version": "file:packages/compass-global-writes", "requires": { - "@mongodb-js/atlas-service": "^0.45.0", - "@mongodb-js/compass-collection": "^4.59.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-field-store": "^9.35.0", + "@mongodb-js/atlas-service": "^0.46.0", + "@mongodb-js/compass-collection": "^4.60.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-field-store": "^9.36.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", "@mongodb-js/eslint-config-compass": "^1.3.10", @@ -58068,14 +58068,14 @@ "version": "file:packages/compass-import-export", "requires": { "@electron/remote": "^2.1.2", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-editor": "^0.40.2", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-editor": "^0.41.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", "@mongodb-js/compass-test-server": "^0.3.10", "@mongodb-js/compass-utils": "^0.9.2", - "@mongodb-js/compass-workspaces": "^0.41.0", + "@mongodb-js/compass-workspaces": "^0.42.0", "@mongodb-js/eslint-config-compass": "^1.3.10", "@mongodb-js/mocha-config-compass": "^1.6.8", "@mongodb-js/prettier-config-compass": "^1.2.8", @@ -58094,7 +58094,7 @@ "bson": "^6.10.3", "chai": "^4.3.6", "chai-as-promised": "^7.1.1", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "debug": "^4.3.4", "depcheck": "^1.4.1", "electron": "^36.4.0", @@ -58239,15 +58239,15 @@ "@mongodb-js/compass-indexes": { "version": "file:packages/compass-indexes", "requires": { - "@mongodb-js/atlas-service": "^0.45.0", - "@mongodb-js/compass-app-stores": "^7.46.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-editor": "^0.40.2", - "@mongodb-js/compass-field-store": "^9.35.0", + "@mongodb-js/atlas-service": "^0.46.0", + "@mongodb-js/compass-app-stores": "^7.47.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-editor": "^0.41.0", + "@mongodb-js/compass-field-store": "^9.36.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", - "@mongodb-js/compass-workspaces": "^0.41.0", + "@mongodb-js/compass-workspaces": "^0.42.0", "@mongodb-js/eslint-config-compass": "^1.3.10", "@mongodb-js/mocha-config-compass": "^1.6.8", "@mongodb-js/mongodb-constants": "^0.11.0", @@ -58258,7 +58258,7 @@ "@types/numeral": "^2.0.5", "bson": "^6.10.3", "chai": "^4.2.0", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "depcheck": "^1.4.1", "electron": "^36.4.0", "electron-mocha": "^12.2.0", @@ -58352,7 +58352,7 @@ "@types/mocha": "^9.0.0", "@types/sinon-chai": "^3.2.5", "chai": "^4.3.6", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "depcheck": "^1.4.1", "gen-esm-wrapper": "^1.1.0", "nyc": "^15.1.0", @@ -58501,7 +58501,7 @@ "@types/mocha": "^9.0.0", "@types/sinon-chai": "^3.2.5", "chai": "^4.3.6", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "depcheck": "^1.4.1", "gen-esm-wrapper": "^1.1.0", "mocha": "^10.2.0", @@ -58538,26 +58538,26 @@ "@mongodb-js/compass-query-bar": { "version": "file:packages/compass-query-bar", "requires": { - "@mongodb-js/atlas-service": "^0.45.0", - "@mongodb-js/compass-app-stores": "^7.46.0", - "@mongodb-js/compass-collection": "^4.59.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-editor": "^0.40.2", - "@mongodb-js/compass-field-store": "^9.35.0", - "@mongodb-js/compass-generative-ai": "^0.40.0", + "@mongodb-js/atlas-service": "^0.46.0", + "@mongodb-js/compass-app-stores": "^7.47.0", + "@mongodb-js/compass-collection": "^4.60.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-editor": "^0.41.0", + "@mongodb-js/compass-field-store": "^9.36.0", + "@mongodb-js/compass-generative-ai": "^0.41.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", "@mongodb-js/eslint-config-compass": "^1.3.10", "@mongodb-js/mocha-config-compass": "^1.6.8", "@mongodb-js/mongodb-constants": "^0.11.0", - "@mongodb-js/my-queries-storage": "^0.27.3", + "@mongodb-js/my-queries-storage": "^0.28.0", "@mongodb-js/prettier-config-compass": "^1.2.8", "@mongodb-js/testing-library-compass": "^1.3.2", "@mongodb-js/tsconfig-compass": "^1.2.8", "bson": "^6.10.3", "chai": "^4.2.0", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "depcheck": "^1.4.1", "electron": "^36.4.0", "electron-mocha": "^12.2.0", @@ -58565,7 +58565,7 @@ "lodash": "^4.17.21", "mocha": "^10.2.0", "mongodb": "^6.16.0", - "mongodb-instance-model": "^12.32.2", + "mongodb-instance-model": "^12.33.0", "mongodb-ns": "^2.4.2", "mongodb-query-parser": "^4.3.0", "mongodb-query-util": "^2.4.10", @@ -58703,17 +58703,17 @@ "@mongodb-js/compass-saved-aggregations-queries": { "version": "file:packages/compass-saved-aggregations-queries", "requires": { - "@mongodb-js/compass-app-stores": "^7.46.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", + "@mongodb-js/compass-app-stores": "^7.47.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", - "@mongodb-js/compass-workspaces": "^0.41.0", - "@mongodb-js/connection-form": "^1.52.3", + "@mongodb-js/compass-workspaces": "^0.42.0", + "@mongodb-js/connection-form": "^1.53.0", "@mongodb-js/connection-info": "^0.15.2", "@mongodb-js/eslint-config-compass": "^1.3.10", "@mongodb-js/mocha-config-compass": "^1.6.8", - "@mongodb-js/my-queries-storage": "^0.27.3", + "@mongodb-js/my-queries-storage": "^0.28.0", "@mongodb-js/prettier-config-compass": "^1.2.8", "@mongodb-js/testing-library-compass": "^1.3.2", "@mongodb-js/tsconfig-compass": "^1.2.8", @@ -58725,7 +58725,7 @@ "@types/sinon-chai": "^3.2.5", "bson": "^6.10.3", "chai": "^4.3.4", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "depcheck": "^1.4.1", "electron-mocha": "^12.2.0", "fuse.js": "^6.5.3", @@ -58770,18 +58770,18 @@ "@mongodb-js/compass-schema": { "version": "file:packages/compass-schema", "requires": { - "@mongodb-js/compass-collection": "^4.59.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-editor": "^0.40.2", - "@mongodb-js/compass-field-store": "^9.35.0", + "@mongodb-js/compass-collection": "^4.60.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-editor": "^0.41.0", + "@mongodb-js/compass-field-store": "^9.36.0", "@mongodb-js/compass-logging": "^1.7.2", - "@mongodb-js/compass-query-bar": "^8.61.0", + "@mongodb-js/compass-query-bar": "^8.62.0", "@mongodb-js/compass-telemetry": "^1.10.0", - "@mongodb-js/connection-storage": "^0.35.0", + "@mongodb-js/connection-storage": "^0.36.0", "@mongodb-js/eslint-config-compass": "^1.3.10", "@mongodb-js/mocha-config-compass": "^1.6.8", - "@mongodb-js/my-queries-storage": "^0.27.3", + "@mongodb-js/my-queries-storage": "^0.28.0", "@mongodb-js/prettier-config-compass": "^1.2.8", "@mongodb-js/testing-library-compass": "^1.3.2", "@mongodb-js/tsconfig-compass": "^1.2.8", @@ -58793,7 +58793,7 @@ "@types/react-dom": "^17.0.10", "bson": "^6.10.3", "chai": "^4.3.4", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "d3": "^3.5.17", "depcheck": "^1.4.1", "electron-mocha": "^12.2.0", @@ -58919,17 +58919,17 @@ "@mongodb-js/compass-schema-validation": { "version": "file:packages/compass-schema-validation", "requires": { - "@mongodb-js/compass-app-stores": "^7.46.0", - "@mongodb-js/compass-collection": "^4.59.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-crud": "^13.60.0", - "@mongodb-js/compass-editor": "^0.40.2", - "@mongodb-js/compass-field-store": "^9.35.0", + "@mongodb-js/compass-app-stores": "^7.47.0", + "@mongodb-js/compass-collection": "^4.60.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-crud": "^13.61.0", + "@mongodb-js/compass-editor": "^0.41.0", + "@mongodb-js/compass-field-store": "^9.36.0", "@mongodb-js/compass-logging": "^1.7.2", - "@mongodb-js/compass-schema": "^6.61.0", + "@mongodb-js/compass-schema": "^6.62.0", "@mongodb-js/compass-telemetry": "^1.10.0", - "@mongodb-js/compass-workspaces": "^0.41.0", + "@mongodb-js/compass-workspaces": "^0.42.0", "@mongodb-js/eslint-config-compass": "^1.3.10", "@mongodb-js/mocha-config-compass": "^1.6.8", "@mongodb-js/mongodb-constants": "^0.11.0", @@ -58938,7 +58938,7 @@ "@mongodb-js/tsconfig-compass": "^1.2.8", "bson": "^6.10.3", "chai": "^4.2.0", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "depcheck": "^1.4.1", "electron": "^36.4.0", "electron-mocha": "^12.2.0", @@ -58948,7 +58948,7 @@ "lodash": "^4.17.21", "mocha": "^10.2.0", "mongodb": "^6.16.0", - "mongodb-instance-model": "^12.32.2", + "mongodb-instance-model": "^12.33.0", "mongodb-ns": "^2.4.2", "mongodb-query-parser": "^4.3.0", "nyc": "^15.1.0", @@ -59150,11 +59150,11 @@ "@mongodb-js/compass-serverstats": { "version": "file:packages/compass-serverstats", "requires": { - "@mongodb-js/compass-app-stores": "^7.46.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", + "@mongodb-js/compass-app-stores": "^7.47.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-telemetry": "^1.10.0", - "@mongodb-js/compass-workspaces": "^0.41.0", + "@mongodb-js/compass-workspaces": "^0.42.0", "@mongodb-js/eslint-config-compass": "^1.3.10", "@mongodb-js/mocha-config-compass": "^1.6.8", "@mongodb-js/prettier-config-compass": "^1.2.8", @@ -59196,9 +59196,9 @@ "@mongodb-js/compass-settings": { "version": "file:packages/compass-settings", "requires": { - "@mongodb-js/atlas-service": "^0.45.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-generative-ai": "^0.40.0", + "@mongodb-js/atlas-service": "^0.46.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-generative-ai": "^0.41.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/eslint-config-compass": "^1.3.10", "@mongodb-js/mocha-config-compass": "^1.6.8", @@ -59212,7 +59212,7 @@ "@types/react-dom": "^17.0.10", "@types/sinon-chai": "^3.2.5", "chai": "^4.3.6", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "depcheck": "^1.4.1", "electron-mocha": "^12.2.0", "hadron-app-registry": "^9.4.11", @@ -59256,14 +59256,14 @@ "@mongodb-js/compass-shell": { "version": "file:packages/compass-shell", "requires": { - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-editor": "^0.40.2", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-editor": "^0.41.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", "@mongodb-js/compass-user-data": "^0.7.2", "@mongodb-js/compass-utils": "^0.9.2", - "@mongodb-js/compass-workspaces": "^0.41.0", + "@mongodb-js/compass-workspaces": "^0.42.0", "@mongodb-js/eslint-config-compass": "^1.3.10", "@mongodb-js/mocha-config-compass": "^1.6.8", "@mongodb-js/prettier-config-compass": "^1.2.8", @@ -59274,7 +59274,7 @@ "@mongosh/node-runtime-worker-thread": "^3.3.10", "bson": "^6.10.3", "chai": "^4.2.0", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "depcheck": "^1.4.1", "electron": "^36.4.0", "electron-mocha": "^12.2.0", @@ -59493,15 +59493,15 @@ "@mongodb-js/compass-sidebar": { "version": "file:packages/compass-sidebar", "requires": { - "@mongodb-js/compass-app-stores": "^7.46.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connection-import-export": "^0.56.0", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-connections-navigation": "^1.59.0", + "@mongodb-js/compass-app-stores": "^7.47.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connection-import-export": "^0.57.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-connections-navigation": "^1.60.0", "@mongodb-js/compass-logging": "^1.7.2", - "@mongodb-js/compass-maybe-protect-connection-string": "^0.38.2", + "@mongodb-js/compass-maybe-protect-connection-string": "^0.39.0", "@mongodb-js/compass-telemetry": "^1.10.0", - "@mongodb-js/compass-workspaces": "^0.41.0", + "@mongodb-js/compass-workspaces": "^0.42.0", "@mongodb-js/connection-info": "^0.15.2", "@mongodb-js/eslint-config-compass": "^1.3.10", "@mongodb-js/mocha-config-compass": "^1.6.8", @@ -59515,7 +59515,7 @@ "@types/react-dom": "^17.0.10", "@types/sinon-chai": "^3.2.5", "chai": "^4.3.6", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "depcheck": "^1.4.1", "electron-mocha": "^12.2.0", "hadron-app-registry": "^9.4.11", @@ -59523,7 +59523,7 @@ "mocha": "^10.2.0", "mongodb": "^6.16.0", "mongodb-data-service": "^22.28.2", - "mongodb-instance-model": "^12.32.2", + "mongodb-instance-model": "^12.33.0", "mongodb-ns": "^2.4.2", "nyc": "^15.1.0", "react": "^17.0.2", @@ -59568,7 +59568,7 @@ "@mongodb-js/prettier-config-compass": "^1.2.8", "@mongodb-js/tsconfig-compass": "^1.2.8", "@types/node": "^20", - "compass-e2e-tests": "^1.33.0", + "compass-e2e-tests": "^1.33.1", "debug": "^4.3.4", "depcheck": "^1.4.1", "hadron-build": "^25.8.2", @@ -59883,30 +59883,30 @@ "@mongodb-js/compass-web": { "version": "file:packages/compass-web", "requires": { - "@mongodb-js/atlas-service": "^0.45.0", - "@mongodb-js/compass-aggregations": "^9.62.0", - "@mongodb-js/compass-app-stores": "^7.46.0", - "@mongodb-js/compass-collection": "^4.59.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-crud": "^13.60.0", - "@mongodb-js/compass-data-modeling": "^1.11.0", - "@mongodb-js/compass-databases-collections": "^1.59.0", - "@mongodb-js/compass-explain-plan": "^6.60.0", - "@mongodb-js/compass-export-to-language": "^9.36.0", - "@mongodb-js/compass-field-store": "^9.35.0", - "@mongodb-js/compass-generative-ai": "^0.40.0", - "@mongodb-js/compass-global-writes": "^1.19.0", - "@mongodb-js/compass-indexes": "^5.59.0", + "@mongodb-js/atlas-service": "^0.46.0", + "@mongodb-js/compass-aggregations": "^9.63.0", + "@mongodb-js/compass-app-stores": "^7.47.0", + "@mongodb-js/compass-collection": "^4.60.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-crud": "^13.61.0", + "@mongodb-js/compass-data-modeling": "^1.12.0", + "@mongodb-js/compass-databases-collections": "^1.60.0", + "@mongodb-js/compass-explain-plan": "^6.61.0", + "@mongodb-js/compass-export-to-language": "^9.37.0", + "@mongodb-js/compass-field-store": "^9.36.0", + "@mongodb-js/compass-generative-ai": "^0.41.0", + "@mongodb-js/compass-global-writes": "^1.20.0", + "@mongodb-js/compass-indexes": "^5.60.0", "@mongodb-js/compass-logging": "^1.7.2", - "@mongodb-js/compass-query-bar": "^8.61.0", - "@mongodb-js/compass-schema": "^6.61.0", - "@mongodb-js/compass-schema-validation": "^6.60.0", - "@mongodb-js/compass-sidebar": "^5.60.0", + "@mongodb-js/compass-query-bar": "^8.62.0", + "@mongodb-js/compass-schema": "^6.62.0", + "@mongodb-js/compass-schema-validation": "^6.61.0", + "@mongodb-js/compass-sidebar": "^5.61.0", "@mongodb-js/compass-telemetry": "^1.10.0", - "@mongodb-js/compass-welcome": "^0.58.0", - "@mongodb-js/compass-workspaces": "^0.41.0", - "@mongodb-js/connection-storage": "^0.35.0", + "@mongodb-js/compass-welcome": "^0.59.0", + "@mongodb-js/compass-workspaces": "^0.42.0", + "@mongodb-js/connection-storage": "^0.36.0", "@mongodb-js/devtools-proxy-support": "^0.4.4", "@mongodb-js/eslint-config-compass": "^1.3.10", "@mongodb-js/mocha-config-compass": "^1.6.8", @@ -59925,7 +59925,7 @@ "bson": "^6.2.0", "buffer": "^6.0.3", "chai": "^4.3.6", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "crypto-browserify": "^3.12.0", "debug": "^4.3.4", "depcheck": "^1.4.1", @@ -60109,11 +60109,11 @@ "@mongodb-js/compass-welcome": { "version": "file:packages/compass-welcome", "requires": { - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", - "@mongodb-js/compass-workspaces": "^0.41.0", + "@mongodb-js/compass-workspaces": "^0.42.0", "@mongodb-js/eslint-config-compass": "^1.3.10", "@mongodb-js/mocha-config-compass": "^1.6.8", "@mongodb-js/prettier-config-compass": "^1.2.8", @@ -60125,7 +60125,7 @@ "@types/react": "^17.0.5", "@types/sinon-chai": "^3.2.5", "chai": "^4.3.6", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "depcheck": "^1.4.1", "electron-mocha": "^12.2.0", "hadron-app-registry": "^9.4.11", @@ -60166,9 +60166,9 @@ "@mongodb-js/compass-workspaces": { "version": "file:packages/compass-workspaces", "requires": { - "@mongodb-js/compass-app-stores": "^7.46.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", + "@mongodb-js/compass-app-stores": "^7.47.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/eslint-config-compass": "^1.3.10", "@mongodb-js/mocha-config-compass": "^1.6.8", @@ -60183,7 +60183,7 @@ "@types/sinon-chai": "^3.2.5", "bson": "^6.10.3", "chai": "^4.3.6", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "depcheck": "^1.4.1", "electron-mocha": "^12.2.0", "hadron-app-registry": "^9.4.11", @@ -60306,8 +60306,8 @@ "@mongodb-js/connection-form": { "version": "file:packages/connection-form", "requires": { - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-editor": "^0.40.2", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-editor": "^0.41.0", "@mongodb-js/connection-info": "^0.15.2", "@mongodb-js/eslint-config-compass": "^1.3.10", "@mongodb-js/mocha-config-compass": "^1.6.8", @@ -60521,7 +60521,7 @@ "@types/sinon-chai": "^3.2.5", "bson": "^6.10.3", "chai": "^4.3.6", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "depcheck": "^1.4.1", "electron": "^36.4.0", "hadron-app-registry": "^9.4.11", @@ -60561,10 +60561,10 @@ "@mongodb-js/databases-collections-list": { "version": "file:packages/databases-collections-list", "requires": { - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-telemetry": "^1.10.0", - "@mongodb-js/compass-workspaces": "^0.41.0", + "@mongodb-js/compass-workspaces": "^0.42.0", "@mongodb-js/connection-info": "^0.15.2", "@mongodb-js/eslint-config-compass": "^1.3.10", "@mongodb-js/mocha-config-compass": "^1.6.8", @@ -60577,7 +60577,7 @@ "@types/react": "^17.0.5", "@types/sinon-chai": "^3.2.5", "chai": "^4.3.4", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "depcheck": "^1.4.1", "mocha": "^10.2.0", "mongodb-collection-model": "^5.29.2", @@ -61193,7 +61193,7 @@ "@mongodb-js/my-queries-storage": { "version": "file:packages/my-queries-storage", "requires": { - "@mongodb-js/compass-editor": "^0.40.2", + "@mongodb-js/compass-editor": "^0.41.0", "@mongodb-js/compass-user-data": "^0.7.2", "@mongodb-js/eslint-config-compass": "^1.3.10", "@mongodb-js/mocha-config-compass": "^1.6.8", @@ -68801,7 +68801,7 @@ "chai": "^4.3.4", "chai-as-promised": "^7.1.1", "clipboardy": "^2.3.0", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "cross-spawn": "^7.0.5", "debug": "^4.3.4", "depcheck": "^1.4.1", @@ -69104,7 +69104,7 @@ "compass-preferences-model": { "version": "file:packages/compass-preferences-model", "requires": { - "@mongodb-js/compass-components": "^1.38.1", + "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-user-data": "^0.7.2", "@mongodb-js/devtools-proxy-support": "^0.4.4", @@ -79535,47 +79535,47 @@ "requires": { "@electron/rebuild": "^4.0.1", "@electron/remote": "^2.1.2", - "@mongodb-js/atlas-service": "^0.45.0", - "@mongodb-js/compass-aggregations": "^9.62.0", - "@mongodb-js/compass-app-stores": "^7.46.0", - "@mongodb-js/compass-collection": "^4.59.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connection-import-export": "^0.56.0", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-crud": "^13.60.0", - "@mongodb-js/compass-data-modeling": "^1.11.0", - "@mongodb-js/compass-databases-collections": "^1.59.0", - "@mongodb-js/compass-explain-plan": "^6.60.0", - "@mongodb-js/compass-export-to-language": "^9.36.0", - "@mongodb-js/compass-field-store": "^9.35.0", - "@mongodb-js/compass-find-in-page": "^4.39.2", - "@mongodb-js/compass-generative-ai": "^0.40.0", - "@mongodb-js/compass-global-writes": "^1.19.0", - "@mongodb-js/compass-import-export": "^7.59.0", - "@mongodb-js/compass-indexes": "^5.59.0", - "@mongodb-js/compass-intercom": "^0.24.2", + "@mongodb-js/atlas-service": "^0.46.0", + "@mongodb-js/compass-aggregations": "^9.63.0", + "@mongodb-js/compass-app-stores": "^7.47.0", + "@mongodb-js/compass-collection": "^4.60.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connection-import-export": "^0.57.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-crud": "^13.61.0", + "@mongodb-js/compass-data-modeling": "^1.12.0", + "@mongodb-js/compass-databases-collections": "^1.60.0", + "@mongodb-js/compass-explain-plan": "^6.61.0", + "@mongodb-js/compass-export-to-language": "^9.37.0", + "@mongodb-js/compass-field-store": "^9.36.0", + "@mongodb-js/compass-find-in-page": "^4.40.0", + "@mongodb-js/compass-generative-ai": "^0.41.0", + "@mongodb-js/compass-global-writes": "^1.20.0", + "@mongodb-js/compass-import-export": "^7.60.0", + "@mongodb-js/compass-indexes": "^5.60.0", + "@mongodb-js/compass-intercom": "^0.25.0", "@mongodb-js/compass-logging": "^1.7.2", - "@mongodb-js/compass-query-bar": "^8.61.0", - "@mongodb-js/compass-saved-aggregations-queries": "^1.60.0", - "@mongodb-js/compass-schema": "^6.61.0", - "@mongodb-js/compass-schema-validation": "^6.60.0", - "@mongodb-js/compass-serverstats": "^16.59.0", - "@mongodb-js/compass-settings": "^0.58.0", - "@mongodb-js/compass-shell": "^3.59.0", - "@mongodb-js/compass-sidebar": "^5.60.0", + "@mongodb-js/compass-query-bar": "^8.62.0", + "@mongodb-js/compass-saved-aggregations-queries": "^1.61.0", + "@mongodb-js/compass-schema": "^6.62.0", + "@mongodb-js/compass-schema-validation": "^6.61.0", + "@mongodb-js/compass-serverstats": "^16.60.0", + "@mongodb-js/compass-settings": "^0.59.0", + "@mongodb-js/compass-shell": "^3.60.0", + "@mongodb-js/compass-sidebar": "^5.61.0", "@mongodb-js/compass-telemetry": "^1.10.0", "@mongodb-js/compass-utils": "^0.9.2", - "@mongodb-js/compass-welcome": "^0.58.0", - "@mongodb-js/compass-workspaces": "^0.41.0", + "@mongodb-js/compass-welcome": "^0.59.0", + "@mongodb-js/compass-workspaces": "^0.42.0", "@mongodb-js/connection-info": "^0.15.2", - "@mongodb-js/connection-storage": "^0.35.0", + "@mongodb-js/connection-storage": "^0.36.0", "@mongodb-js/device-id": "^0.2.0", "@mongodb-js/devtools-proxy-support": "^0.4.4", "@mongodb-js/eslint-config-compass": "^1.3.10", "@mongodb-js/get-os-info": "^0.4.0", "@mongodb-js/mocha-config-compass": "^1.6.8", "@mongodb-js/mongodb-downloader": "^0.3.7", - "@mongodb-js/my-queries-storage": "^0.27.3", + "@mongodb-js/my-queries-storage": "^0.28.0", "@mongodb-js/prettier-config-compass": "^1.2.8", "@mongodb-js/sbom-tools": "^0.7.2", "@mongodb-js/signing-utils": "^0.3.8", @@ -79590,7 +79590,7 @@ "chalk": "^4.1.2", "clean-stack": "^2.0.0", "clipboard": "^2.0.6", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "cross-spawn": "^7.0.5", "debug": "^4.3.4", "depcheck": "^1.4.1", @@ -79957,7 +79957,7 @@ "@mongodb-js/prettier-config-compass": "^1.2.8", "ampersand-model": "^8.0.1", "chai": "^4.3.4", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "depcheck": "^1.4.1", "mocha": "^10.2.0", "mongodb-collection-model": "^5.29.2", diff --git a/packages/atlas-service/package.json b/packages/atlas-service/package.json index 415887b1cc9..7e2035391ed 100644 --- a/packages/atlas-service/package.json +++ b/packages/atlas-service/package.json @@ -13,7 +13,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "0.45.0", + "version": "0.46.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -71,7 +71,7 @@ "typescript": "^5.0.4" }, "dependencies": { - "@mongodb-js/compass-components": "^1.38.1", + "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", "@mongodb-js/compass-user-data": "^0.7.2", @@ -81,7 +81,7 @@ "@mongodb-js/devtools-proxy-support": "^0.4.4", "@mongodb-js/oidc-plugin": "^1.1.7", "hadron-app-registry": "^9.4.11", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "electron": "^36.4.0", "hadron-ipc": "^3.5.2", "lodash": "^4.17.21", diff --git a/packages/compass-aggregations/package.json b/packages/compass-aggregations/package.json index 9d567feb786..f95c257e64e 100644 --- a/packages/compass-aggregations/package.json +++ b/packages/compass-aggregations/package.json @@ -2,7 +2,7 @@ "name": "@mongodb-js/compass-aggregations", "description": "Compass Aggregation Pipeline Builder", "private": true, - "version": "9.62.0", + "version": "9.63.0", "main": "dist/index.js", "compass:main": "src/index.ts", "types": "dist/index.d.ts", @@ -57,25 +57,25 @@ "@dnd-kit/core": "^6.0.7", "@dnd-kit/sortable": "^7.0.2", "@dnd-kit/utilities": "^3.2.1", - "@mongodb-js/atlas-service": "^0.45.0", - "@mongodb-js/compass-app-stores": "^7.46.0", - "@mongodb-js/compass-collection": "^4.59.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-crud": "^13.60.0", - "@mongodb-js/compass-editor": "^0.40.2", - "@mongodb-js/compass-field-store": "^9.35.0", - "@mongodb-js/compass-generative-ai": "^0.40.0", + "@mongodb-js/atlas-service": "^0.46.0", + "@mongodb-js/compass-app-stores": "^7.47.0", + "@mongodb-js/compass-collection": "^4.60.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-crud": "^13.61.0", + "@mongodb-js/compass-editor": "^0.41.0", + "@mongodb-js/compass-field-store": "^9.36.0", + "@mongodb-js/compass-generative-ai": "^0.41.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", "@mongodb-js/compass-utils": "^0.9.2", - "@mongodb-js/compass-workspaces": "^0.41.0", + "@mongodb-js/compass-workspaces": "^0.42.0", "@mongodb-js/explain-plan-helper": "^1.4.10", "@mongodb-js/mongodb-constants": "^0.11.0", - "@mongodb-js/my-queries-storage": "^0.27.3", + "@mongodb-js/my-queries-storage": "^0.28.0", "@mongodb-js/shell-bson-parser": "^1.2.0", "bson": "^6.10.3", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "hadron-app-registry": "^9.4.11", "hadron-document": "^8.8.12", "hadron-type-checker": "^7.4.10", @@ -84,7 +84,7 @@ "mongodb-collection-model": "^5.29.2", "mongodb-data-service": "^22.28.2", "mongodb-database-model": "^2.29.2", - "mongodb-instance-model": "^12.32.2", + "mongodb-instance-model": "^12.33.0", "mongodb-ns": "^2.4.2", "mongodb-query-parser": "^4.3.0", "mongodb-schema": "^12.6.2", diff --git a/packages/compass-app-stores/package.json b/packages/compass-app-stores/package.json index f10f16e798d..667312b2c3c 100644 --- a/packages/compass-app-stores/package.json +++ b/packages/compass-app-stores/package.json @@ -11,7 +11,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "7.46.0", + "version": "7.47.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -71,15 +71,15 @@ "xvfb-maybe": "^0.2.1" }, "dependencies": { - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/connection-info": "^0.15.2", "hadron-app-registry": "^9.4.11", "mongodb-collection-model": "^5.29.2", "mongodb-database-model": "^2.29.2", - "mongodb-instance-model": "^12.32.2", - "compass-preferences-model": "^2.40.2", + "mongodb-instance-model": "^12.33.0", + "compass-preferences-model": "^2.41.0", "mongodb-ns": "^2.4.2", "react": "^17.0.2" }, diff --git a/packages/compass-collection/package.json b/packages/compass-collection/package.json index b8f884db658..3a2168cb7b1 100644 --- a/packages/compass-collection/package.json +++ b/packages/compass-collection/package.json @@ -11,7 +11,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "4.59.0", + "version": "4.60.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -48,15 +48,15 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "dependencies": { - "@mongodb-js/compass-app-stores": "^7.46.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", + "@mongodb-js/compass-app-stores": "^7.47.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", - "@mongodb-js/compass-workspaces": "^0.41.0", + "@mongodb-js/compass-workspaces": "^0.42.0", "@mongodb-js/connection-info": "^0.15.2", "@mongodb-js/mongodb-constants": "^0.11.0", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "hadron-app-registry": "^9.4.11", "mongodb-collection-model": "^5.29.2", "mongodb-ns": "^2.4.2", diff --git a/packages/compass-components/package.json b/packages/compass-components/package.json index 0e78208a495..14af3a7b98b 100644 --- a/packages/compass-components/package.json +++ b/packages/compass-components/package.json @@ -1,6 +1,6 @@ { "name": "@mongodb-js/compass-components", - "version": "1.38.1", + "version": "1.39.0", "description": "React Components used in Compass", "license": "SSPL", "main": "lib/index.js", diff --git a/packages/compass-connection-import-export/package.json b/packages/compass-connection-import-export/package.json index 8c80f706cde..329fa013bc6 100644 --- a/packages/compass-connection-import-export/package.json +++ b/packages/compass-connection-import-export/package.json @@ -14,7 +14,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "0.56.0", + "version": "0.57.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -51,10 +51,10 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "dependencies": { - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/connection-storage": "^0.35.0", - "compass-preferences-model": "^2.40.2", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/connection-storage": "^0.36.0", + "compass-preferences-model": "^2.41.0", "hadron-ipc": "^3.5.2", "react": "^17.0.2" }, diff --git a/packages/compass-connections-navigation/package.json b/packages/compass-connections-navigation/package.json index a9541e008d3..eb7929a7f6e 100644 --- a/packages/compass-connections-navigation/package.json +++ b/packages/compass-connections-navigation/package.json @@ -13,7 +13,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "1.59.0", + "version": "1.60.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -49,12 +49,12 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "dependencies": { - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-components": "^1.38.1", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/connection-info": "^0.15.2", - "@mongodb-js/connection-form": "^1.52.3", - "@mongodb-js/compass-workspaces": "^0.41.0", - "compass-preferences-model": "^2.40.2", + "@mongodb-js/connection-form": "^1.53.0", + "@mongodb-js/compass-workspaces": "^0.42.0", + "compass-preferences-model": "^2.41.0", "mongodb-build-info": "^1.7.2", "react": "^17.0.2", "react-virtualized-auto-sizer": "^1.0.6", diff --git a/packages/compass-connections/package.json b/packages/compass-connections/package.json index 5fb92e78518..f1d1c03a75b 100644 --- a/packages/compass-connections/package.json +++ b/packages/compass-connections/package.json @@ -13,7 +13,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "1.60.0", + "version": "1.61.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -51,15 +51,15 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "dependencies": { - "@mongodb-js/compass-components": "^1.38.1", + "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", "@mongodb-js/compass-utils": "^0.9.2", - "@mongodb-js/connection-form": "^1.52.3", + "@mongodb-js/connection-form": "^1.53.0", "@mongodb-js/connection-info": "^0.15.2", - "@mongodb-js/connection-storage": "^0.35.0", + "@mongodb-js/connection-storage": "^0.36.0", "bson": "^6.10.3", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "hadron-app-registry": "^9.4.11", "lodash": "^4.17.21", "mongodb": "^6.16.0", diff --git a/packages/compass-crud/package.json b/packages/compass-crud/package.json index 68f275384b2..de7ef6ed6d9 100644 --- a/packages/compass-crud/package.json +++ b/packages/compass-crud/package.json @@ -6,7 +6,7 @@ "email": "compass@mongodb.com" }, "private": true, - "version": "13.60.0", + "version": "13.61.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -63,31 +63,31 @@ "electron-mocha": "^12.2.0", "enzyme": "^3.11.0", "mocha": "^10.2.0", - "mongodb-instance-model": "^12.32.2", + "mongodb-instance-model": "^12.33.0", "nyc": "^15.1.0", "react-dom": "^17.0.2", "sinon": "^8.1.1", "typescript": "^5.0.4" }, "dependencies": { - "@mongodb-js/compass-app-stores": "^7.46.0", - "@mongodb-js/compass-collection": "^4.59.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-editor": "^0.40.2", - "@mongodb-js/compass-field-store": "^9.35.0", + "@mongodb-js/compass-app-stores": "^7.47.0", + "@mongodb-js/compass-collection": "^4.60.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-editor": "^0.41.0", + "@mongodb-js/compass-field-store": "^9.36.0", "@mongodb-js/compass-logging": "^1.7.2", - "@mongodb-js/compass-query-bar": "^8.61.0", + "@mongodb-js/compass-query-bar": "^8.62.0", "@mongodb-js/compass-telemetry": "^1.10.0", - "@mongodb-js/compass-workspaces": "^0.41.0", + "@mongodb-js/compass-workspaces": "^0.42.0", "@mongodb-js/explain-plan-helper": "^1.4.10", - "@mongodb-js/my-queries-storage": "^0.27.3", + "@mongodb-js/my-queries-storage": "^0.28.0", "@mongodb-js/reflux-state-mixin": "^1.2.10", "@mongodb-js/shell-bson-parser": "^1.2.0", "ag-grid-community": "^20.2.0", "ag-grid-react": "^20.2.0", "bson": "^6.10.3", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "hadron-app-registry": "^9.4.11", "hadron-document": "^8.8.12", "hadron-type-checker": "^7.4.10", diff --git a/packages/compass-data-modeling/package.json b/packages/compass-data-modeling/package.json index 8a265d09cb5..a06a218e95f 100644 --- a/packages/compass-data-modeling/package.json +++ b/packages/compass-data-modeling/package.json @@ -11,7 +11,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "1.11.0", + "version": "1.12.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -54,17 +54,17 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "dependencies": { - "@mongodb-js/compass-app-stores": "^7.46.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-editor": "^0.40.2", + "@mongodb-js/compass-app-stores": "^7.47.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-editor": "^0.41.0", "@mongodb-js/diagramming": "^1.0.2", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", "@mongodb-js/compass-user-data": "^0.7.2", - "@mongodb-js/compass-workspaces": "^0.41.0", + "@mongodb-js/compass-workspaces": "^0.42.0", "bson": "^6.10.3", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "hadron-app-registry": "^9.4.11", "lodash": "^4.17.21", "mongodb": "^6.14.1", diff --git a/packages/compass-e2e-tests/package.json b/packages/compass-e2e-tests/package.json index 6b091c42a72..50452fbd24d 100644 --- a/packages/compass-e2e-tests/package.json +++ b/packages/compass-e2e-tests/package.json @@ -1,6 +1,6 @@ { "name": "compass-e2e-tests", - "version": "1.33.0", + "version": "1.33.1", "private": true, "description": "E2E test suite for Compass app that follows smoke tests / feature testing matrix", "scripts": { @@ -46,7 +46,7 @@ "chai": "^4.3.4", "chai-as-promised": "^7.1.1", "clipboardy": "^2.3.0", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "cross-spawn": "^7.0.5", "debug": "^4.3.4", "depcheck": "^1.4.1", diff --git a/packages/compass-editor/package.json b/packages/compass-editor/package.json index 3014f9c0b0a..6775016c10a 100644 --- a/packages/compass-editor/package.json +++ b/packages/compass-editor/package.json @@ -13,7 +13,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "0.40.2", + "version": "0.41.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -71,7 +71,7 @@ "@codemirror/state": "^6.1.4", "@codemirror/view": "^6.7.1", "@lezer/highlight": "^1.2.0", - "@mongodb-js/compass-components": "^1.38.1", + "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/mongodb-constants": "^0.11.0", "mongodb-query-parser": "^4.3.0", "polished": "^4.2.2", diff --git a/packages/compass-explain-plan/package.json b/packages/compass-explain-plan/package.json index fcd422e9176..2033bcc8d8b 100644 --- a/packages/compass-explain-plan/package.json +++ b/packages/compass-explain-plan/package.json @@ -6,7 +6,7 @@ "email": "compass@mongodb.com" }, "private": true, - "version": "6.60.0", + "version": "6.61.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -68,14 +68,14 @@ "xvfb-maybe": "^0.2.1" }, "dependencies": { - "@mongodb-js/compass-collection": "^4.59.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-editor": "^0.40.2", + "@mongodb-js/compass-collection": "^4.60.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-editor": "^0.41.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", "@mongodb-js/explain-plan-helper": "^1.4.10", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "d3": "^3.5.17", "d3-flextree": "^2.1.2", "d3-hierarchy": "^3.1.2", diff --git a/packages/compass-export-to-language/package.json b/packages/compass-export-to-language/package.json index 4e43fea3cdd..74d3c757653 100644 --- a/packages/compass-export-to-language/package.json +++ b/packages/compass-export-to-language/package.json @@ -11,7 +11,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "9.36.0", + "version": "9.37.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -48,15 +48,15 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "dependencies": { - "@mongodb-js/compass-collection": "^4.59.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-editor": "^0.40.2", - "@mongodb-js/compass-maybe-protect-connection-string": "^0.38.2", + "@mongodb-js/compass-collection": "^4.60.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-editor": "^0.41.0", + "@mongodb-js/compass-maybe-protect-connection-string": "^0.39.0", "@mongodb-js/compass-telemetry": "^1.10.0", "@mongodb-js/shell-bson-parser": "^1.2.0", "bson-transpilers": "^3.2.10", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "hadron-app-registry": "^9.4.11", "mongodb-ns": "^2.4.2", "react": "^17.0.2", diff --git a/packages/compass-field-store/package.json b/packages/compass-field-store/package.json index d0114bf33d2..2cad7810e9c 100644 --- a/packages/compass-field-store/package.json +++ b/packages/compass-field-store/package.json @@ -11,7 +11,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "9.35.0", + "version": "9.36.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -67,7 +67,7 @@ "xvfb-maybe": "^0.2.1" }, "dependencies": { - "@mongodb-js/compass-connections": "^1.60.0", + "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-logging": "^1.7.2", "hadron-app-registry": "^9.4.11", "lodash": "^4.17.21", diff --git a/packages/compass-find-in-page/package.json b/packages/compass-find-in-page/package.json index 2fca8bd2f62..59eb6b9ab0b 100644 --- a/packages/compass-find-in-page/package.json +++ b/packages/compass-find-in-page/package.json @@ -6,7 +6,7 @@ "email": "compass@mongodb.com" }, "private": true, - "version": "4.39.2", + "version": "4.40.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -71,7 +71,7 @@ "xvfb-maybe": "^0.2.1" }, "dependencies": { - "@mongodb-js/compass-components": "^1.38.1", + "@mongodb-js/compass-components": "^1.39.0", "hadron-app-registry": "^9.4.11", "hadron-ipc": "^3.5.2", "react": "^17.0.2", diff --git a/packages/compass-generative-ai/package.json b/packages/compass-generative-ai/package.json index 00f4fcf77a3..f398d1bae23 100644 --- a/packages/compass-generative-ai/package.json +++ b/packages/compass-generative-ai/package.json @@ -11,7 +11,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "0.40.0", + "version": "0.41.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -52,15 +52,15 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "dependencies": { - "@mongodb-js/atlas-service": "^0.45.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-intercom": "^0.24.2", + "@mongodb-js/atlas-service": "^0.46.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-intercom": "^0.25.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", "@mongodb-js/compass-utils": "^0.9.2", "bson": "^6.10.3", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "hadron-app-registry": "^9.4.11", "mongodb": "^6.16.0", "mongodb-schema": "^12.6.2", diff --git a/packages/compass-global-writes/package.json b/packages/compass-global-writes/package.json index 346a2825c83..86514733cbc 100644 --- a/packages/compass-global-writes/package.json +++ b/packages/compass-global-writes/package.json @@ -11,7 +11,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "1.19.0", + "version": "1.20.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -49,15 +49,15 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "dependencies": { - "@mongodb-js/atlas-service": "^0.45.0", - "@mongodb-js/compass-collection": "^4.59.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", + "@mongodb-js/atlas-service": "^0.46.0", + "@mongodb-js/compass-collection": "^4.60.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", "hadron-app-registry": "^9.4.11", "lodash": "^4.17.21", - "@mongodb-js/compass-field-store": "^9.35.0", + "@mongodb-js/compass-field-store": "^9.36.0", "mongodb-ns": "^2.4.2", "react": "^17.0.2", "react-redux": "^8.1.3", diff --git a/packages/compass-import-export/package.json b/packages/compass-import-export/package.json index 58c6d475189..535b5e318da 100644 --- a/packages/compass-import-export/package.json +++ b/packages/compass-import-export/package.json @@ -11,7 +11,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "7.59.0", + "version": "7.60.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -49,15 +49,15 @@ }, "dependencies": { "@electron/remote": "^2.1.2", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-editor": "^0.40.2", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-editor": "^0.41.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", "@mongodb-js/compass-utils": "^0.9.2", - "@mongodb-js/compass-workspaces": "^0.41.0", + "@mongodb-js/compass-workspaces": "^0.42.0", "bson": "^6.10.3", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "debug": "^4.3.4", "electron": "^36.4.0", "hadron-app-registry": "^9.4.11", diff --git a/packages/compass-indexes/package.json b/packages/compass-indexes/package.json index 79b572dfce1..512e7a0d9e6 100644 --- a/packages/compass-indexes/package.json +++ b/packages/compass-indexes/package.json @@ -6,7 +6,7 @@ "email": "compass@mongodb.com" }, "private": true, - "version": "5.59.0", + "version": "5.60.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -66,19 +66,19 @@ "xvfb-maybe": "^0.2.1" }, "dependencies": { - "@mongodb-js/atlas-service": "^0.45.0", - "@mongodb-js/compass-app-stores": "^7.46.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-editor": "^0.40.2", - "@mongodb-js/compass-field-store": "^9.35.0", + "@mongodb-js/atlas-service": "^0.46.0", + "@mongodb-js/compass-app-stores": "^7.47.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-editor": "^0.41.0", + "@mongodb-js/compass-field-store": "^9.36.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", - "@mongodb-js/compass-workspaces": "^0.41.0", + "@mongodb-js/compass-workspaces": "^0.42.0", "@mongodb-js/mongodb-constants": "^0.11.0", "@mongodb-js/shell-bson-parser": "^1.2.0", "bson": "^6.10.3", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "hadron-app-registry": "^9.4.11", "lodash": "^4.17.21", "mongodb": "^6.16.0", diff --git a/packages/compass-intercom/package.json b/packages/compass-intercom/package.json index 5f50d5631bf..3e582a546c9 100644 --- a/packages/compass-intercom/package.json +++ b/packages/compass-intercom/package.json @@ -13,7 +13,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "0.24.2", + "version": "0.25.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -65,7 +65,7 @@ "typescript": "^5.0.4" }, "dependencies": { - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "@mongodb-js/compass-logging": "^1.7.2" } } diff --git a/packages/compass-maybe-protect-connection-string/package.json b/packages/compass-maybe-protect-connection-string/package.json index ca250d0987b..358179f2dc3 100644 --- a/packages/compass-maybe-protect-connection-string/package.json +++ b/packages/compass-maybe-protect-connection-string/package.json @@ -13,7 +13,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "0.38.2", + "version": "0.39.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -50,7 +50,7 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "dependencies": { - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "mongodb-connection-string-url": "^3.0.1" }, "devDependencies": { diff --git a/packages/compass-preferences-model/package.json b/packages/compass-preferences-model/package.json index 0d7977d8578..8ce2a3b07ee 100644 --- a/packages/compass-preferences-model/package.json +++ b/packages/compass-preferences-model/package.json @@ -2,7 +2,7 @@ "name": "compass-preferences-model", "description": "Compass preferences model", "author": "Lucas Hrabovsky ", - "version": "2.40.2", + "version": "2.41.0", "bugs": { "url": "https://jira.mongodb.org/projects/COMPASS/issues", "email": "compass@mongodb.com" @@ -52,7 +52,7 @@ "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-user-data": "^0.7.2", "@mongodb-js/devtools-proxy-support": "^0.4.4", - "@mongodb-js/compass-components": "^1.38.1", + "@mongodb-js/compass-components": "^1.39.0", "bson": "^6.10.3", "hadron-app-registry": "^9.4.11", "hadron-ipc": "^3.5.2", diff --git a/packages/compass-query-bar/package.json b/packages/compass-query-bar/package.json index e224004f4ef..e410f4978ae 100644 --- a/packages/compass-query-bar/package.json +++ b/packages/compass-query-bar/package.json @@ -6,7 +6,7 @@ "email": "compass@mongodb.com" }, "private": true, - "version": "8.61.0", + "version": "8.62.0", "homepage": "https://github.com/mongodb-js/compass", "license": "SSPL", "bugs": { @@ -65,24 +65,24 @@ "xvfb-maybe": "^0.2.1" }, "dependencies": { - "@mongodb-js/atlas-service": "^0.45.0", - "@mongodb-js/compass-app-stores": "^7.46.0", - "@mongodb-js/compass-collection": "^4.59.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-editor": "^0.40.2", - "@mongodb-js/compass-field-store": "^9.35.0", - "@mongodb-js/compass-generative-ai": "^0.40.0", + "@mongodb-js/atlas-service": "^0.46.0", + "@mongodb-js/compass-app-stores": "^7.47.0", + "@mongodb-js/compass-collection": "^4.60.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-editor": "^0.41.0", + "@mongodb-js/compass-field-store": "^9.36.0", + "@mongodb-js/compass-generative-ai": "^0.41.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", "@mongodb-js/mongodb-constants": "^0.11.0", - "@mongodb-js/my-queries-storage": "^0.27.3", + "@mongodb-js/my-queries-storage": "^0.28.0", "bson": "^6.10.3", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "hadron-app-registry": "^9.4.11", "lodash": "^4.17.21", "mongodb": "^6.16.0", - "mongodb-instance-model": "^12.32.2", + "mongodb-instance-model": "^12.33.0", "mongodb-ns": "^2.4.2", "mongodb-query-parser": "^4.3.0", "mongodb-query-util": "^2.4.10", diff --git a/packages/compass-saved-aggregations-queries/package.json b/packages/compass-saved-aggregations-queries/package.json index 32d6a1d020d..a49f17eb97a 100644 --- a/packages/compass-saved-aggregations-queries/package.json +++ b/packages/compass-saved-aggregations-queries/package.json @@ -11,7 +11,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "1.60.0", + "version": "1.61.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -48,17 +48,17 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "dependencies": { - "@mongodb-js/compass-app-stores": "^7.46.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", + "@mongodb-js/compass-app-stores": "^7.47.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", - "@mongodb-js/compass-workspaces": "^0.41.0", - "@mongodb-js/connection-form": "^1.52.3", + "@mongodb-js/compass-workspaces": "^0.42.0", + "@mongodb-js/connection-form": "^1.53.0", "@mongodb-js/connection-info": "^0.15.2", - "@mongodb-js/my-queries-storage": "^0.27.3", + "@mongodb-js/my-queries-storage": "^0.28.0", "bson": "^6.10.3", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "fuse.js": "^6.5.3", "hadron-app-registry": "^9.4.11", "mongodb-ns": "^2.4.2", diff --git a/packages/compass-schema-validation/package.json b/packages/compass-schema-validation/package.json index 1ef00605ab1..267af704f0d 100644 --- a/packages/compass-schema-validation/package.json +++ b/packages/compass-schema-validation/package.json @@ -6,7 +6,7 @@ "email": "compass@mongodb.com" }, "private": true, - "version": "6.60.0", + "version": "6.61.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -59,27 +59,27 @@ "electron-mocha": "^12.2.0", "hadron-ipc": "^3.5.2", "mocha": "^10.2.0", - "mongodb-instance-model": "^12.32.2", + "mongodb-instance-model": "^12.33.0", "nyc": "^15.1.0", "react-dom": "^17.0.2", "sinon": "^8.1.1", "typescript": "^5.0.4" }, "dependencies": { - "@mongodb-js/compass-app-stores": "^7.46.0", - "@mongodb-js/compass-collection": "^4.59.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-crud": "^13.60.0", - "@mongodb-js/compass-editor": "^0.40.2", - "@mongodb-js/compass-field-store": "^9.35.0", + "@mongodb-js/compass-app-stores": "^7.47.0", + "@mongodb-js/compass-collection": "^4.60.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-crud": "^13.61.0", + "@mongodb-js/compass-editor": "^0.41.0", + "@mongodb-js/compass-field-store": "^9.36.0", "@mongodb-js/compass-logging": "^1.7.2", - "@mongodb-js/compass-schema": "^6.61.0", + "@mongodb-js/compass-schema": "^6.62.0", "@mongodb-js/compass-telemetry": "^1.10.0", - "@mongodb-js/compass-workspaces": "^0.41.0", + "@mongodb-js/compass-workspaces": "^0.42.0", "@mongodb-js/mongodb-constants": "^0.11.0", "bson": "^6.10.3", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "hadron-app-registry": "^9.4.11", "javascript-stringify": "^2.0.1", "lodash": "^4.17.21", diff --git a/packages/compass-schema/package.json b/packages/compass-schema/package.json index add2c9f84c5..66bbe534cbf 100644 --- a/packages/compass-schema/package.json +++ b/packages/compass-schema/package.json @@ -6,7 +6,7 @@ "email": "compass@mongodb.com" }, "private": true, - "version": "6.61.0", + "version": "6.62.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -50,7 +50,7 @@ "devDependencies": { "@mongodb-js/eslint-config-compass": "^1.3.10", "@mongodb-js/mocha-config-compass": "^1.6.8", - "@mongodb-js/my-queries-storage": "^0.27.3", + "@mongodb-js/my-queries-storage": "^0.28.0", "@mongodb-js/prettier-config-compass": "^1.2.8", "@mongodb-js/testing-library-compass": "^1.3.2", "@mongodb-js/tsconfig-compass": "^1.2.8", @@ -71,17 +71,17 @@ "xvfb-maybe": "^0.2.1" }, "dependencies": { - "@mongodb-js/compass-collection": "^4.59.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-editor": "^0.40.2", - "@mongodb-js/compass-field-store": "^9.35.0", + "@mongodb-js/compass-collection": "^4.60.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-editor": "^0.41.0", + "@mongodb-js/compass-field-store": "^9.36.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", - "@mongodb-js/compass-query-bar": "^8.61.0", - "@mongodb-js/connection-storage": "^0.35.0", + "@mongodb-js/compass-query-bar": "^8.62.0", + "@mongodb-js/connection-storage": "^0.36.0", "bson": "^6.10.3", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "d3": "^3.5.17", "hadron-app-registry": "^9.4.11", "hadron-document": "^8.8.12", diff --git a/packages/compass-serverstats/package.json b/packages/compass-serverstats/package.json index 583af061b3c..e1ac4f3aeea 100644 --- a/packages/compass-serverstats/package.json +++ b/packages/compass-serverstats/package.json @@ -2,7 +2,7 @@ "name": "@mongodb-js/compass-serverstats", "description": "Compass Real Time", "private": true, - "version": "16.59.0", + "version": "16.60.0", "main": "dist/index.js", "compass:main": "src/index.ts", "exports": { @@ -30,11 +30,11 @@ }, "license": "SSPL", "dependencies": { - "@mongodb-js/compass-app-stores": "^7.46.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", + "@mongodb-js/compass-app-stores": "^7.47.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-telemetry": "^1.10.0", - "@mongodb-js/compass-workspaces": "^0.41.0", + "@mongodb-js/compass-workspaces": "^0.42.0", "d3": "^3.5.17", "d3-timer": "^1.0.3", "debug": "^4.3.4", diff --git a/packages/compass-settings/package.json b/packages/compass-settings/package.json index 7943acde773..c0b3ac26362 100644 --- a/packages/compass-settings/package.json +++ b/packages/compass-settings/package.json @@ -11,7 +11,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "0.58.0", + "version": "0.59.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -49,11 +49,11 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "dependencies": { - "@mongodb-js/atlas-service": "^0.45.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-generative-ai": "^0.40.0", + "@mongodb-js/atlas-service": "^0.46.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-generative-ai": "^0.41.0", "@mongodb-js/compass-logging": "^1.7.2", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "hadron-app-registry": "^9.4.11", "hadron-ipc": "^3.5.2", "react": "^17.0.2", diff --git a/packages/compass-shell/package.json b/packages/compass-shell/package.json index f1dccb28327..08f4f746aea 100644 --- a/packages/compass-shell/package.json +++ b/packages/compass-shell/package.json @@ -6,7 +6,7 @@ "email": "compass@mongodb.com" }, "private": true, - "version": "3.59.0", + "version": "3.60.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -49,19 +49,19 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "dependencies": { - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-editor": "^0.40.2", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-editor": "^0.41.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", "@mongodb-js/compass-user-data": "^0.7.2", "@mongodb-js/compass-utils": "^0.9.2", - "@mongodb-js/compass-workspaces": "^0.41.0", + "@mongodb-js/compass-workspaces": "^0.42.0", "@mongosh/browser-repl": "^3.12.0", "@mongosh/logging": "^3.8.0", "@mongosh/node-runtime-worker-thread": "^3.3.10", "bson": "^6.10.3", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "hadron-app-registry": "^9.4.11", "react": "^17.0.2", "react-redux": "^8.1.3", diff --git a/packages/compass-sidebar/package.json b/packages/compass-sidebar/package.json index b616674a5dc..ab716aae54a 100644 --- a/packages/compass-sidebar/package.json +++ b/packages/compass-sidebar/package.json @@ -11,7 +11,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "5.60.0", + "version": "5.61.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -48,21 +48,21 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "dependencies": { - "@mongodb-js/compass-app-stores": "^7.46.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connection-import-export": "^0.56.0", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-connections-navigation": "^1.59.0", + "@mongodb-js/compass-app-stores": "^7.47.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connection-import-export": "^0.57.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-connections-navigation": "^1.60.0", "@mongodb-js/compass-logging": "^1.7.2", - "@mongodb-js/compass-maybe-protect-connection-string": "^0.38.2", + "@mongodb-js/compass-maybe-protect-connection-string": "^0.39.0", "@mongodb-js/compass-telemetry": "^1.10.0", - "@mongodb-js/compass-workspaces": "^0.41.0", + "@mongodb-js/compass-workspaces": "^0.42.0", "@mongodb-js/connection-info": "^0.15.2", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "hadron-app-registry": "^9.4.11", "lodash": "^4.17.21", "mongodb": "^6.16.0", - "mongodb-instance-model": "^12.32.2", + "mongodb-instance-model": "^12.33.0", "mongodb-ns": "^2.4.2", "react": "^17.0.2", "react-redux": "^8.1.3", diff --git a/packages/compass-smoke-tests/package.json b/packages/compass-smoke-tests/package.json index 631366d3824..060110f7847 100644 --- a/packages/compass-smoke-tests/package.json +++ b/packages/compass-smoke-tests/package.json @@ -11,7 +11,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "1.1.19", + "version": "1.1.20", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -35,7 +35,7 @@ "@mongodb-js/prettier-config-compass": "^1.2.8", "@mongodb-js/tsconfig-compass": "^1.2.8", "@types/node": "^20", - "compass-e2e-tests": "^1.33.0", + "compass-e2e-tests": "^1.33.1", "depcheck": "^1.4.1", "debug": "^4.3.4", "hadron-build": "^25.8.2", diff --git a/packages/compass-web/package.json b/packages/compass-web/package.json index e9fe6a8c6aa..171718dd25e 100644 --- a/packages/compass-web/package.json +++ b/packages/compass-web/package.json @@ -14,7 +14,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "0.17.3", + "version": "0.17.4", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -63,30 +63,30 @@ "react-dom": "^17.0.2" }, "devDependencies": { - "@mongodb-js/atlas-service": "^0.45.0", - "@mongodb-js/compass-aggregations": "^9.62.0", - "@mongodb-js/compass-app-stores": "^7.46.0", - "@mongodb-js/compass-collection": "^4.59.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-crud": "^13.60.0", - "@mongodb-js/compass-data-modeling": "^1.11.0", - "@mongodb-js/compass-databases-collections": "^1.59.0", - "@mongodb-js/compass-explain-plan": "^6.60.0", - "@mongodb-js/compass-export-to-language": "^9.36.0", - "@mongodb-js/compass-field-store": "^9.35.0", - "@mongodb-js/compass-generative-ai": "^0.40.0", - "@mongodb-js/compass-global-writes": "^1.19.0", - "@mongodb-js/compass-indexes": "^5.59.0", + "@mongodb-js/atlas-service": "^0.46.0", + "@mongodb-js/compass-aggregations": "^9.63.0", + "@mongodb-js/compass-app-stores": "^7.47.0", + "@mongodb-js/compass-collection": "^4.60.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-crud": "^13.61.0", + "@mongodb-js/compass-data-modeling": "^1.12.0", + "@mongodb-js/compass-databases-collections": "^1.60.0", + "@mongodb-js/compass-explain-plan": "^6.61.0", + "@mongodb-js/compass-export-to-language": "^9.37.0", + "@mongodb-js/compass-field-store": "^9.36.0", + "@mongodb-js/compass-generative-ai": "^0.41.0", + "@mongodb-js/compass-global-writes": "^1.20.0", + "@mongodb-js/compass-indexes": "^5.60.0", "@mongodb-js/compass-logging": "^1.7.2", - "@mongodb-js/compass-query-bar": "^8.61.0", - "@mongodb-js/compass-schema": "^6.61.0", - "@mongodb-js/compass-schema-validation": "^6.60.0", - "@mongodb-js/compass-sidebar": "^5.60.0", + "@mongodb-js/compass-query-bar": "^8.62.0", + "@mongodb-js/compass-schema": "^6.62.0", + "@mongodb-js/compass-schema-validation": "^6.61.0", + "@mongodb-js/compass-sidebar": "^5.61.0", "@mongodb-js/compass-telemetry": "^1.10.0", - "@mongodb-js/compass-welcome": "^0.58.0", - "@mongodb-js/compass-workspaces": "^0.41.0", - "@mongodb-js/connection-storage": "^0.35.0", + "@mongodb-js/compass-welcome": "^0.59.0", + "@mongodb-js/compass-workspaces": "^0.42.0", + "@mongodb-js/connection-storage": "^0.36.0", "@mongodb-js/devtools-proxy-support": "^0.4.4", "@mongodb-js/eslint-config-compass": "^1.3.10", "@mongodb-js/mocha-config-compass": "^1.6.8", @@ -105,7 +105,7 @@ "bson": "^6.2.0", "buffer": "^6.0.3", "chai": "^4.3.6", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "crypto-browserify": "^3.12.0", "debug": "^4.3.4", "depcheck": "^1.4.1", diff --git a/packages/compass-welcome/package.json b/packages/compass-welcome/package.json index 5a55d9110ae..6b82a1c5787 100644 --- a/packages/compass-welcome/package.json +++ b/packages/compass-welcome/package.json @@ -11,7 +11,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "0.58.0", + "version": "0.59.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -49,12 +49,12 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "dependencies": { - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", - "@mongodb-js/compass-workspaces": "^0.41.0", - "compass-preferences-model": "^2.40.2", + "@mongodb-js/compass-workspaces": "^0.42.0", + "compass-preferences-model": "^2.41.0", "hadron-app-registry": "^9.4.11", "react": "^17.0.2", "redux": "^4.2.1", diff --git a/packages/compass-workspaces/package.json b/packages/compass-workspaces/package.json index ed44e0031e7..cd27d79f7d0 100644 --- a/packages/compass-workspaces/package.json +++ b/packages/compass-workspaces/package.json @@ -11,7 +11,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "0.41.0", + "version": "0.42.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -51,13 +51,13 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "dependencies": { - "@mongodb-js/compass-app-stores": "^7.46.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", + "@mongodb-js/compass-app-stores": "^7.47.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-logging": "^1.7.2", "bson": "^6.10.3", "hadron-app-registry": "^9.4.11", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "lodash": "^4.17.21", "mongodb-collection-model": "^5.29.2", "mongodb-database-model": "^2.29.2", diff --git a/packages/compass/package.json b/packages/compass/package.json index 03921fb01b0..e80f1213a6f 100644 --- a/packages/compass/package.json +++ b/packages/compass/package.json @@ -194,46 +194,46 @@ "devDependencies": { "@electron/rebuild": "^4.0.1", "@electron/remote": "^2.1.2", - "@mongodb-js/atlas-service": "^0.45.0", - "@mongodb-js/compass-aggregations": "^9.62.0", - "@mongodb-js/compass-app-stores": "^7.46.0", - "@mongodb-js/compass-collection": "^4.59.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connection-import-export": "^0.56.0", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-crud": "^13.60.0", - "@mongodb-js/compass-data-modeling": "^1.11.0", - "@mongodb-js/compass-databases-collections": "^1.59.0", - "@mongodb-js/compass-explain-plan": "^6.60.0", - "@mongodb-js/compass-export-to-language": "^9.36.0", - "@mongodb-js/compass-field-store": "^9.35.0", - "@mongodb-js/compass-find-in-page": "^4.39.2", - "@mongodb-js/compass-generative-ai": "^0.40.0", - "@mongodb-js/compass-global-writes": "^1.19.0", - "@mongodb-js/compass-import-export": "^7.59.0", - "@mongodb-js/compass-indexes": "^5.59.0", - "@mongodb-js/compass-intercom": "^0.24.2", + "@mongodb-js/atlas-service": "^0.46.0", + "@mongodb-js/compass-aggregations": "^9.63.0", + "@mongodb-js/compass-app-stores": "^7.47.0", + "@mongodb-js/compass-collection": "^4.60.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connection-import-export": "^0.57.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-crud": "^13.61.0", + "@mongodb-js/compass-data-modeling": "^1.12.0", + "@mongodb-js/compass-databases-collections": "^1.60.0", + "@mongodb-js/compass-explain-plan": "^6.61.0", + "@mongodb-js/compass-export-to-language": "^9.37.0", + "@mongodb-js/compass-field-store": "^9.36.0", + "@mongodb-js/compass-find-in-page": "^4.40.0", + "@mongodb-js/compass-generative-ai": "^0.41.0", + "@mongodb-js/compass-global-writes": "^1.20.0", + "@mongodb-js/compass-import-export": "^7.60.0", + "@mongodb-js/compass-indexes": "^5.60.0", + "@mongodb-js/compass-intercom": "^0.25.0", "@mongodb-js/compass-logging": "^1.7.2", - "@mongodb-js/compass-query-bar": "^8.61.0", - "@mongodb-js/compass-saved-aggregations-queries": "^1.60.0", - "@mongodb-js/compass-schema": "^6.61.0", - "@mongodb-js/compass-schema-validation": "^6.60.0", - "@mongodb-js/compass-serverstats": "^16.59.0", - "@mongodb-js/compass-settings": "^0.58.0", - "@mongodb-js/compass-shell": "^3.59.0", - "@mongodb-js/compass-sidebar": "^5.60.0", + "@mongodb-js/compass-query-bar": "^8.62.0", + "@mongodb-js/compass-saved-aggregations-queries": "^1.61.0", + "@mongodb-js/compass-schema": "^6.62.0", + "@mongodb-js/compass-schema-validation": "^6.61.0", + "@mongodb-js/compass-serverstats": "^16.60.0", + "@mongodb-js/compass-settings": "^0.59.0", + "@mongodb-js/compass-shell": "^3.60.0", + "@mongodb-js/compass-sidebar": "^5.61.0", "@mongodb-js/compass-telemetry": "^1.10.0", "@mongodb-js/compass-utils": "^0.9.2", - "@mongodb-js/compass-welcome": "^0.58.0", - "@mongodb-js/compass-workspaces": "^0.41.0", + "@mongodb-js/compass-welcome": "^0.59.0", + "@mongodb-js/compass-workspaces": "^0.42.0", "@mongodb-js/connection-info": "^0.15.2", - "@mongodb-js/connection-storage": "^0.35.0", + "@mongodb-js/connection-storage": "^0.36.0", "@mongodb-js/devtools-proxy-support": "^0.4.4", "@mongodb-js/eslint-config-compass": "^1.3.10", "@mongodb-js/get-os-info": "^0.4.0", "@mongodb-js/mocha-config-compass": "^1.6.8", "@mongodb-js/mongodb-downloader": "^0.3.7", - "@mongodb-js/my-queries-storage": "^0.27.3", + "@mongodb-js/my-queries-storage": "^0.28.0", "@mongodb-js/prettier-config-compass": "^1.2.8", "@mongodb-js/sbom-tools": "^0.7.2", "@mongodb-js/signing-utils": "^0.3.8", @@ -246,7 +246,7 @@ "chai": "^4.3.4", "chalk": "^4.1.2", "clean-stack": "^2.0.0", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "cross-spawn": "^7.0.5", "debug": "^4.3.4", "depcheck": "^1.4.1", diff --git a/packages/connection-form/package.json b/packages/connection-form/package.json index b80e84bfaac..0bcf9849bd3 100644 --- a/packages/connection-form/package.json +++ b/packages/connection-form/package.json @@ -13,7 +13,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "1.52.3", + "version": "1.53.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -48,8 +48,8 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "dependencies": { - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-editor": "^0.40.2", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-editor": "^0.41.0", "@mongodb-js/connection-info": "^0.15.2", "@mongodb-js/shell-bson-parser": "^1.2.0", "lodash": "^4.17.21", diff --git a/packages/connection-storage/package.json b/packages/connection-storage/package.json index d3daed9babc..95baed11cfc 100644 --- a/packages/connection-storage/package.json +++ b/packages/connection-storage/package.json @@ -13,7 +13,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "0.35.0", + "version": "0.36.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -62,7 +62,7 @@ "@mongodb-js/compass-utils": "^0.9.2", "@mongodb-js/connection-info": "^0.15.2", "bson": "^6.10.3", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "electron": "^36.4.0", "hadron-app-registry": "^9.4.11", "hadron-ipc": "^3.5.2", diff --git a/packages/databases-collections-list/package.json b/packages/databases-collections-list/package.json index 521e52ca05d..1dde9a46338 100644 --- a/packages/databases-collections-list/package.json +++ b/packages/databases-collections-list/package.json @@ -13,7 +13,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "1.57.0", + "version": "1.58.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -48,12 +48,12 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "dependencies": { - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-telemetry": "^1.10.0", - "@mongodb-js/compass-workspaces": "^0.41.0", + "@mongodb-js/compass-workspaces": "^0.42.0", "@mongodb-js/connection-info": "^0.15.2", - "compass-preferences-model": "^2.40.2", + "compass-preferences-model": "^2.41.0", "mongodb-collection-model": "^5.29.2", "mongodb-database-model": "^2.29.2", "mongodb-ns": "^2.4.2", diff --git a/packages/databases-collections/package.json b/packages/databases-collections/package.json index a54a2110350..9541f043226 100644 --- a/packages/databases-collections/package.json +++ b/packages/databases-collections/package.json @@ -2,7 +2,7 @@ "name": "@mongodb-js/compass-databases-collections", "description": "Plugin for viewing the list of, creating, and dropping databases and collections", "private": true, - "version": "1.59.0", + "version": "1.60.0", "license": "SSPL", "homepage": "https://github.com/mongodb-js/compass", "bugs": { @@ -58,21 +58,21 @@ "typescript": "^5.0.4" }, "dependencies": { - "@mongodb-js/compass-app-stores": "^7.46.0", - "@mongodb-js/compass-components": "^1.38.1", - "@mongodb-js/compass-connections": "^1.60.0", - "@mongodb-js/compass-editor": "^0.40.2", + "@mongodb-js/compass-app-stores": "^7.47.0", + "@mongodb-js/compass-components": "^1.39.0", + "@mongodb-js/compass-connections": "^1.61.0", + "@mongodb-js/compass-editor": "^0.41.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", - "@mongodb-js/compass-workspaces": "^0.41.0", - "@mongodb-js/databases-collections-list": "^1.57.0", - "@mongodb-js/my-queries-storage": "^0.27.3", - "compass-preferences-model": "^2.40.2", + "@mongodb-js/compass-workspaces": "^0.42.0", + "@mongodb-js/databases-collections-list": "^1.58.0", + "@mongodb-js/my-queries-storage": "^0.28.0", + "compass-preferences-model": "^2.41.0", "hadron-app-registry": "^9.4.11", "lodash": "^4.17.21", "mongodb-collection-model": "^5.29.2", "mongodb-database-model": "^2.29.2", - "mongodb-instance-model": "^12.32.2", + "mongodb-instance-model": "^12.33.0", "mongodb-ns": "^2.4.2", "mongodb-query-parser": "^4.3.0", "prop-types": "^15.7.2", diff --git a/packages/instance-model/package.json b/packages/instance-model/package.json index 581fb4881be..bf92e02b4a7 100644 --- a/packages/instance-model/package.json +++ b/packages/instance-model/package.json @@ -2,7 +2,7 @@ "name": "mongodb-instance-model", "description": "MongoDB instance model", "author": "Lucas Hrabovsky ", - "version": "12.32.2", + "version": "12.33.0", "bugs": { "url": "https://jira.mongodb.org/projects/COMPASS/issues", "email": "compass@mongodb.com" @@ -32,7 +32,7 @@ "mongodb-collection-model": "^5.29.2", "mongodb-data-service": "^22.28.2", "mongodb-database-model": "^2.29.2", - "compass-preferences-model": "^2.40.2" + "compass-preferences-model": "^2.41.0" }, "devDependencies": { "@mongodb-js/eslint-config-compass": "^1.3.10", diff --git a/packages/my-queries-storage/package.json b/packages/my-queries-storage/package.json index 63b9590376f..7eb8941191f 100644 --- a/packages/my-queries-storage/package.json +++ b/packages/my-queries-storage/package.json @@ -13,7 +13,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/compass", - "version": "0.27.3", + "version": "0.28.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/compass.git" @@ -71,7 +71,7 @@ "typescript": "^5.0.4" }, "dependencies": { - "@mongodb-js/compass-editor": "^0.40.2", + "@mongodb-js/compass-editor": "^0.41.0", "@mongodb-js/compass-user-data": "^0.7.2", "bson": "^6.10.3", "hadron-app-registry": "^9.4.11", From 7e534be419f3f84bcbb7345c51254fce9e7f528f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Wed, 18 Jun 2025 23:06:05 +0200 Subject: [PATCH 66/76] chore(smoke-tests): windows fixes (#7038) * Only remove windows installer if it exists * Warn if removal fails * Passing SQUIRREL_TEMP to sandbox the install * Use --checkInstall to silence the Update.exe * Add a debug statement with the executable * Pass --silent directly --- .../src/installers/windows-setup.ts | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/packages/compass-smoke-tests/src/installers/windows-setup.ts b/packages/compass-smoke-tests/src/installers/windows-setup.ts index 4f580d939ca..21864cb5c3e 100644 --- a/packages/compass-smoke-tests/src/installers/windows-setup.ts +++ b/packages/compass-smoke-tests/src/installers/windows-setup.ts @@ -24,6 +24,7 @@ export function installWindowsSetup({ kind, filepath, buildInfo, + sandboxPath, }: InstallablePackage): InstalledAppInfo { assert.equal(kind, 'windows_setup'); const appName = buildInfo.installerOptions.name; @@ -62,7 +63,18 @@ export function installWindowsSetup({ debug(`Running command to uninstall: ${uninstallCommand}`); cp.execSync(uninstallCommand, { stdio: 'inherit' }); // Removing the any remaining files manually - fs.rmSync(installLocation, { recursive: true, force: true }); + try { + if (fs.existsSync(installLocation)) { + debug(`Removing installer: ${installLocation}`); + fs.rmSync(installLocation, { recursive: true, force: true }); + } + } catch (error) { + console.warn( + `Failed to remove install location ${installLocation}: ${ + error instanceof Error ? error.message : error + }` + ); + } } } @@ -72,7 +84,20 @@ export function installWindowsSetup({ console.warn( "Installing globally, since we haven't discovered a way to specify an install path" ); - execute(filepath, []); + execute( + filepath, + [ + // Args are passed through to the Update.exe https://github.com/Squirrel/Squirrel.Windows/blob/51f5e2cb01add79280a53d51e8d0cfa20f8c9f9f/src/Setup/winmain.cpp#L125 + // See options in https://github.com/Squirrel/Squirrel.Windows/blob/51f5e2cb01add79280a53d51e8d0cfa20f8c9f9f/src/Update/StartupOption.cs + '--silent', + ], + { + env: { + // See https://github.com/Squirrel/Squirrel.Windows/blob/51f5e2cb01add79280a53d51e8d0cfa20f8c9f9f/src/Setup/UpdateRunner.cpp#L173C40-L173C54 + SQUIRREL_TEMP: sandboxPath, + }, + } + ); const entry = queryRegistry(); assert(entry !== null, 'Expected an entry in the registry after installing'); @@ -84,6 +109,7 @@ export function installWindowsSetup({ const appExecutablePath = path.resolve(appPath, `${appName}.exe`); // Check if the app executable exists after installing + debug('Using app executable path: %s', appExecutablePath); assert( fs.existsSync(appExecutablePath), `Expected ${appExecutablePath} to exist` From 66abc2c85d161f4c83d88f4dba598e9498dae06a Mon Sep 17 00:00:00 2001 From: Rhys Date: Thu, 19 Jun 2025 03:05:28 -0400 Subject: [PATCH 67/76] refactor(app-registry): rename hadron-app-registry to @mongodb-js/compass-app-registry (#7040) * refactor(app-registry): rename hadron-app-registry to compass-app-registry, rename HadronPlugin to CompassPlugin * fixup: @mongodb-js/compass-app-registry, not compass-app-registry --- CONTRIBUTING.md | 2 +- README.md | 2 +- configs/testing-library-compass/.depcheckrc | 2 +- configs/testing-library-compass/src/index.tsx | 15 +- package-lock.json | 315 ++++++++++-------- packages/atlas-service/package.json | 2 +- packages/atlas-service/src/provider.tsx | 2 +- packages/atlas-service/src/renderer.ts | 4 +- .../src/store/atlas-signin-store.spec.ts | 2 +- .../src/store/atlas-signin-store.ts | 2 +- packages/compass-aggregations/README.md | 8 +- packages/compass-aggregations/package.json | 2 +- packages/compass-aggregations/src/index.ts | 8 +- .../compass-aggregations/src/modules/index.ts | 2 +- .../pipeline-builder/stage-editor.spec.ts | 2 +- .../src/modules/update-view.spec.ts | 2 +- .../src/stores/create-view.spec.ts | 2 +- .../src/stores/create-view.ts | 4 +- .../src/stores/store.spec.ts | 2 +- .../compass-aggregations/src/stores/store.ts | 4 +- .../.depcheckrc | 0 .../.eslintignore | 0 .../.eslintrc.js | 0 .../.gitignore | 0 .../.mocharc.js | 0 .../.npmignore | 0 .../README.md | 12 +- .../package.json | 9 +- .../src/app-registry.spec.ts | 0 .../src/app-registry.ts | 0 .../src/index.ts | 8 +- .../src/react-context.tsx | 0 .../src/register-plugin.spec.tsx | 12 +- .../src/register-plugin.tsx | 42 +-- .../tsconfig-lint.json | 0 .../tsconfig.json | 0 packages/compass-app-stores/package.json | 2 +- packages/compass-app-stores/src/plugin.tsx | 8 +- packages/compass-app-stores/src/provider.tsx | 2 +- .../src/stores/instance-store.spec.ts | 2 +- .../src/stores/instance-store.ts | 5 +- packages/compass-collection/package.json | 2 +- .../components/collection-tab-provider.tsx | 4 +- packages/compass-collection/src/index.ts | 4 +- .../src/modules/collection-tab.ts | 2 +- .../src/stores/collection-tab.spec.ts | 2 +- .../src/stores/collection-tab.ts | 4 +- packages/compass-connections/package.json | 2 +- .../src/connection-info-provider.tsx | 2 +- .../src/connection-scoped-app-registry.ts | 2 +- packages/compass-connections/src/index.tsx | 4 +- packages/compass-connections/src/provider.ts | 2 +- .../src/stores/connections-store-redux.ts | 2 +- .../src/stores/store-context.tsx | 2 +- packages/compass-crud/README.md | 12 +- packages/compass-crud/package.json | 2 +- packages/compass-crud/src/index.ts | 6 +- .../src/stores/crud-store.spec.ts | 4 +- .../compass-crud/src/stores/crud-store.ts | 4 +- packages/compass-data-modeling/package.json | 2 +- packages/compass-data-modeling/src/index.ts | 4 +- .../src/provider/index.tsx | 2 +- .../compass-data-modeling/src/store/index.ts | 2 +- .../test/setup-store.tsx | 2 +- packages/compass-explain-plan/package.json | 2 +- packages/compass-explain-plan/src/index.ts | 4 +- .../stores/explain-plan-modal-store.spec.ts | 4 +- .../compass-explain-plan/src/stores/index.ts | 4 +- .../compass-export-to-language/package.json | 2 +- .../compass-export-to-language/src/index.ts | 4 +- .../src/stores/index.ts | 4 +- packages/compass-field-store/package.json | 2 +- packages/compass-field-store/src/index.tsx | 4 +- .../src/stores/field-store-service.ts | 2 +- .../compass-field-store/src/stores/store.ts | 2 +- packages/compass-find-in-page/package.json | 2 +- packages/compass-find-in-page/src/index.ts | 4 +- packages/compass-generative-ai/package.json | 2 +- packages/compass-generative-ai/src/index.ts | 4 +- .../compass-generative-ai/src/provider.tsx | 2 +- .../src/store/atlas-ai-store.ts | 2 +- packages/compass-global-writes/package.json | 2 +- packages/compass-global-writes/src/index.ts | 6 +- .../compass-global-writes/src/store/index.ts | 2 +- .../tests/create-store.tsx | 2 +- packages/compass-import-export/package.json | 2 +- packages/compass-import-export/src/index.ts | 6 +- .../src/stores/export-store.spec.tsx | 2 +- .../src/stores/export-store.ts | 4 +- .../src/stores/import-store.spec.tsx | 2 +- .../src/stores/import-store.ts | 4 +- packages/compass-indexes/package.json | 2 +- packages/compass-indexes/src/index.spec.tsx | 4 +- packages/compass-indexes/src/index.ts | 6 +- packages/compass-indexes/src/modules/index.ts | 2 +- .../compass-indexes/src/stores/store.spec.ts | 2 +- packages/compass-indexes/src/stores/store.ts | 4 +- packages/compass-indexes/test/setup-store.ts | 4 +- packages/compass-logging/package.json | 2 +- packages/compass-logging/src/provider.ts | 2 +- .../compass-preferences-model/package.json | 2 +- .../compass-preferences-model/src/react.ts | 2 +- packages/compass-query-bar/package.json | 2 +- .../src/components/hooks.tsx | 2 +- packages/compass-query-bar/src/index.tsx | 4 +- .../src/stores/query-bar-reducer.spec.ts | 2 +- .../src/stores/query-bar-store.spec.ts | 2 +- .../src/stores/query-bar-store.ts | 4 +- .../package.json | 2 +- .../src/index.ts | 4 +- .../src/stores/index.ts | 2 +- .../compass-schema-validation/package.json | 2 +- .../compass-schema-validation/src/index.ts | 6 +- .../src/modules/index.ts | 2 +- .../src/stores/store.spec.ts | 4 +- .../src/stores/store.ts | 5 +- packages/compass-schema/package.json | 2 +- packages/compass-schema/src/index.ts | 6 +- .../compass-schema/src/stores/store.spec.ts | 4 +- packages/compass-schema/src/stores/store.ts | 4 +- packages/compass-serverstats/package.json | 2 +- packages/compass-serverstats/src/index.ts | 4 +- packages/compass-settings/package.json | 2 +- packages/compass-settings/src/index.ts | 4 +- packages/compass-settings/src/stores/index.ts | 2 +- packages/compass-shell/package.json | 2 +- packages/compass-shell/src/index.ts | 4 +- packages/compass-shell/src/plugin.tsx | 2 +- packages/compass-sidebar/package.json | 2 +- .../multiple-connections/sidebar.spec.tsx | 2 +- packages/compass-sidebar/src/index.ts | 9 +- packages/compass-sidebar/src/modules/index.ts | 2 +- .../src/modules/instance.spec.ts | 2 +- packages/compass-sidebar/src/stores/store.ts | 5 +- packages/compass-telemetry/package.json | 2 +- packages/compass-telemetry/src/provider.tsx | 2 +- packages/compass-web/package.json | 2 +- .../compass-web/src/connection-storage.tsx | 2 +- packages/compass-web/src/entrypoint.tsx | 2 +- packages/compass-welcome/package.json | 2 +- packages/compass-welcome/src/index.ts | 6 +- packages/compass-welcome/src/stores/index.ts | 2 +- packages/compass-workspaces/package.json | 2 +- .../workspace-tab-context-provider.tsx | 2 +- packages/compass-workspaces/src/index.ts | 8 +- packages/compass-workspaces/src/provider.tsx | 2 +- .../src/stores/workspaces.ts | 2 +- packages/compass-workspaces/src/types.ts | 4 +- packages/compass/package.json | 2 +- packages/compass/src/app/application.tsx | 2 +- .../compass/src/app/components/entrypoint.tsx | 2 +- packages/compass/src/app/components/home.tsx | 2 +- packages/connection-storage/package.json | 2 +- packages/connection-storage/src/provider.ts | 2 +- packages/databases-collections/package.json | 2 +- .../src/collections-plugin.tsx | 4 +- .../create-namespace-modal.spec.tsx | 2 +- .../rename-collection-modal.spec.tsx | 2 +- .../src/databases-plugin.tsx | 4 +- packages/databases-collections/src/index.ts | 8 +- .../src/modules/databases.ts | 2 +- .../rename-collection.spec.ts | 4 +- .../src/stores/collections-store.ts | 4 +- .../src/stores/create-namespace.spec.tsx | 2 +- .../src/stores/create-namespace.ts | 4 +- .../src/stores/databases-store.ts | 4 +- .../src/stores/drop-namespace.spec.tsx | 2 +- .../src/stores/drop-namespace.tsx | 4 +- .../src/stores/rename-collection.ts | 4 +- packages/hadron-ipc/README.md | 8 +- packages/my-queries-storage/package.json | 2 +- packages/my-queries-storage/src/provider.ts | 2 +- scripts/create-workspace.js | 6 +- 173 files changed, 479 insertions(+), 420 deletions(-) rename packages/{hadron-app-registry => compass-app-registry}/.depcheckrc (100%) rename packages/{hadron-app-registry => compass-app-registry}/.eslintignore (100%) rename packages/{hadron-app-registry => compass-app-registry}/.eslintrc.js (100%) rename packages/{hadron-app-registry => compass-app-registry}/.gitignore (100%) rename packages/{hadron-app-registry => compass-app-registry}/.mocharc.js (100%) rename packages/{hadron-app-registry => compass-app-registry}/.npmignore (100%) rename packages/{hadron-app-registry => compass-app-registry}/README.md (95%) rename packages/{hadron-app-registry => compass-app-registry}/package.json (92%) rename packages/{hadron-app-registry => compass-app-registry}/src/app-registry.spec.ts (100%) rename packages/{hadron-app-registry => compass-app-registry}/src/app-registry.ts (100%) rename packages/{hadron-app-registry => compass-app-registry}/src/index.ts (76%) rename packages/{hadron-app-registry => compass-app-registry}/src/react-context.tsx (100%) rename packages/{hadron-app-registry => compass-app-registry}/src/register-plugin.spec.tsx (95%) rename packages/{hadron-app-registry => compass-app-registry}/src/register-plugin.tsx (93%) rename packages/{hadron-app-registry => compass-app-registry}/tsconfig-lint.json (100%) rename packages/{hadron-app-registry => compass-app-registry}/tsconfig.json (100%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8621680d04d..1e8129bd853 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,7 +42,7 @@ To enable the Chrome DevTools for the Electron renderer processes, click "Settin > [!NOTE] > For documentation regarding how to write plugin packages, check out the -> [hadron-app-registry](./packages/hadron-app-registry/README.md) documentation. +> [@mongodb-js/compass-app-registry](./packages/compass-app-registry/README.md) documentation. To run npm scripts inside specific workspaces in the monorepo you can use either `lerna --scope` or `npm --workspace` command line arguments. As an example, to run all tests in one plugin that you are working on such as the `compass-aggregations` plugin, you can run `npm run test --workspace packages/compass-aggregation` or `lerna run test --scope @mongodb-js/compass-aggregations` commands diff --git a/README.md b/README.md index b05d0667a2c..397e734975a 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ Is there anything else you’d like to see in Compass? Let us know by submitting ### Shared Libraries and Build Tools - [**@mongodb-js/atlas-service**](packages/atlas-service): Service to handle Atlas sign in and API requests +- [**@mongodb-js/compass-app-registry**](packages/compass-app-registry): Compass App Registry - [**@mongodb-js/compass-components**](packages/compass-components): React Components used in Compass - [**@mongodb-js/compass-connection-import-export**](packages/compass-connection-import-export): UI for Compass connection import/export - [**@mongodb-js/compass-connections**](packages/compass-connections): Manage your MongoDB connections and connect in Compass @@ -67,7 +68,6 @@ Is there anything else you’d like to see in Compass? Let us know by submitting - [**bson-transpilers**](packages/bson-transpilers): Source to source compilers using ANTLR - [**compass-e2e-tests**](packages/compass-e2e-tests): E2E test suite for Compass app that follows smoke tests / feature testing matrix - [**compass-preferences-model**](packages/compass-preferences-model): Compass preferences model -- [**hadron-app-registry**](packages/hadron-app-registry): Hadron App Registry - [**hadron-build**](packages/hadron-build): Tooling for Hadron apps like Compass - [**hadron-document**](packages/hadron-document): Hadron Document - [**hadron-ipc**](packages/hadron-ipc): Simplified IPC for electron apps. diff --git a/configs/testing-library-compass/.depcheckrc b/configs/testing-library-compass/.depcheckrc index 18b9386cedf..18e3a995825 100644 --- a/configs/testing-library-compass/.depcheckrc +++ b/configs/testing-library-compass/.depcheckrc @@ -8,6 +8,7 @@ ignores: # dependency will introduce a circular one in our dependency tree, as it's only # used for testing and doesn't require compilation, we're escaping the # recursiveness issue by just not including those in the package.json + - '@mongodb-js/compass-app-registry' - '@mongodb-js/compass-logging' - '@mongodb-js/compass-telemetry' - '@mongodb-js/connection-info' @@ -15,7 +16,6 @@ ignores: - '@mongodb-js/compass-components' - '@mongodb-js/connection-storage' - 'compass-preferences-model' - - 'hadron-app-registry' - 'mongodb-data-service' ignore-patterns: - 'dist' diff --git a/configs/testing-library-compass/src/index.tsx b/configs/testing-library-compass/src/index.tsx index 6f3bf3bb79d..05f40da4ec6 100644 --- a/configs/testing-library-compass/src/index.tsx +++ b/configs/testing-library-compass/src/index.tsx @@ -60,11 +60,14 @@ import { import CompassConnections, { ConnectFnProvider, } from '@mongodb-js/compass-connections/src/index'; -import type { HadronPluginComponent, HadronPlugin } from 'hadron-app-registry'; +import type { + CompassPluginComponent, + CompassPlugin, +} from '@mongodb-js/compass-app-registry'; import AppRegistry, { AppRegistryProvider, GlobalAppRegistryProvider, -} from 'hadron-app-registry'; +} from '@mongodb-js/compass-app-registry'; import { expect } from 'chai'; import { Provider } from 'react-redux'; import ConnectionString from 'mongodb-connection-string-url'; @@ -560,9 +563,9 @@ async function renderHookWithActiveConnection( function createPluginWrapper< Props, ServiceLocators extends Record unknown>, - PluginContext extends HadronPlugin + PluginContext extends CompassPlugin >( - Plugin: HadronPluginComponent, + Plugin: CompassPluginComponent, initialPluginProps?: Props, ReactTestingLibraryWrapper: ComponentWithChildren = EmptyWrapper ) { @@ -585,9 +588,9 @@ function createPluginWrapper< function createPluginTestHelpers< Props, ServiceLocators extends Record unknown>, - PluginContext extends HadronPlugin + PluginContext extends CompassPlugin >( - Plugin: HadronPluginComponent, + Plugin: CompassPluginComponent, defaultInitialPluginProps?: Props ) { return { diff --git a/package-lock.json b/package-lock.json index 2fcbb642825..a3533acc313 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8310,6 +8310,10 @@ "resolved": "packages/compass-aggregations", "link": true }, + "node_modules/@mongodb-js/compass-app-registry": { + "resolved": "packages/compass-app-registry", + "link": true + }, "node_modules/@mongodb-js/compass-app-stores": { "resolved": "packages/compass-app-stores", "link": true @@ -24876,10 +24880,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/hadron-app-registry": { - "resolved": "packages/hadron-app-registry", - "link": true - }, "node_modules/hadron-build": { "resolved": "packages/hadron-build", "link": true @@ -42872,6 +42872,7 @@ "version": "0.46.0", "license": "SSPL", "dependencies": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", @@ -42883,7 +42884,6 @@ "@mongodb-js/oidc-plugin": "^1.1.7", "compass-preferences-model": "^2.41.0", "electron": "^36.4.0", - "hadron-app-registry": "^9.4.11", "hadron-ipc": "^3.5.2", "lodash": "^4.17.21", "react": "^17.0.2", @@ -43009,6 +43009,7 @@ "@electron/remote": "^2.1.2", "@mongodb-js/atlas-service": "^0.46.0", "@mongodb-js/compass-aggregations": "^9.63.0", + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-app-stores": "^7.47.0", "@mongodb-js/compass-collection": "^4.60.0", "@mongodb-js/compass-components": "^1.39.0", @@ -43069,7 +43070,6 @@ "electron-mocha": "^12.2.0", "ensure-error": "^3.0.1", "glob": "^10.2.5", - "hadron-app-registry": "^9.4.11", "hadron-build": "^25.8.2", "hadron-ipc": "^3.5.2", "make-fetch-happen": "^10.2.1", @@ -43108,6 +43108,7 @@ "@dnd-kit/sortable": "^7.0.2", "@dnd-kit/utilities": "^3.2.1", "@mongodb-js/atlas-service": "^0.46.0", + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-app-stores": "^7.47.0", "@mongodb-js/compass-collection": "^4.60.0", "@mongodb-js/compass-components": "^1.39.0", @@ -43126,7 +43127,6 @@ "@mongodb-js/shell-bson-parser": "^1.2.0", "bson": "^6.10.3", "compass-preferences-model": "^2.41.0", - "hadron-app-registry": "^9.4.11", "hadron-document": "^8.8.12", "hadron-type-checker": "^7.4.10", "lodash": "^4.17.21", @@ -43329,17 +43329,74 @@ "node": ">=12" } }, + "packages/compass-app-registry": { + "name": "@mongodb-js/compass-app-registry", + "version": "9.4.11", + "license": "SSPL", + "dependencies": { + "eventemitter3": "^4.0.0", + "react": "^17.0.2", + "react-redux": "^8.1.3", + "redux": "^4.2.1", + "reflux": "^0.4.1" + }, + "devDependencies": { + "@mongodb-js/eslint-config-compass": "^1.3.10", + "@mongodb-js/mocha-config-compass": "^1.6.8", + "@mongodb-js/prettier-config-compass": "^1.2.8", + "@mongodb-js/testing-library-compass": "^1.3.2", + "@mongodb-js/tsconfig-compass": "^1.2.8", + "@types/chai": "^4.2.21", + "@types/mocha": "^9.0.0", + "@types/reflux": "^6.4.3", + "chai": "^4.1.2", + "depcheck": "^1.4.1", + "mocha": "^10.2.0", + "sinon": "^9.0.0", + "typescript": "^5.0.4" + } + }, + "packages/compass-app-registry/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "packages/compass-app-registry/node_modules/sinon": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.4.tgz", + "integrity": "sha512-zljcULZQsJxVra28qIAL6ow1Z9tpattkCTEJR4RBP3TGc00FcttsP5pK284Nas5WjMZU5Yzy3kAIp3B3KRf5Yg==", + "deprecated": "16.1.1", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^1.8.1", + "@sinonjs/fake-timers": "^6.0.1", + "@sinonjs/samsam": "^5.3.1", + "diff": "^4.0.2", + "nise": "^4.0.4", + "supports-color": "^7.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, "packages/compass-app-stores": { "name": "@mongodb-js/compass-app-stores", "version": "7.47.0", "license": "SSPL", "dependencies": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/connection-info": "^0.15.2", "compass-preferences-model": "^2.41.0", - "hadron-app-registry": "^9.4.11", "mongodb-collection-model": "^5.29.2", "mongodb-database-model": "^2.29.2", "mongodb-instance-model": "^12.33.0", @@ -43397,6 +43454,7 @@ "version": "4.60.0", "license": "SSPL", "dependencies": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-app-stores": "^7.47.0", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-connections": "^1.61.0", @@ -43406,7 +43464,6 @@ "@mongodb-js/connection-info": "^0.15.2", "@mongodb-js/mongodb-constants": "^0.11.0", "compass-preferences-model": "^2.41.0", - "hadron-app-registry": "^9.4.11", "mongodb-collection-model": "^5.29.2", "mongodb-ns": "^2.4.2", "react": "^17.0.2", @@ -43783,6 +43840,7 @@ "version": "1.61.0", "license": "SSPL", "dependencies": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", @@ -43792,7 +43850,6 @@ "@mongodb-js/connection-storage": "^0.36.0", "bson": "^6.10.3", "compass-preferences-model": "^2.41.0", - "hadron-app-registry": "^9.4.11", "lodash": "^4.17.21", "mongodb": "^6.16.0", "mongodb-build-info": "^1.7.2", @@ -43925,6 +43982,7 @@ "version": "13.61.0", "license": "SSPL", "dependencies": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-app-stores": "^7.47.0", "@mongodb-js/compass-collection": "^4.60.0", "@mongodb-js/compass-components": "^1.39.0", @@ -43943,7 +44001,6 @@ "ag-grid-react": "^20.2.0", "bson": "^6.10.3", "compass-preferences-model": "^2.41.0", - "hadron-app-registry": "^9.4.11", "hadron-document": "^8.8.12", "hadron-type-checker": "^7.4.10", "jsondiffpatch": "^0.5.0", @@ -44021,6 +44078,7 @@ "version": "1.12.0", "license": "SSPL", "dependencies": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-app-stores": "^7.47.0", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-connections": "^1.61.0", @@ -44032,7 +44090,6 @@ "@mongodb-js/diagramming": "^1.0.2", "bson": "^6.10.3", "compass-preferences-model": "^2.41.0", - "hadron-app-registry": "^9.4.11", "lodash": "^4.17.21", "mongodb": "^6.14.1", "mongodb-ns": "^2.4.2", @@ -44890,6 +44947,7 @@ "version": "6.61.0", "license": "SSPL", "dependencies": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-collection": "^4.60.0", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-connections": "^1.61.0", @@ -44901,7 +44959,6 @@ "d3": "^3.5.17", "d3-flextree": "^2.1.2", "d3-hierarchy": "^3.1.2", - "hadron-app-registry": "^9.4.11", "lodash": "^4.17.21", "mongodb": "^6.16.0", "react": "^17.0.2", @@ -44968,6 +45025,7 @@ "version": "9.37.0", "license": "SSPL", "dependencies": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-collection": "^4.60.0", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-connections": "^1.61.0", @@ -44977,7 +45035,6 @@ "@mongodb-js/shell-bson-parser": "^1.2.0", "bson-transpilers": "^3.2.10", "compass-preferences-model": "^2.41.0", - "hadron-app-registry": "^9.4.11", "mongodb-ns": "^2.4.2", "react": "^17.0.2", "react-redux": "^8.1.3", @@ -45042,9 +45099,9 @@ "version": "9.36.0", "license": "SSPL", "dependencies": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-logging": "^1.7.2", - "hadron-app-registry": "^9.4.11", "lodash": "^4.17.21", "mongodb-schema": "^12.6.2", "react": "^17.0.2", @@ -45203,8 +45260,8 @@ "version": "4.40.0", "license": "SSPL", "dependencies": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-components": "^1.39.0", - "hadron-app-registry": "^9.4.11", "hadron-ipc": "^3.5.2", "react": "^17.0.2", "react-redux": "^8.1.3", @@ -45268,6 +45325,7 @@ "license": "SSPL", "dependencies": { "@mongodb-js/atlas-service": "^0.46.0", + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-intercom": "^0.25.0", @@ -45276,7 +45334,6 @@ "@mongodb-js/compass-utils": "^0.9.2", "bson": "^6.10.3", "compass-preferences-model": "^2.41.0", - "hadron-app-registry": "^9.4.11", "mongodb": "^6.16.0", "mongodb-schema": "^12.6.2", "react": "^17.0.2", @@ -45474,13 +45531,13 @@ "license": "SSPL", "dependencies": { "@mongodb-js/atlas-service": "^0.46.0", + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-collection": "^4.60.0", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-field-store": "^9.36.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", - "hadron-app-registry": "^9.4.11", "lodash": "^4.17.21", "mongodb-ns": "^2.4.2", "react": "^17.0.2", @@ -45666,6 +45723,7 @@ "license": "SSPL", "dependencies": { "@electron/remote": "^2.1.2", + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-editor": "^0.41.0", @@ -45677,7 +45735,6 @@ "compass-preferences-model": "^2.41.0", "debug": "^4.3.4", "electron": "^36.4.0", - "hadron-app-registry": "^9.4.11", "hadron-document": "^8.8.12", "hadron-ipc": "^3.5.2", "lodash": "^4.17.21", @@ -45885,6 +45942,7 @@ "license": "SSPL", "dependencies": { "@mongodb-js/atlas-service": "^0.46.0", + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-app-stores": "^7.47.0", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-connections": "^1.61.0", @@ -45897,7 +45955,6 @@ "@mongodb-js/shell-bson-parser": "^1.2.0", "bson": "^6.10.3", "compass-preferences-model": "^2.41.0", - "hadron-app-registry": "^9.4.11", "lodash": "^4.17.21", "mongodb": "^6.16.0", "mongodb-collection-model": "^5.29.2", @@ -46154,8 +46211,8 @@ "version": "1.7.2", "license": "SSPL", "dependencies": { + "@mongodb-js/compass-app-registry": "^9.4.11", "debug": "^4.3.4", - "hadron-app-registry": "^9.4.11", "hadron-ipc": "^3.5.2", "is-electron-renderer": "^2.0.1", "mongodb-log-writer": "^2.3.4", @@ -46261,12 +46318,12 @@ "version": "2.41.0", "license": "SSPL", "dependencies": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-user-data": "^0.7.2", "@mongodb-js/devtools-proxy-support": "^0.4.4", "bson": "^6.10.3", - "hadron-app-registry": "^9.4.11", "hadron-ipc": "^3.5.2", "js-yaml": "^4.1.0", "lodash": "^4.17.21", @@ -46327,6 +46384,7 @@ "license": "SSPL", "dependencies": { "@mongodb-js/atlas-service": "^0.46.0", + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-app-stores": "^7.47.0", "@mongodb-js/compass-collection": "^4.60.0", "@mongodb-js/compass-components": "^1.39.0", @@ -46340,7 +46398,6 @@ "@mongodb-js/my-queries-storage": "^0.28.0", "bson": "^6.10.3", "compass-preferences-model": "^2.41.0", - "hadron-app-registry": "^9.4.11", "lodash": "^4.17.21", "mongodb": "^6.16.0", "mongodb-instance-model": "^12.33.0", @@ -46539,6 +46596,7 @@ "version": "1.61.0", "license": "SSPL", "dependencies": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-app-stores": "^7.47.0", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-connections": "^1.61.0", @@ -46551,7 +46609,6 @@ "bson": "^6.10.3", "compass-preferences-model": "^2.41.0", "fuse.js": "^6.5.3", - "hadron-app-registry": "^9.4.11", "mongodb-ns": "^2.4.2", "react": "^17.0.2", "react-redux": "^8.1.3", @@ -46613,6 +46670,7 @@ "version": "6.62.0", "license": "SSPL", "dependencies": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-collection": "^4.60.0", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-connections": "^1.61.0", @@ -46625,7 +46683,6 @@ "bson": "^6.10.3", "compass-preferences-model": "^2.41.0", "d3": "^3.5.17", - "hadron-app-registry": "^9.4.11", "hadron-document": "^8.8.12", "leaflet": "^1.5.1", "leaflet-defaulticon-compatibility": "^0.1.1", @@ -46672,6 +46729,7 @@ "version": "6.61.0", "license": "SSPL", "dependencies": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-app-stores": "^7.47.0", "@mongodb-js/compass-collection": "^4.60.0", "@mongodb-js/compass-components": "^1.39.0", @@ -46686,7 +46744,6 @@ "@mongodb-js/mongodb-constants": "^0.11.0", "bson": "^6.10.3", "compass-preferences-model": "^2.41.0", - "hadron-app-registry": "^9.4.11", "javascript-stringify": "^2.0.1", "lodash": "^4.17.21", "mongodb": "^6.16.0", @@ -46885,6 +46942,7 @@ "version": "16.60.0", "license": "SSPL", "dependencies": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-app-stores": "^7.47.0", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-connections": "^1.61.0", @@ -46893,7 +46951,6 @@ "d3": "^3.5.17", "d3-timer": "^1.0.3", "debug": "^4.3.4", - "hadron-app-registry": "^9.4.11", "lodash": "^4.17.21", "mongodb-ns": "^2.4.2", "prop-types": "^15.7.2", @@ -46934,11 +46991,11 @@ "license": "SSPL", "dependencies": { "@mongodb-js/atlas-service": "^0.46.0", + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-generative-ai": "^0.41.0", "@mongodb-js/compass-logging": "^1.7.2", "compass-preferences-model": "^2.41.0", - "hadron-app-registry": "^9.4.11", "hadron-ipc": "^3.5.2", "react": "^17.0.2", "react-redux": "^8.1.3", @@ -47000,6 +47057,7 @@ "version": "3.60.0", "license": "SSPL", "dependencies": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-editor": "^0.41.0", @@ -47013,7 +47071,6 @@ "@mongosh/node-runtime-worker-thread": "^3.3.10", "bson": "^6.10.3", "compass-preferences-model": "^2.41.0", - "hadron-app-registry": "^9.4.11", "react": "^17.0.2", "react-redux": "^8.1.3", "redux": "^4.2.1", @@ -47321,6 +47378,7 @@ "version": "5.61.0", "license": "SSPL", "dependencies": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-app-stores": "^7.47.0", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-connection-import-export": "^0.57.0", @@ -47332,7 +47390,6 @@ "@mongodb-js/compass-workspaces": "^0.42.0", "@mongodb-js/connection-info": "^0.15.2", "compass-preferences-model": "^2.41.0", - "hadron-app-registry": "^9.4.11", "lodash": "^4.17.21", "mongodb": "^6.16.0", "mongodb-instance-model": "^12.33.0", @@ -47484,8 +47541,8 @@ "version": "1.10.0", "license": "SSPL", "dependencies": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-logging": "^1.7.2", - "hadron-app-registry": "^9.4.11", "hadron-ipc": "^3.5.2", "react": "^17.0.2" }, @@ -47782,6 +47839,7 @@ "devDependencies": { "@mongodb-js/atlas-service": "^0.46.0", "@mongodb-js/compass-aggregations": "^9.63.0", + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-app-stores": "^7.47.0", "@mongodb-js/compass-collection": "^4.60.0", "@mongodb-js/compass-components": "^1.39.0", @@ -47831,7 +47889,6 @@ "events": "^3.3.0", "express": "^4.21.1", "express-http-proxy": "^2.0.0", - "hadron-app-registry": "^9.4.11", "is-ip": "^5.0.1", "lodash": "^4.17.21", "mocha": "^10.2.0", @@ -48037,13 +48094,13 @@ "version": "0.59.0", "license": "SSPL", "dependencies": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", "@mongodb-js/compass-workspaces": "^0.42.0", "compass-preferences-model": "^2.41.0", - "hadron-app-registry": "^9.4.11", "react": "^17.0.2", "redux": "^4.2.1", "redux-thunk": "^2.4.2" @@ -48101,13 +48158,13 @@ "version": "0.42.0", "license": "SSPL", "dependencies": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-app-stores": "^7.47.0", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-logging": "^1.7.2", "bson": "^6.10.3", "compass-preferences-model": "^2.41.0", - "hadron-app-registry": "^9.4.11", "lodash": "^4.17.21", "mongodb-collection-model": "^5.29.2", "mongodb-database-model": "^2.29.2", @@ -48642,6 +48699,7 @@ "version": "0.36.0", "license": "SSPL", "dependencies": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", "@mongodb-js/compass-user-data": "^0.7.2", @@ -48650,7 +48708,6 @@ "bson": "^6.10.3", "compass-preferences-model": "^2.41.0", "electron": "^36.4.0", - "hadron-app-registry": "^9.4.11", "hadron-ipc": "^3.5.2", "keytar": "^7.9.0", "lodash": "^4.17.21", @@ -48953,6 +49010,7 @@ "version": "1.60.0", "license": "SSPL", "dependencies": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-app-stores": "^7.47.0", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-connections": "^1.61.0", @@ -48963,7 +49021,6 @@ "@mongodb-js/databases-collections-list": "^1.58.0", "@mongodb-js/my-queries-storage": "^0.28.0", "compass-preferences-model": "^2.41.0", - "hadron-app-registry": "^9.4.11", "lodash": "^4.17.21", "mongodb-collection-model": "^5.29.2", "mongodb-database-model": "^2.29.2", @@ -49230,6 +49287,7 @@ }, "packages/hadron-app-registry": { "version": "9.4.11", + "extraneous": true, "license": "SSPL", "dependencies": { "eventemitter3": "^4.0.0", @@ -49254,33 +49312,6 @@ "typescript": "^5.0.4" } }, - "packages/hadron-app-registry/node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "packages/hadron-app-registry/node_modules/sinon": { - "version": "9.2.4", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.4.tgz", - "integrity": "sha512-zljcULZQsJxVra28qIAL6ow1Z9tpattkCTEJR4RBP3TGc00FcttsP5pK284Nas5WjMZU5Yzy3kAIp3B3KRf5Yg==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.8.1", - "@sinonjs/fake-timers": "^6.0.1", - "@sinonjs/samsam": "^5.3.1", - "diff": "^4.0.2", - "nise": "^4.0.4", - "supports-color": "^7.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/sinon" - } - }, "packages/hadron-build": { "version": "25.8.2", "hasInstallScript": true, @@ -50307,10 +50338,10 @@ "version": "0.28.0", "license": "SSPL", "dependencies": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-editor": "^0.41.0", "@mongodb-js/compass-user-data": "^0.7.2", "bson": "^6.10.3", - "hadron-app-registry": "^9.4.11", "react": "^17.0.2" }, "devDependencies": { @@ -56228,6 +56259,7 @@ "@mongodb-js/atlas-service": { "version": "file:packages/atlas-service", "requires": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", @@ -56249,7 +56281,6 @@ "compass-preferences-model": "^2.41.0", "depcheck": "^1.4.1", "electron": "^36.4.0", - "hadron-app-registry": "^9.4.11", "hadron-ipc": "^3.5.2", "lodash": "^4.17.21", "mocha": "^10.2.0", @@ -56293,6 +56324,7 @@ "@dnd-kit/sortable": "^7.0.2", "@dnd-kit/utilities": "^3.2.1", "@mongodb-js/atlas-service": "^0.46.0", + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-app-stores": "^7.47.0", "@mongodb-js/compass-collection": "^4.60.0", "@mongodb-js/compass-components": "^1.39.0", @@ -56322,7 +56354,6 @@ "compass-preferences-model": "^2.41.0", "depcheck": "^1.4.1", "electron-mocha": "^12.2.0", - "hadron-app-registry": "^9.4.11", "hadron-document": "^8.8.12", "hadron-type-checker": "^7.4.10", "lodash": "^4.17.21", @@ -56468,9 +56499,55 @@ } } }, + "@mongodb-js/compass-app-registry": { + "version": "file:packages/compass-app-registry", + "requires": { + "@mongodb-js/eslint-config-compass": "^1.3.10", + "@mongodb-js/mocha-config-compass": "^1.6.8", + "@mongodb-js/prettier-config-compass": "^1.2.8", + "@mongodb-js/testing-library-compass": "^1.3.2", + "@mongodb-js/tsconfig-compass": "^1.2.8", + "@types/chai": "^4.2.21", + "@types/mocha": "^9.0.0", + "@types/reflux": "^6.4.3", + "chai": "^4.1.2", + "depcheck": "^1.4.1", + "eventemitter3": "^4.0.0", + "mocha": "^10.2.0", + "react": "^17.0.2", + "react-redux": "^8.1.3", + "redux": "^4.2.1", + "reflux": "^0.4.1", + "sinon": "^9.0.0", + "typescript": "^5.0.4" + }, + "dependencies": { + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "sinon": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.4.tgz", + "integrity": "sha512-zljcULZQsJxVra28qIAL6ow1Z9tpattkCTEJR4RBP3TGc00FcttsP5pK284Nas5WjMZU5Yzy3kAIp3B3KRf5Yg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.8.1", + "@sinonjs/fake-timers": "^6.0.1", + "@sinonjs/samsam": "^5.3.1", + "diff": "^4.0.2", + "nise": "^4.0.4", + "supports-color": "^7.1.0" + } + } + } + }, "@mongodb-js/compass-app-stores": { "version": "file:packages/compass-app-stores", "requires": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-logging": "^1.7.2", @@ -56487,7 +56564,6 @@ "compass-preferences-model": "^2.41.0", "depcheck": "^1.4.1", "electron-mocha": "^12.2.0", - "hadron-app-registry": "^9.4.11", "mocha": "^10.2.0", "mongodb-collection-model": "^5.29.2", "mongodb-database-model": "^2.29.2", @@ -56525,6 +56601,7 @@ "@mongodb-js/compass-collection": { "version": "file:packages/compass-collection", "requires": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-app-stores": "^7.47.0", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-connections": "^1.61.0", @@ -56548,7 +56625,6 @@ "compass-preferences-model": "^2.41.0", "depcheck": "^1.4.1", "electron-mocha": "^12.2.0", - "hadron-app-registry": "^9.4.11", "mocha": "^10.2.0", "mongodb-collection-model": "^5.29.2", "mongodb-ns": "^2.4.2", @@ -56861,6 +56937,7 @@ "@mongodb-js/compass-connections": { "version": "file:packages/compass-connections", "requires": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", @@ -56885,7 +56962,6 @@ "compass-preferences-model": "^2.41.0", "depcheck": "^1.4.1", "electron-mocha": "^12.2.0", - "hadron-app-registry": "^9.4.11", "lodash": "^4.17.21", "mocha": "^10.2.0", "mongodb": "^6.16.0", @@ -56989,6 +57065,7 @@ "@mongodb-js/compass-crud": { "version": "file:packages/compass-crud", "requires": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-app-stores": "^7.47.0", "@mongodb-js/compass-collection": "^4.60.0", "@mongodb-js/compass-components": "^1.39.0", @@ -57021,7 +57098,6 @@ "electron": "^36.4.0", "electron-mocha": "^12.2.0", "enzyme": "^3.11.0", - "hadron-app-registry": "^9.4.11", "hadron-document": "^8.8.12", "hadron-type-checker": "^7.4.10", "jsondiffpatch": "^0.5.0", @@ -57072,6 +57148,7 @@ "@mongodb-js/compass-data-modeling": { "version": "file:packages/compass-data-modeling", "requires": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-app-stores": "^7.47.0", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-connections": "^1.61.0", @@ -57096,7 +57173,6 @@ "chai": "^4.3.6", "compass-preferences-model": "^2.41.0", "depcheck": "^1.4.1", - "hadron-app-registry": "^9.4.11", "lodash": "^4.17.21", "mocha": "^10.2.0", "mongodb": "^6.14.1", @@ -57318,6 +57394,7 @@ "@mongodb-js/compass-databases-collections": { "version": "file:packages/databases-collections", "requires": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-app-stores": "^7.47.0", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-connections": "^1.61.0", @@ -57337,7 +57414,6 @@ "compass-preferences-model": "^2.41.0", "depcheck": "^1.4.1", "enzyme": "^3.11.0", - "hadron-app-registry": "^9.4.11", "lodash": "^4.17.21", "mocha": "^10.2.0", "mongodb-collection-model": "^5.29.2", @@ -57486,6 +57562,7 @@ "@mongodb-js/compass-explain-plan": { "version": "file:packages/compass-explain-plan", "requires": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-collection": "^4.60.0", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-connections": "^1.61.0", @@ -57509,7 +57586,6 @@ "depcheck": "^1.4.1", "electron": "^36.4.0", "electron-mocha": "^12.2.0", - "hadron-app-registry": "^9.4.11", "lodash": "^4.17.21", "mocha": "^10.2.0", "mongodb": "^6.16.0", @@ -57555,6 +57631,7 @@ "@mongodb-js/compass-export-to-language": { "version": "file:packages/compass-export-to-language", "requires": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-collection": "^4.60.0", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-connections": "^1.61.0", @@ -57571,7 +57648,6 @@ "chai": "^4.3.6", "compass-preferences-model": "^2.41.0", "depcheck": "^1.4.1", - "hadron-app-registry": "^9.4.11", "mocha": "^10.2.0", "mongodb-ns": "^2.4.2", "nyc": "^15.1.0", @@ -57618,6 +57694,7 @@ "@mongodb-js/compass-field-store": { "version": "file:packages/compass-field-store", "requires": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/eslint-config-compass": "^1.3.10", @@ -57631,7 +57708,6 @@ "chai": "^4.3.6", "depcheck": "^1.4.1", "electron-mocha": "^12.2.0", - "hadron-app-registry": "^9.4.11", "lodash": "^4.17.21", "mocha": "^10.2.0", "mongodb-schema": "^12.6.2", @@ -57740,6 +57816,7 @@ "@mongodb-js/compass-find-in-page": { "version": "file:packages/compass-find-in-page", "requires": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/eslint-config-compass": "^1.3.10", "@mongodb-js/mocha-config-compass": "^1.6.8", @@ -57756,7 +57833,6 @@ "depcheck": "^1.4.1", "electron": "^36.4.0", "electron-mocha": "^12.2.0", - "hadron-app-registry": "^9.4.11", "hadron-ipc": "^3.5.2", "mocha": "^10.2.0", "nyc": "^15.1.0", @@ -57798,6 +57874,7 @@ "version": "file:packages/compass-generative-ai", "requires": { "@mongodb-js/atlas-service": "^0.46.0", + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-intercom": "^0.25.0", @@ -57820,7 +57897,6 @@ "compass-preferences-model": "^2.41.0", "depcheck": "^1.4.1", "electron-mocha": "^12.2.0", - "hadron-app-registry": "^9.4.11", "mocha": "^10.2.0", "mongodb": "^6.16.0", "mongodb-schema": "^12.6.2", @@ -57953,6 +58029,7 @@ "version": "file:packages/compass-global-writes", "requires": { "@mongodb-js/atlas-service": "^0.46.0", + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-collection": "^4.60.0", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-connections": "^1.61.0", @@ -57972,7 +58049,6 @@ "@types/sinon-chai": "^3.2.5", "chai": "^4.3.6", "depcheck": "^1.4.1", - "hadron-app-registry": "^9.4.11", "lodash": "^4.17.21", "mocha": "^10.2.0", "mongodb-ns": "^2.4.2", @@ -58068,6 +58144,7 @@ "version": "file:packages/compass-import-export", "requires": { "@electron/remote": "^2.1.2", + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-editor": "^0.41.0", @@ -58099,7 +58176,6 @@ "depcheck": "^1.4.1", "electron": "^36.4.0", "electron-mocha": "^12.2.0", - "hadron-app-registry": "^9.4.11", "hadron-document": "^8.8.12", "hadron-ipc": "^3.5.2", "lodash": "^4.17.21", @@ -58240,6 +58316,7 @@ "version": "file:packages/compass-indexes", "requires": { "@mongodb-js/atlas-service": "^0.46.0", + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-app-stores": "^7.47.0", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-connections": "^1.61.0", @@ -58262,7 +58339,6 @@ "depcheck": "^1.4.1", "electron": "^36.4.0", "electron-mocha": "^12.2.0", - "hadron-app-registry": "^9.4.11", "lodash": "^4.17.21", "mocha": "^10.2.0", "mongodb": "^6.16.0", @@ -58444,6 +58520,7 @@ "@mongodb-js/compass-logging": { "version": "file:packages/compass-logging", "requires": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/eslint-config-compass": "^1.3.10", "@mongodb-js/mocha-config-compass": "^1.6.8", "@mongodb-js/prettier-config-compass": "^1.2.8", @@ -58455,7 +58532,6 @@ "chai": "^4.3.4", "debug": "^4.3.4", "depcheck": "^1.4.1", - "hadron-app-registry": "^9.4.11", "hadron-ipc": "^3.5.2", "is-electron-renderer": "^2.0.1", "mocha": "^10.2.0", @@ -58539,6 +58615,7 @@ "version": "file:packages/compass-query-bar", "requires": { "@mongodb-js/atlas-service": "^0.46.0", + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-app-stores": "^7.47.0", "@mongodb-js/compass-collection": "^4.60.0", "@mongodb-js/compass-components": "^1.39.0", @@ -58561,7 +58638,6 @@ "depcheck": "^1.4.1", "electron": "^36.4.0", "electron-mocha": "^12.2.0", - "hadron-app-registry": "^9.4.11", "lodash": "^4.17.21", "mocha": "^10.2.0", "mongodb": "^6.16.0", @@ -58703,6 +58779,7 @@ "@mongodb-js/compass-saved-aggregations-queries": { "version": "file:packages/compass-saved-aggregations-queries", "requires": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-app-stores": "^7.47.0", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-connections": "^1.61.0", @@ -58729,7 +58806,6 @@ "depcheck": "^1.4.1", "electron-mocha": "^12.2.0", "fuse.js": "^6.5.3", - "hadron-app-registry": "^9.4.11", "mocha": "^10.2.0", "mongodb-ns": "^2.4.2", "nyc": "^15.1.0", @@ -58770,6 +58846,7 @@ "@mongodb-js/compass-schema": { "version": "file:packages/compass-schema", "requires": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-collection": "^4.60.0", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-connections": "^1.61.0", @@ -58797,7 +58874,6 @@ "d3": "^3.5.17", "depcheck": "^1.4.1", "electron-mocha": "^12.2.0", - "hadron-app-registry": "^9.4.11", "hadron-document": "^8.8.12", "leaflet": "^1.5.1", "leaflet-defaulticon-compatibility": "^0.1.1", @@ -58919,6 +58995,7 @@ "@mongodb-js/compass-schema-validation": { "version": "file:packages/compass-schema-validation", "requires": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-app-stores": "^7.47.0", "@mongodb-js/compass-collection": "^4.60.0", "@mongodb-js/compass-components": "^1.39.0", @@ -58942,7 +59019,6 @@ "depcheck": "^1.4.1", "electron": "^36.4.0", "electron-mocha": "^12.2.0", - "hadron-app-registry": "^9.4.11", "hadron-ipc": "^3.5.2", "javascript-stringify": "^2.0.1", "lodash": "^4.17.21", @@ -59150,6 +59226,7 @@ "@mongodb-js/compass-serverstats": { "version": "file:packages/compass-serverstats", "requires": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-app-stores": "^7.47.0", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-connections": "^1.61.0", @@ -59168,7 +59245,6 @@ "depcheck": "^1.4.1", "electron-mocha": "^12.2.0", "enzyme": "^3.11.0", - "hadron-app-registry": "^9.4.11", "lodash": "^4.17.21", "mocha": "^10.2.0", "mongodb-ns": "^2.4.2", @@ -59197,6 +59273,7 @@ "version": "file:packages/compass-settings", "requires": { "@mongodb-js/atlas-service": "^0.46.0", + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-generative-ai": "^0.41.0", "@mongodb-js/compass-logging": "^1.7.2", @@ -59215,7 +59292,6 @@ "compass-preferences-model": "^2.41.0", "depcheck": "^1.4.1", "electron-mocha": "^12.2.0", - "hadron-app-registry": "^9.4.11", "hadron-ipc": "^3.5.2", "mocha": "^10.2.0", "nyc": "^15.1.0", @@ -59256,6 +59332,7 @@ "@mongodb-js/compass-shell": { "version": "file:packages/compass-shell", "requires": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-editor": "^0.41.0", @@ -59278,7 +59355,6 @@ "depcheck": "^1.4.1", "electron": "^36.4.0", "electron-mocha": "^12.2.0", - "hadron-app-registry": "^9.4.11", "mocha": "^10.2.0", "nyc": "^15.1.0", "react": "^17.0.2", @@ -59493,6 +59569,7 @@ "@mongodb-js/compass-sidebar": { "version": "file:packages/compass-sidebar", "requires": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-app-stores": "^7.47.0", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-connection-import-export": "^0.57.0", @@ -59518,7 +59595,6 @@ "compass-preferences-model": "^2.41.0", "depcheck": "^1.4.1", "electron-mocha": "^12.2.0", - "hadron-app-registry": "^9.4.11", "lodash": "^4.17.21", "mocha": "^10.2.0", "mongodb": "^6.16.0", @@ -59629,6 +59705,7 @@ "@mongodb-js/compass-telemetry": { "version": "file:packages/compass-telemetry", "requires": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/eslint-config-compass": "^1.3.10", "@mongodb-js/mocha-config-compass": "^1.6.8", @@ -59640,7 +59717,6 @@ "chai": "^4.3.6", "depcheck": "^1.4.1", "gen-esm-wrapper": "^1.1.0", - "hadron-app-registry": "^9.4.11", "hadron-ipc": "^3.5.2", "mocha": "^10.2.0", "nyc": "^15.1.0", @@ -59885,6 +59961,7 @@ "requires": { "@mongodb-js/atlas-service": "^0.46.0", "@mongodb-js/compass-aggregations": "^9.63.0", + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-app-stores": "^7.47.0", "@mongodb-js/compass-collection": "^4.60.0", "@mongodb-js/compass-components": "^1.39.0", @@ -59934,7 +60011,6 @@ "events": "^3.3.0", "express": "^4.21.1", "express-http-proxy": "^2.0.0", - "hadron-app-registry": "^9.4.11", "is-ip": "^5.0.1", "lodash": "^4.17.21", "mocha": "^10.2.0", @@ -60109,6 +60185,7 @@ "@mongodb-js/compass-welcome": { "version": "file:packages/compass-welcome", "requires": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-logging": "^1.7.2", @@ -60128,7 +60205,6 @@ "compass-preferences-model": "^2.41.0", "depcheck": "^1.4.1", "electron-mocha": "^12.2.0", - "hadron-app-registry": "^9.4.11", "mocha": "^10.2.0", "nyc": "^15.1.0", "react": "^17.0.2", @@ -60166,6 +60242,7 @@ "@mongodb-js/compass-workspaces": { "version": "file:packages/compass-workspaces", "requires": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-app-stores": "^7.47.0", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-connections": "^1.61.0", @@ -60186,7 +60263,6 @@ "compass-preferences-model": "^2.41.0", "depcheck": "^1.4.1", "electron-mocha": "^12.2.0", - "hadron-app-registry": "^9.4.11", "lodash": "^4.17.21", "mocha": "^10.2.0", "mongodb-collection-model": "^5.29.2", @@ -60507,6 +60583,7 @@ "@mongodb-js/connection-storage": { "version": "file:packages/connection-storage", "requires": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", "@mongodb-js/compass-user-data": "^0.7.2", @@ -60524,7 +60601,6 @@ "compass-preferences-model": "^2.41.0", "depcheck": "^1.4.1", "electron": "^36.4.0", - "hadron-app-registry": "^9.4.11", "hadron-ipc": "^3.5.2", "keytar": "^7.9.0", "lodash": "^4.17.21", @@ -61193,6 +61269,7 @@ "@mongodb-js/my-queries-storage": { "version": "file:packages/my-queries-storage", "requires": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-editor": "^0.41.0", "@mongodb-js/compass-user-data": "^0.7.2", "@mongodb-js/eslint-config-compass": "^1.3.10", @@ -61206,7 +61283,6 @@ "chai": "^4.3.6", "depcheck": "^1.4.1", "gen-esm-wrapper": "^1.1.0", - "hadron-app-registry": "^9.4.11", "mocha": "^10.2.0", "nyc": "^15.1.0", "react": "^17.0.2", @@ -69104,6 +69180,7 @@ "compass-preferences-model": { "version": "file:packages/compass-preferences-model", "requires": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-user-data": "^0.7.2", @@ -69117,7 +69194,6 @@ "bson": "^6.10.3", "chai": "^4.3.6", "depcheck": "^1.4.1", - "hadron-app-registry": "^9.4.11", "hadron-ipc": "^3.5.2", "js-yaml": "^4.1.0", "lodash": "^4.17.21", @@ -74286,51 +74362,6 @@ "duplexer": "^0.1.2" } }, - "hadron-app-registry": { - "version": "file:packages/hadron-app-registry", - "requires": { - "@mongodb-js/eslint-config-compass": "^1.3.10", - "@mongodb-js/mocha-config-compass": "^1.6.8", - "@mongodb-js/prettier-config-compass": "^1.2.8", - "@mongodb-js/testing-library-compass": "^1.3.2", - "@mongodb-js/tsconfig-compass": "^1.2.8", - "@types/chai": "^4.2.21", - "@types/mocha": "^9.0.0", - "@types/reflux": "^6.4.3", - "chai": "^4.1.2", - "depcheck": "^1.4.1", - "eventemitter3": "^4.0.0", - "mocha": "^10.2.0", - "react": "^17.0.2", - "react-redux": "^8.1.3", - "redux": "^4.2.1", - "reflux": "^0.4.1", - "sinon": "^9.0.0", - "typescript": "^5.0.4" - }, - "dependencies": { - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - }, - "sinon": { - "version": "9.2.4", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.4.tgz", - "integrity": "sha512-zljcULZQsJxVra28qIAL6ow1Z9tpattkCTEJR4RBP3TGc00FcttsP5pK284Nas5WjMZU5Yzy3kAIp3B3KRf5Yg==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.8.1", - "@sinonjs/fake-timers": "^6.0.1", - "@sinonjs/samsam": "^5.3.1", - "diff": "^4.0.2", - "nise": "^4.0.4", - "supports-color": "^7.1.0" - } - } - } - }, "hadron-build": { "version": "file:packages/hadron-build", "requires": { @@ -79537,6 +79568,7 @@ "@electron/remote": "^2.1.2", "@mongodb-js/atlas-service": "^0.46.0", "@mongodb-js/compass-aggregations": "^9.63.0", + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-app-stores": "^7.47.0", "@mongodb-js/compass-collection": "^4.60.0", "@mongodb-js/compass-components": "^1.39.0", @@ -79600,7 +79632,6 @@ "electron-mocha": "^12.2.0", "ensure-error": "^3.0.1", "glob": "^10.2.5", - "hadron-app-registry": "^9.4.11", "hadron-build": "^25.8.2", "hadron-ipc": "^3.5.2", "kerberos": "^2.2.1", diff --git a/packages/atlas-service/package.json b/packages/atlas-service/package.json index 7e2035391ed..9f39fb05b59 100644 --- a/packages/atlas-service/package.json +++ b/packages/atlas-service/package.json @@ -71,6 +71,7 @@ "typescript": "^5.0.4" }, "dependencies": { + "@mongodb-js/compass-app-registry": "^9.4.11", "@mongodb-js/compass-components": "^1.39.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", @@ -80,7 +81,6 @@ "@mongodb-js/devtools-connect": "^3.7.2", "@mongodb-js/devtools-proxy-support": "^0.4.4", "@mongodb-js/oidc-plugin": "^1.1.7", - "hadron-app-registry": "^9.4.11", "compass-preferences-model": "^2.41.0", "electron": "^36.4.0", "hadron-ipc": "^3.5.2", diff --git a/packages/atlas-service/src/provider.tsx b/packages/atlas-service/src/provider.tsx index 92ec08bfac8..c4786939a91 100644 --- a/packages/atlas-service/src/provider.tsx +++ b/packages/atlas-service/src/provider.tsx @@ -6,7 +6,7 @@ import { useLogger } from '@mongodb-js/compass-logging/provider'; import { createServiceLocator, createServiceProvider, -} from 'hadron-app-registry'; +} from '@mongodb-js/compass-app-registry'; const AtlasAuthServiceContext = createContext(null); diff --git a/packages/atlas-service/src/renderer.ts b/packages/atlas-service/src/renderer.ts index f69e1ce5225..351168ad3a5 100644 --- a/packages/atlas-service/src/renderer.ts +++ b/packages/atlas-service/src/renderer.ts @@ -1,8 +1,8 @@ -import { registerHadronPlugin } from 'hadron-app-registry'; +import { registerCompassPlugin } from '@mongodb-js/compass-app-registry'; import { activatePlugin } from './store/atlas-signin-store'; import { atlasAuthServiceLocator } from './provider'; -export const AtlasAuthPlugin = registerHadronPlugin( +export const AtlasAuthPlugin = registerCompassPlugin( { name: 'AtlasAuth', component: () => null, diff --git a/packages/atlas-service/src/store/atlas-signin-store.spec.ts b/packages/atlas-service/src/store/atlas-signin-store.spec.ts index da0e727c095..9e85859043a 100644 --- a/packages/atlas-service/src/store/atlas-signin-store.spec.ts +++ b/packages/atlas-service/src/store/atlas-signin-store.spec.ts @@ -1,7 +1,7 @@ import { expect } from 'chai'; import type { AtlasAuthPluginServices } from './atlas-signin-store'; import { activatePlugin } from './atlas-signin-store'; -import type { ActivateHelpers } from 'hadron-app-registry'; +import type { ActivateHelpers } from '@mongodb-js/compass-app-registry'; import { waitFor } from '@mongodb-js/testing-library-compass'; const activateHelpers = { diff --git a/packages/atlas-service/src/store/atlas-signin-store.ts b/packages/atlas-service/src/store/atlas-signin-store.ts index 3d7692caea4..1807cb1ce8a 100644 --- a/packages/atlas-service/src/store/atlas-signin-store.ts +++ b/packages/atlas-service/src/store/atlas-signin-store.ts @@ -7,7 +7,7 @@ import reducer, { } from './atlas-signin-reducer'; import { type AtlasAuthService } from '../provider'; import { ipcRenderer } from 'hadron-ipc'; -import type { ActivateHelpers } from 'hadron-app-registry'; +import type { ActivateHelpers } from '@mongodb-js/compass-app-registry'; let store: AtlasServiceStore; export function getStore() { diff --git a/packages/compass-aggregations/README.md b/packages/compass-aggregations/README.md index 3219943f4eb..6757cdea936 100644 --- a/packages/compass-aggregations/README.md +++ b/packages/compass-aggregations/README.md @@ -68,7 +68,7 @@ This is for: Setting values via configure: ```js -import AppRegistry from 'hadron-app-registry'; +import AppRegistry from '@mongodb-js/compass-app-registry'; import AggregationsPlugin, { configureStore as configureAggregationsStore } from '@mongodb-js/compass-aggregations'; @@ -108,7 +108,7 @@ const exportToLanguageStore = configureExportToLanguageStore({ ``` -### Hadron/Electron +### Compass/Electron ```js const role = appRegistry.getRole('Collection.Tab')[0]; @@ -167,7 +167,7 @@ provider.aggregate(namespace, pipeline, options, callback); ### App Registry Events Emmitted Various actions within this plugin will emit events for other parts of the -application can be listened to via [hadron-app-registry][hadron-app-registry]. +application can be listened to via [compass-app-registry][compass-app-registry]. `Local` events are scoped to a `Tab`. `Global` events are scoped to the whole Compass application. @@ -250,4 +250,4 @@ npm run analyze npm i -S @mongodb-js/compass-aggregations ``` -[hadron-app-registry]: https://github.com/mongodb-js/hadron-app-registry +[compass-app-registry]: https://github.com/mongodb-js/compass/tree/main/packages/compass-app-registry diff --git a/packages/compass-aggregations/package.json b/packages/compass-aggregations/package.json index f95c257e64e..1aeb06538ec 100644 --- a/packages/compass-aggregations/package.json +++ b/packages/compass-aggregations/package.json @@ -76,7 +76,7 @@ "@mongodb-js/shell-bson-parser": "^1.2.0", "bson": "^6.10.3", "compass-preferences-model": "^2.41.0", - "hadron-app-registry": "^9.4.11", + "@mongodb-js/compass-app-registry": "^9.4.11", "hadron-document": "^8.8.12", "hadron-type-checker": "^7.4.10", "lodash": "^4.17.21", diff --git a/packages/compass-aggregations/src/index.ts b/packages/compass-aggregations/src/index.ts index bf9a7c4d3f0..0a8a49e1e39 100644 --- a/packages/compass-aggregations/src/index.ts +++ b/packages/compass-aggregations/src/index.ts @@ -1,5 +1,5 @@ import React from 'react'; -import { registerHadronPlugin } from 'hadron-app-registry'; +import { registerCompassPlugin } from '@mongodb-js/compass-app-registry'; import { AggregationsPlugin } from './plugin'; import { activateAggregationsPlugin } from './stores/store'; import { Aggregations } from './components/aggregations'; @@ -29,7 +29,7 @@ import { atlasAiServiceLocator } from '@mongodb-js/compass-generative-ai/provide import { pipelineStorageLocator } from '@mongodb-js/my-queries-storage/provider'; import { AggregationsTabTitle } from './plugin-title'; -const CompassAggregationsHadronPlugin = registerHadronPlugin( +const CompassAggregationsPluginProvider = registerCompassPlugin( { name: 'CompassAggregations', component: function AggregationsProvider({ children }) { @@ -58,12 +58,12 @@ const CompassAggregationsHadronPlugin = registerHadronPlugin( export const CompassAggregationsPlugin = { name: 'Aggregations' as const, - provider: CompassAggregationsHadronPlugin, + provider: CompassAggregationsPluginProvider, content: AggregationsPlugin, header: AggregationsTabTitle, }; -export const CreateViewPlugin = registerHadronPlugin( +export const CreateViewPlugin = registerCompassPlugin( { name: 'CreateView', component: CreateViewModal, diff --git a/packages/compass-aggregations/src/modules/index.ts b/packages/compass-aggregations/src/modules/index.ts index 1f5de34002b..21173f150b2 100644 --- a/packages/compass-aggregations/src/modules/index.ts +++ b/packages/compass-aggregations/src/modules/index.ts @@ -40,7 +40,7 @@ import searchIndexes from './search-indexes'; import type { WorkspacesService } from '@mongodb-js/compass-workspaces/provider'; import type { PreferencesAccess } from 'compass-preferences-model'; import type { Logger } from '@mongodb-js/compass-logging/provider'; -import type AppRegistry from 'hadron-app-registry'; +import type AppRegistry from '@mongodb-js/compass-app-registry'; import type { AtlasAiService } from '@mongodb-js/compass-generative-ai/provider'; import type { MongoDBInstance } from 'mongodb-instance-model'; import type { DataService } from '../modules/data-service'; diff --git a/packages/compass-aggregations/src/modules/pipeline-builder/stage-editor.spec.ts b/packages/compass-aggregations/src/modules/pipeline-builder/stage-editor.spec.ts index b85472af616..b2c032fca55 100644 --- a/packages/compass-aggregations/src/modules/pipeline-builder/stage-editor.spec.ts +++ b/packages/compass-aggregations/src/modules/pipeline-builder/stage-editor.spec.ts @@ -29,7 +29,7 @@ import { getId } from './stage-ids'; import { defaultPreferencesInstance } from 'compass-preferences-model'; import { createNoopLogger } from '@mongodb-js/compass-logging/provider'; import { createNoopTrack } from '@mongodb-js/compass-telemetry/provider'; -import AppRegistry from 'hadron-app-registry'; +import AppRegistry from '@mongodb-js/compass-app-registry'; import { ConnectionScopedAppRegistryImpl } from '@mongodb-js/compass-connections/provider'; import { createDefaultConnectionInfo } from '@mongodb-js/testing-library-compass'; diff --git a/packages/compass-aggregations/src/modules/update-view.spec.ts b/packages/compass-aggregations/src/modules/update-view.spec.ts index 9c016fcf712..4e0c6603d4f 100644 --- a/packages/compass-aggregations/src/modules/update-view.spec.ts +++ b/packages/compass-aggregations/src/modules/update-view.spec.ts @@ -3,7 +3,7 @@ import { expect } from 'chai'; import { ERROR_UPDATING_VIEW, updateView } from './update-view'; import { createNoopLogger } from '@mongodb-js/compass-logging/provider'; import { createNoopTrack } from '@mongodb-js/compass-telemetry/provider'; -import AppRegistry from 'hadron-app-registry'; +import AppRegistry from '@mongodb-js/compass-app-registry'; import { type ConnectionInfoRef, ConnectionScopedAppRegistryImpl, diff --git a/packages/compass-aggregations/src/stores/create-view.spec.ts b/packages/compass-aggregations/src/stores/create-view.spec.ts index 9b39f7a7d8d..e7f456dd1bc 100644 --- a/packages/compass-aggregations/src/stores/create-view.spec.ts +++ b/packages/compass-aggregations/src/stores/create-view.spec.ts @@ -1,4 +1,4 @@ -import type AppRegistry from 'hadron-app-registry'; +import type AppRegistry from '@mongodb-js/compass-app-registry'; import { expect } from 'chai'; import { changeViewName, createView } from '../modules/create-view'; import Sinon from 'sinon'; diff --git a/packages/compass-aggregations/src/stores/create-view.ts b/packages/compass-aggregations/src/stores/create-view.ts index 684339c52b1..767b9cf042a 100644 --- a/packages/compass-aggregations/src/stores/create-view.ts +++ b/packages/compass-aggregations/src/stores/create-view.ts @@ -4,11 +4,11 @@ import type { ThunkAction } from 'redux-thunk'; import thunk from 'redux-thunk'; import type { CreateViewAction } from '../modules/create-view'; import reducer, { open } from '../modules/create-view'; -import type AppRegistry from 'hadron-app-registry'; +import type AppRegistry from '@mongodb-js/compass-app-registry'; import type { Logger } from '@mongodb-js/compass-logging/provider'; import type { WorkspacesService } from '@mongodb-js/compass-workspaces/provider'; import type { ConnectionsService } from '@mongodb-js/compass-connections/provider'; -import type { ActivateHelpers } from 'hadron-app-registry'; +import type { ActivateHelpers } from '@mongodb-js/compass-app-registry'; import type { TrackFunction } from '@mongodb-js/compass-telemetry'; type CreateViewServices = { diff --git a/packages/compass-aggregations/src/stores/store.spec.ts b/packages/compass-aggregations/src/stores/store.spec.ts index b2724ab3d9b..e08e485d6ac 100644 --- a/packages/compass-aggregations/src/stores/store.spec.ts +++ b/packages/compass-aggregations/src/stores/store.spec.ts @@ -1,4 +1,4 @@ -import type AppRegistry from 'hadron-app-registry'; +import type AppRegistry from '@mongodb-js/compass-app-registry'; import rootReducer from '../modules'; import { expect } from 'chai'; import configureStore from '../../test/configure-store'; diff --git a/packages/compass-aggregations/src/stores/store.ts b/packages/compass-aggregations/src/stores/store.ts index cbefaaa7dae..6111aa30702 100644 --- a/packages/compass-aggregations/src/stores/store.ts +++ b/packages/compass-aggregations/src/stores/store.ts @@ -15,7 +15,7 @@ import { mapStoreStagesToStageIdAndType, } from '../modules/pipeline-builder/stage-editor'; import { updatePipelinePreview } from '../modules/pipeline-builder/builder-helpers'; -import type AppRegistry from 'hadron-app-registry'; +import type AppRegistry from '@mongodb-js/compass-app-registry'; import type { ENVS } from '@mongodb-js/mongodb-constants'; import { setCollectionFields, @@ -26,7 +26,7 @@ import { INITIAL_STATE as SEARCH_INDEXES_INITIAL_STATE } from '../modules/search import { INITIAL_PANEL_OPEN_LOCAL_STORAGE_KEY } from '../modules/side-panel'; import type { DataService } from '../modules/data-service'; import type { WorkspacesService } from '@mongodb-js/compass-workspaces/provider'; -import type { ActivateHelpers } from 'hadron-app-registry'; +import type { ActivateHelpers } from '@mongodb-js/compass-app-registry'; import type { MongoDBInstance } from 'mongodb-instance-model'; import type Database from 'mongodb-database-model'; import type { CollectionTabPluginMetadata } from '@mongodb-js/compass-collection'; diff --git a/packages/hadron-app-registry/.depcheckrc b/packages/compass-app-registry/.depcheckrc similarity index 100% rename from packages/hadron-app-registry/.depcheckrc rename to packages/compass-app-registry/.depcheckrc diff --git a/packages/hadron-app-registry/.eslintignore b/packages/compass-app-registry/.eslintignore similarity index 100% rename from packages/hadron-app-registry/.eslintignore rename to packages/compass-app-registry/.eslintignore diff --git a/packages/hadron-app-registry/.eslintrc.js b/packages/compass-app-registry/.eslintrc.js similarity index 100% rename from packages/hadron-app-registry/.eslintrc.js rename to packages/compass-app-registry/.eslintrc.js diff --git a/packages/hadron-app-registry/.gitignore b/packages/compass-app-registry/.gitignore similarity index 100% rename from packages/hadron-app-registry/.gitignore rename to packages/compass-app-registry/.gitignore diff --git a/packages/hadron-app-registry/.mocharc.js b/packages/compass-app-registry/.mocharc.js similarity index 100% rename from packages/hadron-app-registry/.mocharc.js rename to packages/compass-app-registry/.mocharc.js diff --git a/packages/hadron-app-registry/.npmignore b/packages/compass-app-registry/.npmignore similarity index 100% rename from packages/hadron-app-registry/.npmignore rename to packages/compass-app-registry/.npmignore diff --git a/packages/hadron-app-registry/README.md b/packages/compass-app-registry/README.md similarity index 95% rename from packages/hadron-app-registry/README.md rename to packages/compass-app-registry/README.md index c28398aa4dd..6b74d0c3162 100644 --- a/packages/hadron-app-registry/README.md +++ b/packages/compass-app-registry/README.md @@ -1,4 +1,4 @@ -# hadron-app-registry +# @mongodb-js/compass-app-registry ## Concepts @@ -54,15 +54,15 @@ import { globalAppRegistry, AppRegistry, AppRegistryProvider, - registerHadronPlugin, -} from 'hadron-app-registry'; + registerCompassPlugin, +} from '@mongodb-js/compass-app-registry'; import CompassLogging from '@mongodb-js/compass-logging'; import { LoggingProvider, loggingLocator, } from '@mongodb-js/compass-logging/provider'; -const PluginWithLogger = registerHadronPlugin( +const PluginWithLogger = registerCompassPlugin( { name: 'LoggingPlugin', component: function () { @@ -93,7 +93,7 @@ intended to use. Typically, these functions are implemented using React contexts. ```typescript -import { createServiceLocator } from 'hadron-app-registry'; +import { createServiceLocator } from '@mongodb-js/compass-app-registry'; const ConnectionStorageContext = createContext(null); @@ -133,7 +133,7 @@ associated with it is destroyed). In order to make this easier, helpers are provided that automatically register cleanup functions: ```js -const Plugin = registerHadronPlugin({ +const Plugin = registerCompassPlugin({ name: 'TestPlugin', component: TestPluginComponent, activate(props, services, { on, addCleanup, cleanup }) { diff --git a/packages/hadron-app-registry/package.json b/packages/compass-app-registry/package.json similarity index 92% rename from packages/hadron-app-registry/package.json rename to packages/compass-app-registry/package.json index 4d7fe53356d..99c1df4a38e 100644 --- a/packages/hadron-app-registry/package.json +++ b/packages/compass-app-registry/package.json @@ -1,7 +1,10 @@ { - "name": "hadron-app-registry", - "description": "Hadron App Registry", - "author": "Durran Jordan ", + "name": "@mongodb-js/compass-app-registry", + "description": "Compass App Registry", + "author": { + "name": "MongoDB Inc", + "email": "compass@mongodb.com" + }, "bugs": { "url": "https://jira.mongodb.org/projects/COMPASS/issues", "email": "compass@mongodb.com" diff --git a/packages/hadron-app-registry/src/app-registry.spec.ts b/packages/compass-app-registry/src/app-registry.spec.ts similarity index 100% rename from packages/hadron-app-registry/src/app-registry.spec.ts rename to packages/compass-app-registry/src/app-registry.spec.ts diff --git a/packages/hadron-app-registry/src/app-registry.ts b/packages/compass-app-registry/src/app-registry.ts similarity index 100% rename from packages/hadron-app-registry/src/app-registry.ts rename to packages/compass-app-registry/src/app-registry.ts diff --git a/packages/hadron-app-registry/src/index.ts b/packages/compass-app-registry/src/index.ts similarity index 76% rename from packages/hadron-app-registry/src/index.ts rename to packages/compass-app-registry/src/index.ts index 3dbc0835869..0a2259e67b6 100644 --- a/packages/hadron-app-registry/src/index.ts +++ b/packages/compass-app-registry/src/index.ts @@ -7,15 +7,15 @@ export { GlobalAppRegistryProvider, } from './react-context'; export type { - HadronPluginComponent, - HadronPluginConfig, + CompassPluginComponent, + CompassPluginConfig, ActivateHelpers, } from './register-plugin'; export { - registerHadronPlugin, + registerCompassPlugin, createActivateHelpers, createServiceLocator, createServiceProvider, } from './register-plugin'; -export type { Plugin as HadronPlugin } from './app-registry'; +export type { Plugin as CompassPlugin } from './app-registry'; export default AppRegistry; diff --git a/packages/hadron-app-registry/src/react-context.tsx b/packages/compass-app-registry/src/react-context.tsx similarity index 100% rename from packages/hadron-app-registry/src/react-context.tsx rename to packages/compass-app-registry/src/react-context.tsx diff --git a/packages/hadron-app-registry/src/register-plugin.spec.tsx b/packages/compass-app-registry/src/register-plugin.spec.tsx similarity index 95% rename from packages/hadron-app-registry/src/register-plugin.spec.tsx rename to packages/compass-app-registry/src/register-plugin.spec.tsx index 94a2a34a978..17beaf75718 100644 --- a/packages/hadron-app-registry/src/register-plugin.spec.tsx +++ b/packages/compass-app-registry/src/register-plugin.spec.tsx @@ -4,7 +4,7 @@ import { expect } from 'chai'; import sinon from 'sinon'; import { AppRegistryProvider, - registerHadronPlugin, + registerCompassPlugin, createActivateHelpers, createServiceLocator, } from './'; @@ -12,13 +12,13 @@ import { createStore } from 'redux'; import { connect } from 'react-redux'; import { EventEmitter } from 'events'; -describe('registerHadronPlugin', function () { +describe('registerCompassPlugin', function () { afterEach(cleanup); it('allows registering plugins with a reflux-ish store', function () { const component = sinon.stub().callsFake(() => <>); const activate = sinon.stub().returns({ store: { state: { foo: 'bar' } } }); - const Plugin = registerHadronPlugin({ + const Plugin = registerCompassPlugin({ name: 'refluxish', component, activate, @@ -44,7 +44,7 @@ describe('registerHadronPlugin', function () { const component = sinon.stub().callsFake(() => <>); const store = { state: { foo: 'bar' } }; const activate = sinon.stub().returns({ store }); - const Plugin = registerHadronPlugin({ + const Plugin = registerCompassPlugin({ name: 'reflux', component, activate, @@ -77,7 +77,7 @@ describe('registerHadronPlugin', function () { } ); const activate = sinon.stub().returns({ store }); - const Plugin = registerHadronPlugin({ + const Plugin = registerCompassPlugin({ name: 'redux', component: connector(component), activate, @@ -112,7 +112,7 @@ describe('registerHadronPlugin', function () { const component = sinon.stub().callsFake(() => <>); const store = createStore(() => ({})); const activate = sinon.stub().returns({ store }); - const Plugin = registerHadronPlugin( + const Plugin = registerCompassPlugin( { name: 'service1', component: connector(component), diff --git a/packages/hadron-app-registry/src/register-plugin.tsx b/packages/compass-app-registry/src/register-plugin.tsx similarity index 93% rename from packages/hadron-app-registry/src/register-plugin.tsx rename to packages/compass-app-registry/src/register-plugin.tsx index 5810b768769..66b329948ba 100644 --- a/packages/hadron-app-registry/src/register-plugin.tsx +++ b/packages/compass-app-registry/src/register-plugin.tsx @@ -127,7 +127,7 @@ type Services unknown>> = { [SvcName in keyof S]: ReturnType; }; -export type HadronPluginConfig< +export type CompassPluginConfig< T, S extends Record unknown>, A extends Plugin @@ -188,7 +188,7 @@ export function createServiceLocator< if (!serviceLocationInProgress) { throw new Error( `Using service locator function "${name}" outside of the service location lifecycle. ` + - `Make sure that service locator function is passed as a second argument to the registerHadronPlugin method and is not used directly in a React render method.` + `Make sure that service locator function is passed as a second argument to the registerCompassPlugin method and is not used directly in a React render method.` ); } return fn.call(this, ...args); @@ -202,7 +202,7 @@ export function createServiceLocator< * need access to other service locators to facilitate service injections. In * these cases service provider can be wrapped with the createServiceProvider * function to allow usage of serviceLocator functions in providers outside of - * the usual hadron plugin "activate" lifecycle. + * the usual compass plugin "activate" lifecycle. */ export function createServiceProvider>( fn: T @@ -226,12 +226,12 @@ function isServiceLocator(val: any): boolean { return Object.prototype.hasOwnProperty.call(val, kLocator); } -function useHadronPluginActivate< +function useCompassPluginActivate< T, S extends Record unknown>, A extends Plugin >( - config: HadronPluginConfig, + config: CompassPluginConfig, services: S | undefined, props: T, mockOptions?: MockOptions @@ -306,7 +306,7 @@ function useHadronPluginActivate< return { store, actions, context }; } -export type HadronPluginComponent< +export type CompassPluginComponent< T, S extends Record unknown>, A extends Plugin @@ -320,7 +320,7 @@ export type HadronPluginComponent< * first render in their lifecycle * * @example - * const Plugin = registerHadronPlugin(...); + * const Plugin = registerCompassPlugin(...); * * function Component() { * Plugin.useActivate(); @@ -338,7 +338,7 @@ export type HadronPluginComponent< * registries available in the plugin context * * @example - * const PluginWithLogger = registerHadronPlugin({ ... }, { logger: loggerLocator }); + * const PluginWithLogger = registerCompassPlugin({ ... }, { logger: loggerLocator }); * * const MockPlugin = PluginWithLogger.withMockServices({ logger: Sinon.stub() }); * @@ -351,20 +351,20 @@ export type HadronPluginComponent< withMockServices( mocks: Partial>, options?: Partial> - ): HadronPluginComponent; + ): CompassPluginComponent; }; /** - * Creates a hadron plugin that will be automatically activated on first render + * Creates a compass plugin that will be automatically activated on first render * and cleaned up when localAppRegistry unmounts * - * @param config Hadron plugin configuration + * @param config Compass plugin configuration * @param services Map of service locator functions that plugin depends on * - * @returns Hadron plugin component + * @returns Compass plugin component * * @example - * const CreateCollectionPlugin = registerHadronPlugin({ + * const CreateCollectionPlugin = registerCompassPlugin({ * name: 'CreateCollection', * component: CreateCollectionModal, * activate(opts, { globalAppRegistry }) { @@ -399,7 +399,7 @@ export type HadronPluginComponent< * // plugin.js * import { logging } from '@mongodb-js/compass-logging/provider' * - * const PluginWithLogger = registerHadronPlugin({ + * const PluginWithLogger = registerCompassPlugin({ * name: 'LoggingPlugin', * component: () => null, * activate(opts, { logging }) { @@ -407,14 +407,14 @@ export type HadronPluginComponent< * } * }, { logging }) */ -export function registerHadronPlugin< +export function registerCompassPlugin< T, S extends Record unknown>, A extends Plugin >( - config: HadronPluginConfig, + config: CompassPluginConfig, services?: S -): HadronPluginComponent { +): CompassPluginComponent { const Component = config.component; const Plugin = (props: React.PropsWithChildren) => { const isMockedEnvironment = useMockOption('mockedEnvironment'); @@ -437,7 +437,7 @@ export function registerHadronPlugin< // thinks so: values returned by `useMock*` hooks are constant in React // runtime // eslint-disable-next-line react-hooks/rules-of-hooks - const { store, actions, context } = useHadronPluginActivate( + const { store, actions, context } = useCompassPluginActivate( config, services, props @@ -460,12 +460,12 @@ export function registerHadronPlugin< return Object.assign(Plugin, { displayName: config.name, useActivate: (props: T): A => { - return useHadronPluginActivate(config, services, props) as A; + return useCompassPluginActivate(config, services, props) as A; }, withMockServices( mocks: Partial> = {}, options?: Partial> - ): HadronPluginComponent { + ): CompassPluginComponent { const { // In case globalAppRegistry mock is not provided, we use the one // created in scope so that plugins don't leak their events and @@ -513,7 +513,7 @@ export function registerHadronPlugin< return Object.assign(MockPluginWithContext, { displayName: config.name, useActivate: (props: T): A => { - return useHadronPluginActivate( + return useCompassPluginActivate( config, services, props, diff --git a/packages/hadron-app-registry/tsconfig-lint.json b/packages/compass-app-registry/tsconfig-lint.json similarity index 100% rename from packages/hadron-app-registry/tsconfig-lint.json rename to packages/compass-app-registry/tsconfig-lint.json diff --git a/packages/hadron-app-registry/tsconfig.json b/packages/compass-app-registry/tsconfig.json similarity index 100% rename from packages/hadron-app-registry/tsconfig.json rename to packages/compass-app-registry/tsconfig.json diff --git a/packages/compass-app-stores/package.json b/packages/compass-app-stores/package.json index 667312b2c3c..643729b266d 100644 --- a/packages/compass-app-stores/package.json +++ b/packages/compass-app-stores/package.json @@ -75,7 +75,7 @@ "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/connection-info": "^0.15.2", - "hadron-app-registry": "^9.4.11", + "@mongodb-js/compass-app-registry": "^9.4.11", "mongodb-collection-model": "^5.29.2", "mongodb-database-model": "^2.29.2", "mongodb-instance-model": "^12.33.0", diff --git a/packages/compass-app-stores/src/plugin.tsx b/packages/compass-app-stores/src/plugin.tsx index 2a8b12d599a..e2ef6552d43 100644 --- a/packages/compass-app-stores/src/plugin.tsx +++ b/packages/compass-app-stores/src/plugin.tsx @@ -1,9 +1,9 @@ import React from 'react'; import type { Logger } from '@mongodb-js/compass-logging/provider'; import { createLoggerLocator } from '@mongodb-js/compass-logging/provider'; -import type AppRegistry from 'hadron-app-registry'; -import type { ActivateHelpers } from 'hadron-app-registry'; -import { registerHadronPlugin } from 'hadron-app-registry'; +import type AppRegistry from '@mongodb-js/compass-app-registry'; +import type { ActivateHelpers } from '@mongodb-js/compass-app-registry'; +import { registerCompassPlugin } from '@mongodb-js/compass-app-registry'; import { MongoDBInstancesManagerContext } from './provider'; import { createInstancesStore } from './stores'; import type { ConnectionsService } from '@mongodb-js/compass-connections/provider'; @@ -28,7 +28,7 @@ function MongoDBInstancesManagerProvider({ ); } -export const CompassInstanceStorePlugin = registerHadronPlugin( +export const CompassInstanceStorePlugin = registerCompassPlugin( { name: 'CompassInstanceStore', component: MongoDBInstancesManagerProvider as React.FunctionComponent< diff --git a/packages/compass-app-stores/src/provider.tsx b/packages/compass-app-stores/src/provider.tsx index 7b0cf82f7d6..9bbfc34616c 100644 --- a/packages/compass-app-stores/src/provider.tsx +++ b/packages/compass-app-stores/src/provider.tsx @@ -5,7 +5,7 @@ import { import { createServiceLocator, createServiceProvider, -} from 'hadron-app-registry'; +} from '@mongodb-js/compass-app-registry'; import type { MongoDBInstanceProps } from 'mongodb-instance-model'; import { MongoDBInstance } from 'mongodb-instance-model'; import React, { diff --git a/packages/compass-app-stores/src/stores/instance-store.spec.ts b/packages/compass-app-stores/src/stores/instance-store.spec.ts index 768a609945e..e486dbc4c9d 100644 --- a/packages/compass-app-stores/src/stores/instance-store.spec.ts +++ b/packages/compass-app-stores/src/stores/instance-store.spec.ts @@ -1,4 +1,4 @@ -import type AppRegistry from 'hadron-app-registry'; +import type AppRegistry from '@mongodb-js/compass-app-registry'; import { CompassInstanceStorePlugin } from '../plugin'; import sinon from 'sinon'; import { expect } from 'chai'; diff --git a/packages/compass-app-stores/src/stores/instance-store.ts b/packages/compass-app-stores/src/stores/instance-store.ts index e91ee2f3b67..27fa52958a9 100644 --- a/packages/compass-app-stores/src/stores/instance-store.ts +++ b/packages/compass-app-stores/src/stores/instance-store.ts @@ -5,7 +5,10 @@ import type { ConnectionsService, DataService, } from '@mongodb-js/compass-connections/provider'; -import type { ActivateHelpers, AppRegistry } from 'hadron-app-registry'; +import type { + ActivateHelpers, + AppRegistry, +} from '@mongodb-js/compass-app-registry'; import type { Logger } from '@mongodb-js/compass-logging/provider'; import { openToast } from '@mongodb-js/compass-components'; import { MongoDBInstancesManager } from '../instances-manager'; diff --git a/packages/compass-collection/package.json b/packages/compass-collection/package.json index 3a2168cb7b1..6397f204b1a 100644 --- a/packages/compass-collection/package.json +++ b/packages/compass-collection/package.json @@ -57,7 +57,7 @@ "@mongodb-js/connection-info": "^0.15.2", "@mongodb-js/mongodb-constants": "^0.11.0", "compass-preferences-model": "^2.41.0", - "hadron-app-registry": "^9.4.11", + "@mongodb-js/compass-app-registry": "^9.4.11", "mongodb-collection-model": "^5.29.2", "mongodb-ns": "^2.4.2", "react": "^17.0.2", diff --git a/packages/compass-collection/src/components/collection-tab-provider.tsx b/packages/compass-collection/src/components/collection-tab-provider.tsx index b4a1fe94266..531e5c9fbf9 100644 --- a/packages/compass-collection/src/components/collection-tab-provider.tsx +++ b/packages/compass-collection/src/components/collection-tab-provider.tsx @@ -1,11 +1,11 @@ import React, { useContext, useRef } from 'react'; import type { CollectionTabPluginMetadata } from '../modules/collection-tab'; -import type { HadronPluginComponent } from 'hadron-app-registry'; +import type { CompassPluginComponent } from '@mongodb-js/compass-app-registry'; import type { CollectionSubtab } from '@mongodb-js/compass-workspaces'; export interface CollectionTabPlugin { name: CollectionSubtab; - provider: HadronPluginComponent; + provider: CompassPluginComponent; content: React.FunctionComponent; header: React.FunctionComponent; } diff --git a/packages/compass-collection/src/index.ts b/packages/compass-collection/src/index.ts index 4f754f76b0b..dc087a96e0b 100644 --- a/packages/compass-collection/src/index.ts +++ b/packages/compass-collection/src/index.ts @@ -1,7 +1,7 @@ import React from 'react'; import CollectionTab from './components/collection-tab'; import { activatePlugin as activateCollectionTabPlugin } from './stores/collection-tab'; -import { registerHadronPlugin } from 'hadron-app-registry'; +import { registerCompassPlugin } from '@mongodb-js/compass-app-registry'; import { dataServiceLocator, type DataServiceLocator, @@ -17,7 +17,7 @@ import { export const WorkspaceTab: WorkspacePlugin = { name: CollectionWorkspaceTitle, - provider: registerHadronPlugin( + provider: registerCompassPlugin( { name: CollectionWorkspaceTitle, component: function CollectionProvider({ children }) { diff --git a/packages/compass-collection/src/modules/collection-tab.ts b/packages/compass-collection/src/modules/collection-tab.ts index 0edeba38b05..cfa162fd848 100644 --- a/packages/compass-collection/src/modules/collection-tab.ts +++ b/packages/compass-collection/src/modules/collection-tab.ts @@ -1,7 +1,7 @@ import type { Reducer, AnyAction, Action } from 'redux'; import type { CollectionMetadata } from 'mongodb-collection-model'; import type { ThunkAction } from 'redux-thunk'; -import type AppRegistry from 'hadron-app-registry'; +import type AppRegistry from '@mongodb-js/compass-app-registry'; import type { workspacesServiceLocator } from '@mongodb-js/compass-workspaces/provider'; import type { CollectionSubtab } from '@mongodb-js/compass-workspaces'; import type { DataService } from '@mongodb-js/compass-connections/provider'; diff --git a/packages/compass-collection/src/stores/collection-tab.spec.ts b/packages/compass-collection/src/stores/collection-tab.spec.ts index 8f6445a7633..69bc0ff56e5 100644 --- a/packages/compass-collection/src/stores/collection-tab.spec.ts +++ b/packages/compass-collection/src/stores/collection-tab.spec.ts @@ -3,7 +3,7 @@ import { activatePlugin } from './collection-tab'; import { selectTab } from '../modules/collection-tab'; import { waitFor } from '@mongodb-js/testing-library-compass'; import Sinon from 'sinon'; -import AppRegistry from 'hadron-app-registry'; +import AppRegistry from '@mongodb-js/compass-app-registry'; import { expect } from 'chai'; import type { workspacesServiceLocator } from '@mongodb-js/compass-workspaces/provider'; diff --git a/packages/compass-collection/src/stores/collection-tab.ts b/packages/compass-collection/src/stores/collection-tab.ts index 991887b1fb4..d04359af108 100644 --- a/packages/compass-collection/src/stores/collection-tab.ts +++ b/packages/compass-collection/src/stores/collection-tab.ts @@ -1,4 +1,4 @@ -import type AppRegistry from 'hadron-app-registry'; +import type AppRegistry from '@mongodb-js/compass-app-registry'; import type { DataService } from '@mongodb-js/compass-connections/provider'; import { createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; @@ -7,7 +7,7 @@ import reducer, { collectionMetadataFetched, } from '../modules/collection-tab'; import type { Collection } from '@mongodb-js/compass-app-stores/provider'; -import type { ActivateHelpers } from 'hadron-app-registry'; +import type { ActivateHelpers } from '@mongodb-js/compass-app-registry'; import type { workspacesServiceLocator } from '@mongodb-js/compass-workspaces/provider'; export type CollectionTabOptions = { diff --git a/packages/compass-connections/package.json b/packages/compass-connections/package.json index f1d1c03a75b..fd03e27c807 100644 --- a/packages/compass-connections/package.json +++ b/packages/compass-connections/package.json @@ -60,7 +60,7 @@ "@mongodb-js/connection-storage": "^0.36.0", "bson": "^6.10.3", "compass-preferences-model": "^2.41.0", - "hadron-app-registry": "^9.4.11", + "@mongodb-js/compass-app-registry": "^9.4.11", "lodash": "^4.17.21", "mongodb": "^6.16.0", "mongodb-build-info": "^1.7.2", diff --git a/packages/compass-connections/src/connection-info-provider.tsx b/packages/compass-connections/src/connection-info-provider.tsx index c41325085d6..9783d6718ed 100644 --- a/packages/compass-connections/src/connection-info-provider.tsx +++ b/packages/compass-connections/src/connection-info-provider.tsx @@ -3,7 +3,7 @@ import { type ConnectionInfo } from '@mongodb-js/connection-info'; import { createServiceLocator, createServiceProvider, -} from 'hadron-app-registry'; +} from '@mongodb-js/compass-app-registry'; import { useConnectionForId, useConnectionInfoForId, diff --git a/packages/compass-connections/src/connection-scoped-app-registry.ts b/packages/compass-connections/src/connection-scoped-app-registry.ts index 43d1981e247..df1ce490262 100644 --- a/packages/compass-connections/src/connection-scoped-app-registry.ts +++ b/packages/compass-connections/src/connection-scoped-app-registry.ts @@ -2,7 +2,7 @@ import { type AppRegistry, createServiceLocator, useGlobalAppRegistry, -} from 'hadron-app-registry'; +} from '@mongodb-js/compass-app-registry'; import type { ConnectionInfoRef } from './connection-info-provider'; import { connectionInfoRefLocator } from './connection-info-provider'; diff --git a/packages/compass-connections/src/index.tsx b/packages/compass-connections/src/index.tsx index aab107b17a2..299165f51ee 100644 --- a/packages/compass-connections/src/index.tsx +++ b/packages/compass-connections/src/index.tsx @@ -1,5 +1,5 @@ import { preferencesLocator } from 'compass-preferences-model/provider'; -import { registerHadronPlugin } from 'hadron-app-registry'; +import { registerCompassPlugin } from '@mongodb-js/compass-app-registry'; import type { connect as devtoolsConnect } from 'mongodb-data-service'; import React, { useContext, useRef } from 'react'; import { createLoggerLocator } from '@mongodb-js/compass-logging/provider'; @@ -76,7 +76,7 @@ const ConnectionsComponent: React.FunctionComponent<{ ); }; -const CompassConnectionsPlugin = registerHadronPlugin( +const CompassConnectionsPlugin = registerCompassPlugin( { name: 'CompassConnections', component: ConnectionsComponent, diff --git a/packages/compass-connections/src/provider.ts b/packages/compass-connections/src/provider.ts index e01a522615a..1e1a3226ab7 100644 --- a/packages/compass-connections/src/provider.ts +++ b/packages/compass-connections/src/provider.ts @@ -1,4 +1,4 @@ -import { createServiceLocator } from 'hadron-app-registry'; +import { createServiceLocator } from '@mongodb-js/compass-app-registry'; import { useConnectionInfo } from './connection-info-provider'; import type { DataService } from 'mongodb-data-service'; import { getDataServiceForConnection } from './stores/connections-store-redux'; diff --git a/packages/compass-connections/src/stores/connections-store-redux.ts b/packages/compass-connections/src/stores/connections-store-redux.ts index dcf83927a44..55eabbc396d 100644 --- a/packages/compass-connections/src/stores/connections-store-redux.ts +++ b/packages/compass-connections/src/stores/connections-store-redux.ts @@ -1,4 +1,4 @@ -import type AppRegistry from 'hadron-app-registry'; +import type AppRegistry from '@mongodb-js/compass-app-registry'; import type { Reducer, AnyAction, Action } from 'redux'; import { createStore, applyMiddleware } from 'redux'; import type { ThunkAction } from 'redux-thunk'; diff --git a/packages/compass-connections/src/stores/store-context.tsx b/packages/compass-connections/src/stores/store-context.tsx index 5619ac8fba4..0e107a0df3b 100644 --- a/packages/compass-connections/src/stores/store-context.tsx +++ b/packages/compass-connections/src/stores/store-context.tsx @@ -39,7 +39,7 @@ import { getConnectionTitle, type ConnectionInfo, } from '@mongodb-js/connection-info'; -import { createServiceLocator } from 'hadron-app-registry'; +import { createServiceLocator } from '@mongodb-js/compass-app-registry'; import { isEqual } from 'lodash'; type ConnectionsStore = ReturnType extends Store< diff --git a/packages/compass-crud/README.md b/packages/compass-crud/README.md index 7322f081e3d..9cba8e59498 100644 --- a/packages/compass-crud/README.md +++ b/packages/compass-crud/README.md @@ -28,7 +28,7 @@ Compass. | `CRUD.LoadMoreDocumentsStore` | Triggers when more documents are fetched via scrolling. | Components from this plugin can be interracted with using -[hadron-app][hadron-app] and [hadron-app-registry][hadron-app-registry]. Here are +[hadron-app][hadron-app] and [compass-app-registry][compass-app-registry]. Here are a few examples of working with `compass-crud`'s `Action` and `Roles`. Render an editable document in a React component. @@ -89,7 +89,7 @@ CrudActions.insertDocument((doc) => { ### App Registry Events Emmitted Various actions within this plugin will emit events for other parts of the -application can be listened to via [hadron-app-registry][hadron-app-registry]. +application can be listened to via [compass-app-registry][compass-app-registry]. `Local` events are scoped to a `Tab`. `Global` events are scoped to the whole Compass application. @@ -163,11 +163,11 @@ npm install -S @mongodb-js/compass-crud ## See Also - [compass][compass] -- [hadron-app-registry][hadron-app-registry] +- [compass-app-registry][compass-app-registry] - [hadron-app][hadron-app] [npm_img]: https://img.shields.io/npm/v/@mongodb-js/compass-crud.svg?style=flat-square [npm_url]: https://www.npmjs.org/package/@mongodb-js/compass-crud -[hadron-app]: https://github.com/mongodb-js/compass/packages/hadron-app -[hadron-app-registry]: https://github.com/mongodb-js/compass/packages/hadron-app-registry -[compass]: https://github.com/mongodb-js/compass/packages/compass +[hadron-app]: https://github.com/mongodb-js/compass/tree/main/packages/hadron-app +[compass-app-registry]: https://github.com/mongodb-js/compass/tree/main/packages/compass-app-registry +[compass]: https://github.com/mongodb-js/compass/tree/main/packages/compass diff --git a/packages/compass-crud/package.json b/packages/compass-crud/package.json index de7ef6ed6d9..e29d0f97e14 100644 --- a/packages/compass-crud/package.json +++ b/packages/compass-crud/package.json @@ -88,7 +88,7 @@ "ag-grid-react": "^20.2.0", "bson": "^6.10.3", "compass-preferences-model": "^2.41.0", - "hadron-app-registry": "^9.4.11", + "@mongodb-js/compass-app-registry": "^9.4.11", "hadron-document": "^8.8.12", "hadron-type-checker": "^7.4.10", "jsondiffpatch": "^0.5.0", diff --git a/packages/compass-crud/src/index.ts b/packages/compass-crud/src/index.ts index c539352679f..36ca93f22da 100644 --- a/packages/compass-crud/src/index.ts +++ b/packages/compass-crud/src/index.ts @@ -22,7 +22,7 @@ import { collectionModelLocator, mongoDBInstanceLocator, } from '@mongodb-js/compass-app-stores/provider'; -import { registerHadronPlugin } from 'hadron-app-registry'; +import { registerCompassPlugin } from '@mongodb-js/compass-app-registry'; import { preferencesLocator } from 'compass-preferences-model/provider'; import { createLoggerLocator } from '@mongodb-js/compass-logging/provider'; import { @@ -34,7 +34,7 @@ import { queryBarServiceLocator } from '@mongodb-js/compass-query-bar'; import { telemetryLocator } from '@mongodb-js/compass-telemetry/provider'; import { CrudTabTitle } from './plugin-title'; -const CompassDocumentsHadronPlugin = registerHadronPlugin( +const CompassDocumentsPluginProvider = registerCompassPlugin( { name: 'CompassDocuments', component: function CrudProvider({ children, ...props }) { @@ -71,7 +71,7 @@ const CompassDocumentsHadronPlugin = registerHadronPlugin( export const CompassDocumentsPlugin = { name: 'Documents' as const, - provider: CompassDocumentsHadronPlugin, + provider: CompassDocumentsPluginProvider, content: DocumentList as any, // as any because of reflux store header: CrudTabTitle as any, // as any because of reflux store }; diff --git a/packages/compass-crud/src/stores/crud-store.spec.ts b/packages/compass-crud/src/stores/crud-store.spec.ts index 87f084f5c11..6a16a83cc8e 100644 --- a/packages/compass-crud/src/stores/crud-store.spec.ts +++ b/packages/compass-crud/src/stores/crud-store.spec.ts @@ -1,7 +1,9 @@ import util from 'util'; import type { DataService } from 'mongodb-data-service'; import { connect } from 'mongodb-data-service'; -import AppRegistry, { createActivateHelpers } from 'hadron-app-registry'; +import AppRegistry, { + createActivateHelpers, +} from '@mongodb-js/compass-app-registry'; import HadronDocument, { Element } from 'hadron-document'; import { MongoDBInstance } from 'mongodb-instance-model'; import { once } from 'events'; diff --git a/packages/compass-crud/src/stores/crud-store.ts b/packages/compass-crud/src/stores/crud-store.ts index c2c98a61370..81bc2087c3d 100644 --- a/packages/compass-crud/src/stores/crud-store.ts +++ b/packages/compass-crud/src/stores/crud-store.ts @@ -41,8 +41,8 @@ import type { UpdatePreview } from 'mongodb-data-service'; import type { GridStore, TableHeaderType } from './grid-store'; import configureGridStore from './grid-store'; import type { TypeCastMap } from 'hadron-type-checker'; -import type AppRegistry from 'hadron-app-registry'; -import type { ActivateHelpers } from 'hadron-app-registry'; +import type AppRegistry from '@mongodb-js/compass-app-registry'; +import type { ActivateHelpers } from '@mongodb-js/compass-app-registry'; import { BaseRefluxStore } from './base-reflux-store'; import { openToast, showConfirmation } from '@mongodb-js/compass-components'; import { diff --git a/packages/compass-data-modeling/package.json b/packages/compass-data-modeling/package.json index a06a218e95f..dcfb28826eb 100644 --- a/packages/compass-data-modeling/package.json +++ b/packages/compass-data-modeling/package.json @@ -65,7 +65,7 @@ "@mongodb-js/compass-workspaces": "^0.42.0", "bson": "^6.10.3", "compass-preferences-model": "^2.41.0", - "hadron-app-registry": "^9.4.11", + "@mongodb-js/compass-app-registry": "^9.4.11", "lodash": "^4.17.21", "mongodb": "^6.14.1", "mongodb-ns": "^2.4.2", diff --git a/packages/compass-data-modeling/src/index.ts b/packages/compass-data-modeling/src/index.ts index 25196f4e70e..11d8c5db407 100644 --- a/packages/compass-data-modeling/src/index.ts +++ b/packages/compass-data-modeling/src/index.ts @@ -1,5 +1,5 @@ import React from 'react'; -import { registerHadronPlugin } from 'hadron-app-registry'; +import { registerCompassPlugin } from '@mongodb-js/compass-app-registry'; import { preferencesLocator } from 'compass-preferences-model/provider'; import { connectionsLocator } from '@mongodb-js/compass-connections/provider'; import { telemetryLocator } from '@mongodb-js/compass-telemetry/provider'; @@ -13,7 +13,7 @@ import { PluginTabTitleComponent, WorkspaceName } from './plugin-tab-title'; export const WorkspaceTab: WorkspacePlugin = { name: WorkspaceName, - provider: registerHadronPlugin( + provider: registerCompassPlugin( { name: 'DataModeling', component: function DataModelingProvider({ children }) { diff --git a/packages/compass-data-modeling/src/provider/index.tsx b/packages/compass-data-modeling/src/provider/index.tsx index ec5b683ac66..72fdb9045b7 100644 --- a/packages/compass-data-modeling/src/provider/index.tsx +++ b/packages/compass-data-modeling/src/provider/index.tsx @@ -3,7 +3,7 @@ import type { DataModelStorage, MongoDBDataModelDescription, } from '../services/data-model-storage'; -import { createServiceLocator } from 'hadron-app-registry'; +import { createServiceLocator } from '@mongodb-js/compass-app-registry'; export type DataModelStorageServiceState = { status: 'INITIAL' | 'LOADING' | 'REFRESHING' | 'READY' | 'ERROR'; diff --git a/packages/compass-data-modeling/src/store/index.ts b/packages/compass-data-modeling/src/store/index.ts index 2457a9e49de..92b84772321 100644 --- a/packages/compass-data-modeling/src/store/index.ts +++ b/packages/compass-data-modeling/src/store/index.ts @@ -7,7 +7,7 @@ import type { DataModelStorageService } from '../provider'; import { applyMiddleware, createStore } from 'redux'; import reducer from './reducer'; import thunk from 'redux-thunk'; -import type { ActivateHelpers } from 'hadron-app-registry'; +import type { ActivateHelpers } from '@mongodb-js/compass-app-registry'; export type DataModelingStoreOptions = Record; diff --git a/packages/compass-data-modeling/test/setup-store.tsx b/packages/compass-data-modeling/test/setup-store.tsx index 270f5874233..9939c2453b3 100644 --- a/packages/compass-data-modeling/test/setup-store.tsx +++ b/packages/compass-data-modeling/test/setup-store.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { renderWithConnections } from '@mongodb-js/testing-library-compass'; -import { createActivateHelpers } from 'hadron-app-registry'; +import { createActivateHelpers } from '@mongodb-js/compass-app-registry'; import { createNoopTrack } from '@mongodb-js/compass-telemetry/provider'; import { createNoopLogger } from '@mongodb-js/compass-logging/provider'; import { TestMongoDBInstanceManager } from '@mongodb-js/compass-app-stores/provider'; diff --git a/packages/compass-explain-plan/package.json b/packages/compass-explain-plan/package.json index 2033bcc8d8b..d8074f2b291 100644 --- a/packages/compass-explain-plan/package.json +++ b/packages/compass-explain-plan/package.json @@ -79,7 +79,7 @@ "d3": "^3.5.17", "d3-flextree": "^2.1.2", "d3-hierarchy": "^3.1.2", - "hadron-app-registry": "^9.4.11", + "@mongodb-js/compass-app-registry": "^9.4.11", "lodash": "^4.17.21", "mongodb": "^6.16.0", "react": "^17.0.2", diff --git a/packages/compass-explain-plan/src/index.ts b/packages/compass-explain-plan/src/index.ts index c1809dd2ecc..605d9e363dc 100644 --- a/packages/compass-explain-plan/src/index.ts +++ b/packages/compass-explain-plan/src/index.ts @@ -1,6 +1,6 @@ import ExplainPlanModal from './components/explain-plan-modal'; import { activatePlugin } from './stores'; -import { registerHadronPlugin } from 'hadron-app-registry'; +import { registerCompassPlugin } from '@mongodb-js/compass-app-registry'; import { connectionInfoRefLocator, dataServiceLocator, @@ -10,7 +10,7 @@ import { createLoggerLocator } from '@mongodb-js/compass-logging/provider'; import { telemetryLocator } from '@mongodb-js/compass-telemetry/provider'; import { preferencesLocator } from 'compass-preferences-model/provider'; -const ExplainPlanModalPlugin = registerHadronPlugin( +const ExplainPlanModalPlugin = registerCompassPlugin( { name: 'ExplainPlanModal', component: ExplainPlanModal, diff --git a/packages/compass-explain-plan/src/stores/explain-plan-modal-store.spec.ts b/packages/compass-explain-plan/src/stores/explain-plan-modal-store.spec.ts index c2cd4656cac..1a2b1cd293e 100644 --- a/packages/compass-explain-plan/src/stores/explain-plan-modal-store.spec.ts +++ b/packages/compass-explain-plan/src/stores/explain-plan-modal-store.spec.ts @@ -1,4 +1,6 @@ -import AppRegistry, { createActivateHelpers } from 'hadron-app-registry'; +import AppRegistry, { + createActivateHelpers, +} from '@mongodb-js/compass-app-registry'; import { closeExplainPlanModal, openExplainPlanModal, diff --git a/packages/compass-explain-plan/src/stores/index.ts b/packages/compass-explain-plan/src/stores/index.ts index 2ba90aaee4b..c880f98e3f2 100644 --- a/packages/compass-explain-plan/src/stores/index.ts +++ b/packages/compass-explain-plan/src/stores/index.ts @@ -1,13 +1,13 @@ import { applyMiddleware, createStore } from 'redux'; import thunk from 'redux-thunk'; -import type { ActivateHelpers } from 'hadron-app-registry'; +import type { ActivateHelpers } from '@mongodb-js/compass-app-registry'; import { reducer, INITIAL_STATE, openExplainPlanModal, } from './explain-plan-modal-store'; import type { AggregateOptions, Document, FindOptions } from 'mongodb'; -import type AppRegistry from 'hadron-app-registry'; +import type AppRegistry from '@mongodb-js/compass-app-registry'; import type { CollectionTabPluginMetadata } from '@mongodb-js/compass-collection'; import type { ConnectionInfoRef, diff --git a/packages/compass-export-to-language/package.json b/packages/compass-export-to-language/package.json index 74d3c757653..76bb8de821e 100644 --- a/packages/compass-export-to-language/package.json +++ b/packages/compass-export-to-language/package.json @@ -57,7 +57,7 @@ "@mongodb-js/shell-bson-parser": "^1.2.0", "bson-transpilers": "^3.2.10", "compass-preferences-model": "^2.41.0", - "hadron-app-registry": "^9.4.11", + "@mongodb-js/compass-app-registry": "^9.4.11", "mongodb-ns": "^2.4.2", "react": "^17.0.2", "react-redux": "^8.1.3", diff --git a/packages/compass-export-to-language/src/index.ts b/packages/compass-export-to-language/src/index.ts index 0bdfcf2473f..e38df702136 100644 --- a/packages/compass-export-to-language/src/index.ts +++ b/packages/compass-export-to-language/src/index.ts @@ -1,4 +1,4 @@ -import { registerHadronPlugin } from 'hadron-app-registry'; +import { registerCompassPlugin } from '@mongodb-js/compass-app-registry'; import ExportToLanguageModal from './components/modal'; import { activatePlugin } from './stores'; import { @@ -6,7 +6,7 @@ import { type DataServiceLocator, } from '@mongodb-js/compass-connections/provider'; -const ExportToLanguagePlugin = registerHadronPlugin( +const ExportToLanguagePlugin = registerCompassPlugin( { name: 'ExportToLanguage', component: ExportToLanguageModal, diff --git a/packages/compass-export-to-language/src/stores/index.ts b/packages/compass-export-to-language/src/stores/index.ts index 7e484882966..70beb1f48cc 100644 --- a/packages/compass-export-to-language/src/stores/index.ts +++ b/packages/compass-export-to-language/src/stores/index.ts @@ -4,8 +4,8 @@ import type { QueryExpression, InputExpression } from '../modules/transpiler'; import { isValidExportMode } from '../modules/transpiler'; import type { CollectionTabPluginMetadata } from '@mongodb-js/compass-collection'; import type { DataService } from '@mongodb-js/compass-connections/provider'; -import type { ActivateHelpers } from 'hadron-app-registry'; -import type AppRegistry from 'hadron-app-registry'; +import type { ActivateHelpers } from '@mongodb-js/compass-app-registry'; +import type AppRegistry from '@mongodb-js/compass-app-registry'; function isAction
( action: Action, diff --git a/packages/compass-field-store/package.json b/packages/compass-field-store/package.json index 2cad7810e9c..0260630b2ce 100644 --- a/packages/compass-field-store/package.json +++ b/packages/compass-field-store/package.json @@ -69,7 +69,7 @@ "dependencies": { "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-logging": "^1.7.2", - "hadron-app-registry": "^9.4.11", + "@mongodb-js/compass-app-registry": "^9.4.11", "lodash": "^4.17.21", "mongodb-schema": "^12.6.2", "react": "^17.0.2", diff --git a/packages/compass-field-store/src/index.tsx b/packages/compass-field-store/src/index.tsx index 71bdc65fecc..992a01f05bc 100644 --- a/packages/compass-field-store/src/index.tsx +++ b/packages/compass-field-store/src/index.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { registerHadronPlugin } from 'hadron-app-registry'; +import { registerCompassPlugin } from '@mongodb-js/compass-app-registry'; import { activatePlugin } from './stores/store'; import { connectionsLocator } from '@mongodb-js/compass-connections/provider'; import { createLoggerLocator } from '@mongodb-js/compass-logging/provider'; @@ -11,7 +11,7 @@ const FieldStoreComponent: React.FunctionComponent = ({ children }) => { return <>{children}; }; -const FieldStorePlugin = registerHadronPlugin( +const FieldStorePlugin = registerCompassPlugin( { name: 'FieldStore', component: FieldStoreComponent, diff --git a/packages/compass-field-store/src/stores/field-store-service.ts b/packages/compass-field-store/src/stores/field-store-service.ts index 4077ab1bba8..c299863e434 100644 --- a/packages/compass-field-store/src/stores/field-store-service.ts +++ b/packages/compass-field-store/src/stores/field-store-service.ts @@ -1,5 +1,5 @@ import { type Schema } from 'mongodb-schema'; -import { createServiceLocator } from 'hadron-app-registry'; +import { createServiceLocator } from '@mongodb-js/compass-app-registry'; import { useConnectionInfoRef, type ConnectionInfoRef, diff --git a/packages/compass-field-store/src/stores/store.ts b/packages/compass-field-store/src/stores/store.ts index 2356fdc764f..09971e373c2 100644 --- a/packages/compass-field-store/src/stores/store.ts +++ b/packages/compass-field-store/src/stores/store.ts @@ -2,7 +2,7 @@ import { applyMiddleware, createStore } from 'redux'; import reducer, { connectionDisconnected } from '../modules'; import { FieldStoreContext } from './context'; import type { ConnectionsService } from '@mongodb-js/compass-connections/provider'; -import type { ActivateHelpers } from 'hadron-app-registry'; +import type { ActivateHelpers } from '@mongodb-js/compass-app-registry'; import thunk from 'redux-thunk'; import type { Logger } from '@mongodb-js/compass-logging/provider'; diff --git a/packages/compass-find-in-page/package.json b/packages/compass-find-in-page/package.json index 59eb6b9ab0b..3cb6bb0f723 100644 --- a/packages/compass-find-in-page/package.json +++ b/packages/compass-find-in-page/package.json @@ -72,7 +72,7 @@ }, "dependencies": { "@mongodb-js/compass-components": "^1.39.0", - "hadron-app-registry": "^9.4.11", + "@mongodb-js/compass-app-registry": "^9.4.11", "hadron-ipc": "^3.5.2", "react": "^17.0.2", "react-redux": "^8.1.3", diff --git a/packages/compass-find-in-page/src/index.ts b/packages/compass-find-in-page/src/index.ts index 1efd23c6530..78022021267 100644 --- a/packages/compass-find-in-page/src/index.ts +++ b/packages/compass-find-in-page/src/index.ts @@ -1,8 +1,8 @@ -import { registerHadronPlugin } from 'hadron-app-registry'; +import { registerCompassPlugin } from '@mongodb-js/compass-app-registry'; import CompassFindInPage from './components/compass-find-in-page'; import { activatePlugin } from './stores/store'; -export const CompassFindInPagePlugin = registerHadronPlugin({ +export const CompassFindInPagePlugin = registerCompassPlugin({ name: 'CompassFindInPage', component: CompassFindInPage, activate: activatePlugin, diff --git a/packages/compass-generative-ai/package.json b/packages/compass-generative-ai/package.json index f398d1bae23..62f4da67a98 100644 --- a/packages/compass-generative-ai/package.json +++ b/packages/compass-generative-ai/package.json @@ -61,7 +61,7 @@ "@mongodb-js/compass-utils": "^0.9.2", "bson": "^6.10.3", "compass-preferences-model": "^2.41.0", - "hadron-app-registry": "^9.4.11", + "@mongodb-js/compass-app-registry": "^9.4.11", "mongodb": "^6.16.0", "mongodb-schema": "^12.6.2", "react": "^17.0.2", diff --git a/packages/compass-generative-ai/src/index.ts b/packages/compass-generative-ai/src/index.ts index 6e1dae845ad..da945a0f8c7 100644 --- a/packages/compass-generative-ai/src/index.ts +++ b/packages/compass-generative-ai/src/index.ts @@ -1,11 +1,11 @@ -import { registerHadronPlugin } from 'hadron-app-registry'; +import { registerCompassPlugin } from '@mongodb-js/compass-app-registry'; import { atlasAuthServiceLocator } from '@mongodb-js/atlas-service/provider'; import { AtlasAiPlugin } from './components'; import { atlasAiServiceLocator } from './provider'; import { preferencesLocator } from 'compass-preferences-model/provider'; import { activatePlugin } from './store/atlas-ai-store'; -export const CompassGenerativeAIPlugin = registerHadronPlugin( +export const CompassGenerativeAIPlugin = registerCompassPlugin( { name: 'CompassGenerativeAI', component: AtlasAiPlugin, diff --git a/packages/compass-generative-ai/src/provider.tsx b/packages/compass-generative-ai/src/provider.tsx index 05f3bd58db6..9a6d6dbfb56 100644 --- a/packages/compass-generative-ai/src/provider.tsx +++ b/packages/compass-generative-ai/src/provider.tsx @@ -6,7 +6,7 @@ import { atlasServiceLocator } from '@mongodb-js/atlas-service/provider'; import { createServiceLocator, createServiceProvider, -} from 'hadron-app-registry'; +} from '@mongodb-js/compass-app-registry'; const AtlasAiServiceContext = createContext(null); diff --git a/packages/compass-generative-ai/src/store/atlas-ai-store.ts b/packages/compass-generative-ai/src/store/atlas-ai-store.ts index 25e7c9cb4be..56370e8f2f9 100644 --- a/packages/compass-generative-ai/src/store/atlas-ai-store.ts +++ b/packages/compass-generative-ai/src/store/atlas-ai-store.ts @@ -10,7 +10,7 @@ import type { AtlasAuthService } from '@mongodb-js/atlas-service/provider'; import type { AtlasAiService } from '../atlas-ai-service'; import type { PreferencesAccess } from 'compass-preferences-model'; import type { AtlasAiPluginProps } from '../components/plugin'; -import type { ActivateHelpers } from 'hadron-app-registry'; +import type { ActivateHelpers } from '@mongodb-js/compass-app-registry'; export let store: CompassGenerativeAIServiceStore; diff --git a/packages/compass-global-writes/package.json b/packages/compass-global-writes/package.json index 86514733cbc..0d8ccdfd726 100644 --- a/packages/compass-global-writes/package.json +++ b/packages/compass-global-writes/package.json @@ -55,7 +55,7 @@ "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-logging": "^1.7.2", "@mongodb-js/compass-telemetry": "^1.10.0", - "hadron-app-registry": "^9.4.11", + "@mongodb-js/compass-app-registry": "^9.4.11", "lodash": "^4.17.21", "@mongodb-js/compass-field-store": "^9.36.0", "mongodb-ns": "^2.4.2", diff --git a/packages/compass-global-writes/src/index.ts b/packages/compass-global-writes/src/index.ts index a0cadcc0b76..2a02dadad29 100644 --- a/packages/compass-global-writes/src/index.ts +++ b/packages/compass-global-writes/src/index.ts @@ -1,5 +1,5 @@ import React from 'react'; -import { registerHadronPlugin } from 'hadron-app-registry'; +import { registerCompassPlugin } from '@mongodb-js/compass-app-registry'; import GlobalWrites from './components'; import { GlobalWritesTabTitle } from './plugin-title'; @@ -9,7 +9,7 @@ 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( +const CompassGlobalWritesPluginProvider = registerCompassPlugin( { name: 'CompassGlobalWrites', component: function GlobalWritesProvider({ children }) { @@ -27,7 +27,7 @@ const CompassGlobalWritesHadronPlugin = registerHadronPlugin( export const CompassGlobalWritesPlugin = { name: 'GlobalWrites' as const, - provider: CompassGlobalWritesHadronPlugin, + provider: CompassGlobalWritesPluginProvider, content: GlobalWrites as React.FunctionComponent, header: GlobalWritesTabTitle as React.FunctionComponent, }; diff --git a/packages/compass-global-writes/src/store/index.ts b/packages/compass-global-writes/src/store/index.ts index c37217bc185..d390a63d392 100644 --- a/packages/compass-global-writes/src/store/index.ts +++ b/packages/compass-global-writes/src/store/index.ts @@ -1,6 +1,6 @@ import { createStore, applyMiddleware, type Action, type Store } from 'redux'; import thunk from 'redux-thunk'; -import type { ActivateHelpers } from 'hadron-app-registry'; +import type { ActivateHelpers } from '@mongodb-js/compass-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'; diff --git a/packages/compass-global-writes/tests/create-store.tsx b/packages/compass-global-writes/tests/create-store.tsx index 631a6d6d5d9..ad868e95b51 100644 --- a/packages/compass-global-writes/tests/create-store.tsx +++ b/packages/compass-global-writes/tests/create-store.tsx @@ -4,7 +4,7 @@ import type { GlobalWritesPluginServices, } from '../src/store'; import { activateGlobalWritesPlugin } from '../src/store'; -import { createActivateHelpers } from 'hadron-app-registry'; +import { createActivateHelpers } from '@mongodb-js/compass-app-registry'; import { createNoopLogger } from '@mongodb-js/compass-logging/provider'; import { createNoopTrack } from '@mongodb-js/compass-telemetry/provider'; import type { ConnectionInfo } from '@mongodb-js/compass-connections/provider'; diff --git a/packages/compass-import-export/package.json b/packages/compass-import-export/package.json index 535b5e318da..3dda1716afd 100644 --- a/packages/compass-import-export/package.json +++ b/packages/compass-import-export/package.json @@ -60,7 +60,7 @@ "compass-preferences-model": "^2.41.0", "debug": "^4.3.4", "electron": "^36.4.0", - "hadron-app-registry": "^9.4.11", + "@mongodb-js/compass-app-registry": "^9.4.11", "hadron-document": "^8.8.12", "hadron-ipc": "^3.5.2", "lodash": "^4.17.21", diff --git a/packages/compass-import-export/src/index.ts b/packages/compass-import-export/src/index.ts index e1231263b6c..05e2fcd0d81 100644 --- a/packages/compass-import-export/src/index.ts +++ b/packages/compass-import-export/src/index.ts @@ -1,4 +1,4 @@ -import { registerHadronPlugin } from 'hadron-app-registry'; +import { registerCompassPlugin } from '@mongodb-js/compass-app-registry'; import ImportPluginComponent from './import-plugin'; import { activatePlugin as activateImportPlugin } from './stores/import-store'; import ExportPluginComponent from './export-plugin'; @@ -12,7 +12,7 @@ import { connectionsLocator } from '@mongodb-js/compass-connections/provider'; /** * The import plugin. */ -export const ImportPlugin = registerHadronPlugin( +export const ImportPlugin = registerCompassPlugin( { name: 'Import', component: ImportPluginComponent, @@ -30,7 +30,7 @@ export const ImportPlugin = registerHadronPlugin( /** * The export plugin. */ -export const ExportPlugin = registerHadronPlugin( +export const ExportPlugin = registerCompassPlugin( { name: 'Export', component: ExportPluginComponent, diff --git a/packages/compass-import-export/src/stores/export-store.spec.tsx b/packages/compass-import-export/src/stores/export-store.spec.tsx index 67a14fe459d..580c0b07e8b 100644 --- a/packages/compass-import-export/src/stores/export-store.spec.tsx +++ b/packages/compass-import-export/src/stores/export-store.spec.tsx @@ -1,4 +1,4 @@ -import type AppRegistry from 'hadron-app-registry'; +import type AppRegistry from '@mongodb-js/compass-app-registry'; import { expect } from 'chai'; import { createPluginTestHelpers, diff --git a/packages/compass-import-export/src/stores/export-store.ts b/packages/compass-import-export/src/stores/export-store.ts index 13e14232550..3c3208615f8 100644 --- a/packages/compass-import-export/src/stores/export-store.ts +++ b/packages/compass-import-export/src/stores/export-store.ts @@ -1,4 +1,4 @@ -import type AppRegistry from 'hadron-app-registry'; +import type AppRegistry from '@mongodb-js/compass-app-registry'; import type { Action, AnyAction } from 'redux'; import { createStore, applyMiddleware, combineReducers } from 'redux'; import type { ThunkAction } from 'redux-thunk'; @@ -11,7 +11,7 @@ import { } from '../modules/export'; import type { PreferencesAccess } from 'compass-preferences-model'; import type { Logger } from '@mongodb-js/compass-logging/provider'; -import type { ActivateHelpers } from 'hadron-app-registry'; +import type { ActivateHelpers } from '@mongodb-js/compass-app-registry'; import type { ConnectionsService } from '@mongodb-js/compass-connections/provider'; import type { TrackFunction } from '@mongodb-js/compass-telemetry'; diff --git a/packages/compass-import-export/src/stores/import-store.spec.tsx b/packages/compass-import-export/src/stores/import-store.spec.tsx index 4aff691c95e..ecbf29c0280 100644 --- a/packages/compass-import-export/src/stores/import-store.spec.tsx +++ b/packages/compass-import-export/src/stores/import-store.spec.tsx @@ -1,4 +1,4 @@ -import type AppRegistry from 'hadron-app-registry'; +import type AppRegistry from '@mongodb-js/compass-app-registry'; import { expect } from 'chai'; import { createPluginTestHelpers, diff --git a/packages/compass-import-export/src/stores/import-store.ts b/packages/compass-import-export/src/stores/import-store.ts index b617d30751a..4b885840986 100644 --- a/packages/compass-import-export/src/stores/import-store.ts +++ b/packages/compass-import-export/src/stores/import-store.ts @@ -1,4 +1,4 @@ -import type AppRegistry from 'hadron-app-registry'; +import type AppRegistry from '@mongodb-js/compass-app-registry'; import type { Action, AnyAction } from 'redux'; import { createStore, applyMiddleware, combineReducers } from 'redux'; import type { ThunkAction } from 'redux-thunk'; @@ -12,7 +12,7 @@ import { import type { WorkspacesService } from '@mongodb-js/compass-workspaces/provider'; import type { Logger } from '@mongodb-js/compass-logging/provider'; import type { ConnectionsService } from '@mongodb-js/compass-connections/provider'; -import type { ActivateHelpers } from 'hadron-app-registry'; +import type { ActivateHelpers } from '@mongodb-js/compass-app-registry'; import type { TrackFunction } from '@mongodb-js/compass-telemetry'; export type ImportPluginServices = { diff --git a/packages/compass-indexes/package.json b/packages/compass-indexes/package.json index 512e7a0d9e6..8611bcfbc5f 100644 --- a/packages/compass-indexes/package.json +++ b/packages/compass-indexes/package.json @@ -79,7 +79,7 @@ "@mongodb-js/shell-bson-parser": "^1.2.0", "bson": "^6.10.3", "compass-preferences-model": "^2.41.0", - "hadron-app-registry": "^9.4.11", + "@mongodb-js/compass-app-registry": "^9.4.11", "lodash": "^4.17.21", "mongodb": "^6.16.0", "mongodb-collection-model": "^5.29.2", diff --git a/packages/compass-indexes/src/index.spec.tsx b/packages/compass-indexes/src/index.spec.tsx index 49fc9210b31..e65cf9d14e2 100644 --- a/packages/compass-indexes/src/index.spec.tsx +++ b/packages/compass-indexes/src/index.spec.tsx @@ -2,7 +2,7 @@ import React from 'react'; import Sinon from 'sinon'; import { CompassIndexesPlugin as CompassIndexesSubtab, - CompassIndexesHadronPlugin, + CompassIndexesPluginProvider, } from './index'; import { createDefaultConnectionInfo, @@ -35,7 +35,7 @@ describe('CompassIndexesPlugin', function () { }; const renderHelpers = createPluginTestHelpers( - CompassIndexesHadronPlugin.withMockServices({ + CompassIndexesPluginProvider.withMockServices({ dataService, atlasService, instance: { diff --git a/packages/compass-indexes/src/index.ts b/packages/compass-indexes/src/index.ts index 618264908c1..45536723d98 100644 --- a/packages/compass-indexes/src/index.ts +++ b/packages/compass-indexes/src/index.ts @@ -1,5 +1,5 @@ import React from 'react'; -import { registerHadronPlugin } from 'hadron-app-registry'; +import { registerCompassPlugin } from '@mongodb-js/compass-app-registry'; import { activateIndexesPlugin, type IndexesDataServiceProps, @@ -20,7 +20,7 @@ import { IndexesTabTitle } from './plugin-title'; import { atlasServiceLocator } from '@mongodb-js/atlas-service/provider'; import { preferencesLocator } from 'compass-preferences-model/provider'; -export const CompassIndexesHadronPlugin = registerHadronPlugin( +export const CompassIndexesPluginProvider = registerCompassPlugin( { name: 'CompassIndexes', component: function IndexesProvider({ children }) { @@ -43,7 +43,7 @@ export const CompassIndexesHadronPlugin = registerHadronPlugin( export const CompassIndexesPlugin = { name: 'Indexes' as const, - provider: CompassIndexesHadronPlugin, + provider: CompassIndexesPluginProvider, content: Indexes as React.FunctionComponent, header: IndexesTabTitle as React.FunctionComponent, }; diff --git a/packages/compass-indexes/src/modules/index.ts b/packages/compass-indexes/src/modules/index.ts index 079984a1473..e2b6d34e56d 100644 --- a/packages/compass-indexes/src/modules/index.ts +++ b/packages/compass-indexes/src/modules/index.ts @@ -1,6 +1,6 @@ import { combineReducers } from 'redux'; import type { Action, AnyAction } from 'redux'; -import type AppRegistry from 'hadron-app-registry'; +import type AppRegistry from '@mongodb-js/compass-app-registry'; import isWritable from './is-writable'; import indexView from './index-view'; import isReadonlyView from './is-readonly-view'; diff --git a/packages/compass-indexes/src/stores/store.spec.ts b/packages/compass-indexes/src/stores/store.spec.ts index b2dced9256b..4dc569269a7 100644 --- a/packages/compass-indexes/src/stores/store.spec.ts +++ b/packages/compass-indexes/src/stores/store.spec.ts @@ -1,5 +1,5 @@ import { EventEmitter } from 'events'; -import AppRegistry from 'hadron-app-registry'; +import AppRegistry from '@mongodb-js/compass-app-registry'; import { expect } from 'chai'; import { type IndexesStore } from './store'; import { setupStore } from '../../test/setup-store'; diff --git a/packages/compass-indexes/src/stores/store.ts b/packages/compass-indexes/src/stores/store.ts index 0a0a8ee0628..dcb4ea04588 100644 --- a/packages/compass-indexes/src/stores/store.ts +++ b/packages/compass-indexes/src/stores/store.ts @@ -17,8 +17,8 @@ import { stopPollingSearchIndexes, } from '../modules/search-indexes'; import type { DataService } from 'mongodb-data-service'; -import type AppRegistry from 'hadron-app-registry'; -import type { ActivateHelpers } from 'hadron-app-registry'; +import type AppRegistry from '@mongodb-js/compass-app-registry'; +import type { ActivateHelpers } from '@mongodb-js/compass-app-registry'; import type { Collection, MongoDBInstance, diff --git a/packages/compass-indexes/test/setup-store.ts b/packages/compass-indexes/test/setup-store.ts index 1d935f40cd8..e0034988b0b 100644 --- a/packages/compass-indexes/test/setup-store.ts +++ b/packages/compass-indexes/test/setup-store.ts @@ -1,6 +1,6 @@ import { EventEmitter } from 'events'; import Sinon from 'sinon'; -import type AppRegistry from 'hadron-app-registry'; +import type AppRegistry from '@mongodb-js/compass-app-registry'; import type { IndexesDataService, IndexesPluginOptions, @@ -8,7 +8,7 @@ import type { IndexesStore, } from '../src/stores/store'; import { activateIndexesPlugin } from '../src/stores/store'; -import { createActivateHelpers } from 'hadron-app-registry'; +import { createActivateHelpers } from '@mongodb-js/compass-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'; diff --git a/packages/compass-logging/package.json b/packages/compass-logging/package.json index c1b5946d804..0977de1b48e 100644 --- a/packages/compass-logging/package.json +++ b/packages/compass-logging/package.json @@ -52,7 +52,7 @@ }, "dependencies": { "debug": "^4.3.4", - "hadron-app-registry": "^9.4.11", + "@mongodb-js/compass-app-registry": "^9.4.11", "hadron-ipc": "^3.5.2", "is-electron-renderer": "^2.0.1", "mongodb-log-writer": "^2.3.4", diff --git a/packages/compass-logging/src/provider.ts b/packages/compass-logging/src/provider.ts index 8e5c094ec80..c012ac0251d 100644 --- a/packages/compass-logging/src/provider.ts +++ b/packages/compass-logging/src/provider.ts @@ -4,7 +4,7 @@ import type { MongoLogId, MongoLogWriter, } from 'mongodb-log-writer/mongo-log-writer'; -import { createServiceLocator } from 'hadron-app-registry'; +import { createServiceLocator } from '@mongodb-js/compass-app-registry'; export type { Logger } from './logger'; diff --git a/packages/compass-preferences-model/package.json b/packages/compass-preferences-model/package.json index 8ce2a3b07ee..90c9e0aa3c2 100644 --- a/packages/compass-preferences-model/package.json +++ b/packages/compass-preferences-model/package.json @@ -54,7 +54,7 @@ "@mongodb-js/devtools-proxy-support": "^0.4.4", "@mongodb-js/compass-components": "^1.39.0", "bson": "^6.10.3", - "hadron-app-registry": "^9.4.11", + "@mongodb-js/compass-app-registry": "^9.4.11", "hadron-ipc": "^3.5.2", "js-yaml": "^4.1.0", "lodash": "^4.17.21", diff --git a/packages/compass-preferences-model/src/react.ts b/packages/compass-preferences-model/src/react.ts index 2dc7caf8526..93bf1df8058 100644 --- a/packages/compass-preferences-model/src/react.ts +++ b/packages/compass-preferences-model/src/react.ts @@ -11,7 +11,7 @@ import { import { type AllPreferences } from './'; import type { PreferencesAccess } from './preferences'; import { ReadOnlyPreferenceAccess } from './read-only-preferences-access'; -import { createServiceLocator } from 'hadron-app-registry'; +import { createServiceLocator } from '@mongodb-js/compass-app-registry'; import { pick } from 'lodash'; const PreferencesContext = createContext(null); diff --git a/packages/compass-query-bar/package.json b/packages/compass-query-bar/package.json index e410f4978ae..826a4c29e0c 100644 --- a/packages/compass-query-bar/package.json +++ b/packages/compass-query-bar/package.json @@ -79,7 +79,7 @@ "@mongodb-js/my-queries-storage": "^0.28.0", "bson": "^6.10.3", "compass-preferences-model": "^2.41.0", - "hadron-app-registry": "^9.4.11", + "@mongodb-js/compass-app-registry": "^9.4.11", "lodash": "^4.17.21", "mongodb": "^6.16.0", "mongodb-instance-model": "^12.33.0", diff --git a/packages/compass-query-bar/src/components/hooks.tsx b/packages/compass-query-bar/src/components/hooks.tsx index 56264b86a8f..09edf6b0557 100644 --- a/packages/compass-query-bar/src/components/hooks.tsx +++ b/packages/compass-query-bar/src/components/hooks.tsx @@ -4,7 +4,7 @@ import { useSelector, useStore } from '../stores/context'; import type { ChangeFilterEvent } from '../modules/change-filter'; import { applyFilterChange } from '../stores/query-bar-reducer'; import { mapFormFieldsToQuery } from '../utils/query'; -import { createServiceLocator } from 'hadron-app-registry'; +import { createServiceLocator } from '@mongodb-js/compass-app-registry'; import type { RootState } from '../stores/query-bar-store'; import { isQueryEqual } from '../utils'; import type { BaseQuery } from '../constants/query-properties'; diff --git a/packages/compass-query-bar/src/index.tsx b/packages/compass-query-bar/src/index.tsx index f7e55a69ee9..4e171702c27 100644 --- a/packages/compass-query-bar/src/index.tsx +++ b/packages/compass-query-bar/src/index.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { registerHadronPlugin } from 'hadron-app-registry'; +import { registerCompassPlugin } from '@mongodb-js/compass-app-registry'; import { activatePlugin } from './stores/query-bar-store'; import { connectionInfoRefLocator, @@ -27,7 +27,7 @@ import { } from '@mongodb-js/my-queries-storage/provider'; import { telemetryLocator } from '@mongodb-js/compass-telemetry/provider'; -const QueryBarPlugin = registerHadronPlugin( +const QueryBarPlugin = registerCompassPlugin( { name: 'QueryBar', // Query bar is a special case where we render nothing for the purposes of diff --git a/packages/compass-query-bar/src/stores/query-bar-reducer.spec.ts b/packages/compass-query-bar/src/stores/query-bar-reducer.spec.ts index 6904ab0bf0d..e47a643701c 100644 --- a/packages/compass-query-bar/src/stores/query-bar-reducer.spec.ts +++ b/packages/compass-query-bar/src/stores/query-bar-reducer.spec.ts @@ -17,7 +17,7 @@ import { import { configureStore } from './query-bar-store'; import type { QueryBarExtraArgs, RootState } from './query-bar-store'; import Sinon from 'sinon'; -import type AppRegistry from 'hadron-app-registry'; +import type AppRegistry from '@mongodb-js/compass-app-registry'; import { mapQueryToFormFields } from '../utils/query'; import type { PreferencesAccess } from 'compass-preferences-model'; import { createSandboxFromDefaultPreferences } from 'compass-preferences-model'; diff --git a/packages/compass-query-bar/src/stores/query-bar-store.spec.ts b/packages/compass-query-bar/src/stores/query-bar-store.spec.ts index e181190bbe5..b154753d804 100644 --- a/packages/compass-query-bar/src/stores/query-bar-store.spec.ts +++ b/packages/compass-query-bar/src/stores/query-bar-store.spec.ts @@ -2,7 +2,7 @@ import sinon from 'sinon'; import { activatePlugin } from './query-bar-store'; import { createNoopLogger } from '@mongodb-js/compass-logging/provider'; import { createNoopTrack } from '@mongodb-js/compass-telemetry/provider'; -import { AppRegistry } from 'hadron-app-registry'; +import { AppRegistry } from '@mongodb-js/compass-app-registry'; import type { PreferencesAccess } from 'compass-preferences-model'; import { createSandboxFromDefaultPreferences } from 'compass-preferences-model'; import { expect } from 'chai'; diff --git a/packages/compass-query-bar/src/stores/query-bar-store.ts b/packages/compass-query-bar/src/stores/query-bar-store.ts index 52425866cc2..62f2b7e7018 100644 --- a/packages/compass-query-bar/src/stores/query-bar-store.ts +++ b/packages/compass-query-bar/src/stores/query-bar-store.ts @@ -1,4 +1,4 @@ -import type AppRegistry from 'hadron-app-registry'; +import type AppRegistry from '@mongodb-js/compass-app-registry'; import { createStore as _createStore, applyMiddleware, @@ -23,7 +23,7 @@ import { aiQueryReducer } from './ai-query-reducer'; import { getQueryAttributes } from '../utils'; import type { PreferencesAccess } from 'compass-preferences-model'; import type { CollectionTabPluginMetadata } from '@mongodb-js/compass-collection'; -import type { ActivateHelpers } from 'hadron-app-registry'; +import type { ActivateHelpers } from '@mongodb-js/compass-app-registry'; import type { MongoDBInstance } from 'mongodb-instance-model'; import { QueryBarStoreContext } from './context'; import type { Logger } from '@mongodb-js/compass-logging/provider'; diff --git a/packages/compass-saved-aggregations-queries/package.json b/packages/compass-saved-aggregations-queries/package.json index a49f17eb97a..390081308b3 100644 --- a/packages/compass-saved-aggregations-queries/package.json +++ b/packages/compass-saved-aggregations-queries/package.json @@ -60,7 +60,7 @@ "bson": "^6.10.3", "compass-preferences-model": "^2.41.0", "fuse.js": "^6.5.3", - "hadron-app-registry": "^9.4.11", + "@mongodb-js/compass-app-registry": "^9.4.11", "mongodb-ns": "^2.4.2", "react": "^17.0.2", "react-redux": "^8.1.3", diff --git a/packages/compass-saved-aggregations-queries/src/index.ts b/packages/compass-saved-aggregations-queries/src/index.ts index 71d96b26d27..1d497c130bb 100644 --- a/packages/compass-saved-aggregations-queries/src/index.ts +++ b/packages/compass-saved-aggregations-queries/src/index.ts @@ -1,5 +1,5 @@ import React from 'react'; -import { registerHadronPlugin } from 'hadron-app-registry'; +import { registerCompassPlugin } from '@mongodb-js/compass-app-registry'; import { mongoDBInstancesManagerLocator } from '@mongodb-js/compass-app-stores/provider'; import { createLoggerLocator } from '@mongodb-js/compass-logging/provider'; import { telemetryLocator } from '@mongodb-js/compass-telemetry/provider'; @@ -17,7 +17,7 @@ import { PluginTabTitleComponent, WorkspaceName } from './plugin-tab-title'; export const WorkspaceTab: WorkspacePlugin = { name: WorkspaceName, - provider: registerHadronPlugin( + provider: registerCompassPlugin( { name: WorkspaceName, component: function MyQueriesProvider({ children }): any { diff --git a/packages/compass-saved-aggregations-queries/src/stores/index.ts b/packages/compass-saved-aggregations-queries/src/stores/index.ts index 781678c2b3e..0b6dbdcc6d1 100644 --- a/packages/compass-saved-aggregations-queries/src/stores/index.ts +++ b/packages/compass-saved-aggregations-queries/src/stores/index.ts @@ -1,4 +1,4 @@ -import type AppRegistry from 'hadron-app-registry'; +import type AppRegistry from '@mongodb-js/compass-app-registry'; import { createStore, applyMiddleware, combineReducers } from 'redux'; import type { AnyAction, Action } from 'redux'; import thunk from 'redux-thunk'; diff --git a/packages/compass-schema-validation/package.json b/packages/compass-schema-validation/package.json index 267af704f0d..a8e0a09e595 100644 --- a/packages/compass-schema-validation/package.json +++ b/packages/compass-schema-validation/package.json @@ -80,7 +80,7 @@ "@mongodb-js/mongodb-constants": "^0.11.0", "bson": "^6.10.3", "compass-preferences-model": "^2.41.0", - "hadron-app-registry": "^9.4.11", + "@mongodb-js/compass-app-registry": "^9.4.11", "javascript-stringify": "^2.0.1", "lodash": "^4.17.21", "mongodb": "^6.16.0", diff --git a/packages/compass-schema-validation/src/index.ts b/packages/compass-schema-validation/src/index.ts index 6dc65f41c70..17e632456e6 100644 --- a/packages/compass-schema-validation/src/index.ts +++ b/packages/compass-schema-validation/src/index.ts @@ -1,7 +1,7 @@ import React from 'react'; import { onActivated } from './stores'; import { CompassSchemaValidation } from './components/compass-schema-validation'; -import { registerHadronPlugin } from 'hadron-app-registry'; +import { registerCompassPlugin } from '@mongodb-js/compass-app-registry'; import { connectionInfoRefLocator, dataServiceLocator, @@ -15,7 +15,7 @@ import { SchemaValidationTabTitle } from './plugin-title'; import { workspacesServiceLocator } from '@mongodb-js/compass-workspaces/provider'; import type { RequiredDataServiceProps } from './modules'; -const CompassSchemaValidationHadronPlugin = registerHadronPlugin( +const CompassSchemaValidationPluginProvider = registerCompassPlugin( { name: 'CompassSchemaValidationPlugin', component: function SchemaValidationsProvider({ children }) { @@ -36,7 +36,7 @@ const CompassSchemaValidationHadronPlugin = registerHadronPlugin( ); export const CompassSchemaValidationPlugin = { name: 'Validation' as const, - provider: CompassSchemaValidationHadronPlugin, + provider: CompassSchemaValidationPluginProvider, content: CompassSchemaValidation, header: SchemaValidationTabTitle, }; diff --git a/packages/compass-schema-validation/src/modules/index.ts b/packages/compass-schema-validation/src/modules/index.ts index 59901d97389..f2ceb972f2e 100644 --- a/packages/compass-schema-validation/src/modules/index.ts +++ b/packages/compass-schema-validation/src/modules/index.ts @@ -27,7 +27,7 @@ import type { ConnectionInfoRef, DataService as OriginalDataService, } from '@mongodb-js/compass-connections/provider'; -import type AppRegistry from 'hadron-app-registry'; +import type AppRegistry from '@mongodb-js/compass-app-registry'; import type { Logger } from '@mongodb-js/compass-logging/provider'; import type { TrackFunction } from '@mongodb-js/compass-telemetry'; import { type WorkspacesService } from '@mongodb-js/compass-workspaces/provider'; diff --git a/packages/compass-schema-validation/src/stores/store.spec.ts b/packages/compass-schema-validation/src/stores/store.spec.ts index 21510c990cb..31048153ded 100644 --- a/packages/compass-schema-validation/src/stores/store.spec.ts +++ b/packages/compass-schema-validation/src/stores/store.spec.ts @@ -1,5 +1,7 @@ import { expect } from 'chai'; -import AppRegistry, { createActivateHelpers } from 'hadron-app-registry'; +import AppRegistry, { + createActivateHelpers, +} from '@mongodb-js/compass-app-registry'; import { MongoDBInstance } from 'mongodb-instance-model'; import { diff --git a/packages/compass-schema-validation/src/stores/store.ts b/packages/compass-schema-validation/src/stores/store.ts index 0bcea1a7b95..7c0240337cf 100644 --- a/packages/compass-schema-validation/src/stores/store.ts +++ b/packages/compass-schema-validation/src/stores/store.ts @@ -7,7 +7,10 @@ import { activateValidation } from '../modules/validation'; import { editModeChanged } from '../modules/edit-mode'; import semver from 'semver'; import type { CollectionTabPluginMetadata } from '@mongodb-js/compass-collection'; -import type { ActivateHelpers, AppRegistry } from 'hadron-app-registry'; +import type { + ActivateHelpers, + AppRegistry, +} from '@mongodb-js/compass-app-registry'; import type { ConnectionInfoRef } from '@mongodb-js/compass-connections/provider'; import type { MongoDBInstance } from '@mongodb-js/compass-app-stores/provider'; import type { PreferencesAccess } from 'compass-preferences-model'; diff --git a/packages/compass-schema/package.json b/packages/compass-schema/package.json index 66bbe534cbf..d887e8fefc3 100644 --- a/packages/compass-schema/package.json +++ b/packages/compass-schema/package.json @@ -83,7 +83,7 @@ "bson": "^6.10.3", "compass-preferences-model": "^2.41.0", "d3": "^3.5.17", - "hadron-app-registry": "^9.4.11", + "@mongodb-js/compass-app-registry": "^9.4.11", "hadron-document": "^8.8.12", "leaflet": "^1.5.1", "leaflet-defaulticon-compatibility": "^0.1.1", diff --git a/packages/compass-schema/src/index.ts b/packages/compass-schema/src/index.ts index 8ecd87046b2..0e96a892797 100644 --- a/packages/compass-schema/src/index.ts +++ b/packages/compass-schema/src/index.ts @@ -6,7 +6,7 @@ import { } from '@mongodb-js/compass-connections/provider'; import CompassSchema from './components/compass-schema'; -import { registerHadronPlugin } from 'hadron-app-registry'; +import { registerCompassPlugin } from '@mongodb-js/compass-app-registry'; import { activateSchemaPlugin } from './stores/store'; import type { RequiredDataServiceProps } from './stores/store'; import { createLoggerLocator } from '@mongodb-js/compass-logging/provider'; @@ -16,7 +16,7 @@ import { fieldStoreServiceLocator } from '@mongodb-js/compass-field-store'; import { queryBarServiceLocator } from '@mongodb-js/compass-query-bar'; import { SchemaTabTitle } from './plugin-title'; -const CompassSchemaHadronPlugin = registerHadronPlugin( +const CompassSchemaPluginProvider = registerCompassPlugin( { name: 'CompassSchemaPlugin', component: function SchemaProvider({ children, ...props }) { @@ -45,7 +45,7 @@ const CompassSchemaHadronPlugin = registerHadronPlugin( export const CompassSchemaPlugin = { name: 'Schema' as const, - provider: CompassSchemaHadronPlugin, + provider: CompassSchemaPluginProvider, content: CompassSchema as React.FunctionComponent /* reflux store */, header: SchemaTabTitle, }; diff --git a/packages/compass-schema/src/stores/store.spec.ts b/packages/compass-schema/src/stores/store.spec.ts index c792cdf2b2f..f4da5019c79 100644 --- a/packages/compass-schema/src/stores/store.spec.ts +++ b/packages/compass-schema/src/stores/store.spec.ts @@ -1,6 +1,8 @@ import { activateSchemaPlugin } from './store'; import type { SchemaStore, SchemaPluginServices } from './store'; -import AppRegistry, { createActivateHelpers } from 'hadron-app-registry'; +import AppRegistry, { + createActivateHelpers, +} from '@mongodb-js/compass-app-registry'; import { expect } from 'chai'; import { waitFor } from '@mongodb-js/testing-library-compass'; diff --git a/packages/compass-schema/src/stores/store.ts b/packages/compass-schema/src/stores/store.ts index 21d5733ca16..069ca5e40a3 100644 --- a/packages/compass-schema/src/stores/store.ts +++ b/packages/compass-schema/src/stores/store.ts @@ -11,8 +11,8 @@ import type { ConnectionInfoRef, DataService as OriginalDataService, } from '@mongodb-js/compass-connections/provider'; -import type { ActivateHelpers } from 'hadron-app-registry'; -import type AppRegistry from 'hadron-app-registry'; +import type { ActivateHelpers } from '@mongodb-js/compass-app-registry'; +import type AppRegistry from '@mongodb-js/compass-app-registry'; import type { PreferencesAccess } from 'compass-preferences-model/provider'; import type { FieldStoreService } from '@mongodb-js/compass-field-store'; import type { QueryBarService } from '@mongodb-js/compass-query-bar'; diff --git a/packages/compass-serverstats/package.json b/packages/compass-serverstats/package.json index e1ac4f3aeea..0d96b4d8740 100644 --- a/packages/compass-serverstats/package.json +++ b/packages/compass-serverstats/package.json @@ -38,7 +38,7 @@ "d3": "^3.5.17", "d3-timer": "^1.0.3", "debug": "^4.3.4", - "hadron-app-registry": "^9.4.11", + "@mongodb-js/compass-app-registry": "^9.4.11", "lodash": "^4.17.21", "mongodb-ns": "^2.4.2", "prop-types": "^15.7.2", diff --git a/packages/compass-serverstats/src/index.ts b/packages/compass-serverstats/src/index.ts index a2de4a71407..7dd017a94c4 100644 --- a/packages/compass-serverstats/src/index.ts +++ b/packages/compass-serverstats/src/index.ts @@ -1,6 +1,6 @@ import React from 'react'; import { PerformanceComponent } from './components'; -import { registerHadronPlugin } from 'hadron-app-registry'; +import { registerCompassPlugin } from '@mongodb-js/compass-app-registry'; import { dataServiceLocator, type DataServiceLocator, @@ -20,7 +20,7 @@ type PerformancePluginInitialProps = Record; const WorkspaceTab: WorkspacePlugin = { name: WorkspaceName, - provider: registerHadronPlugin( + provider: registerCompassPlugin( { name: WorkspaceName, component: function PerformanceProvider({ children }) { diff --git a/packages/compass-settings/package.json b/packages/compass-settings/package.json index c0b3ac26362..592a9532b4d 100644 --- a/packages/compass-settings/package.json +++ b/packages/compass-settings/package.json @@ -54,7 +54,7 @@ "@mongodb-js/compass-generative-ai": "^0.41.0", "@mongodb-js/compass-logging": "^1.7.2", "compass-preferences-model": "^2.41.0", - "hadron-app-registry": "^9.4.11", + "@mongodb-js/compass-app-registry": "^9.4.11", "hadron-ipc": "^3.5.2", "react": "^17.0.2", "react-redux": "^8.1.3", diff --git a/packages/compass-settings/src/index.ts b/packages/compass-settings/src/index.ts index 0b0fdf3de70..959ec343ee1 100644 --- a/packages/compass-settings/src/index.ts +++ b/packages/compass-settings/src/index.ts @@ -1,4 +1,4 @@ -import { registerHadronPlugin } from 'hadron-app-registry'; +import { registerCompassPlugin } from '@mongodb-js/compass-app-registry'; import { createLoggerLocator } from '@mongodb-js/compass-logging/provider'; import { atlasAuthServiceLocator } from '@mongodb-js/atlas-service/provider'; import { atlasAiServiceLocator } from '@mongodb-js/compass-generative-ai/provider'; @@ -8,7 +8,7 @@ import { onActivated } from './stores'; export type { SettingsTabId } from './stores/settings'; -export const CompassSettingsPlugin = registerHadronPlugin( +export const CompassSettingsPlugin = registerCompassPlugin( { name: 'CompassSettings', component: SettingsPlugin, diff --git a/packages/compass-settings/src/stores/index.ts b/packages/compass-settings/src/stores/index.ts index 1a1ca4dee3f..fffcb7f0d4f 100644 --- a/packages/compass-settings/src/stores/index.ts +++ b/packages/compass-settings/src/stores/index.ts @@ -1,5 +1,5 @@ import { ipcRenderer } from 'hadron-ipc'; -import type AppRegistry from 'hadron-app-registry'; +import type AppRegistry from '@mongodb-js/compass-app-registry'; import type { Reducer, AnyAction } from 'redux'; import { createStore, combineReducers, applyMiddleware } from 'redux'; import type { ThunkAction } from 'redux-thunk'; diff --git a/packages/compass-shell/package.json b/packages/compass-shell/package.json index 08f4f746aea..645b3bb9fb5 100644 --- a/packages/compass-shell/package.json +++ b/packages/compass-shell/package.json @@ -62,7 +62,7 @@ "@mongosh/node-runtime-worker-thread": "^3.3.10", "bson": "^6.10.3", "compass-preferences-model": "^2.41.0", - "hadron-app-registry": "^9.4.11", + "@mongodb-js/compass-app-registry": "^9.4.11", "react": "^17.0.2", "react-redux": "^8.1.3", "redux": "^4.2.1", diff --git a/packages/compass-shell/src/index.ts b/packages/compass-shell/src/index.ts index ac829daea94..a51b82ba65c 100644 --- a/packages/compass-shell/src/index.ts +++ b/packages/compass-shell/src/index.ts @@ -2,7 +2,7 @@ import React from 'react'; import { createLoggerLocator } from '@mongodb-js/compass-logging/provider'; import { telemetryLocator } from '@mongodb-js/compass-telemetry/provider'; import { ShellPlugin, onActivated } from './plugin'; -import { registerHadronPlugin } from 'hadron-app-registry'; +import { registerCompassPlugin } from '@mongodb-js/compass-app-registry'; import { preferencesLocator } from 'compass-preferences-model/provider'; import type { WorkspacePlugin } from '@mongodb-js/compass-workspaces'; import { @@ -15,7 +15,7 @@ import { WorkspaceName, ShellPluginTitleComponent } from './plugin-tab-title'; export const WorkspaceTab: WorkspacePlugin = { name: WorkspaceName, - provider: registerHadronPlugin( + provider: registerCompassPlugin( { name: WorkspaceName, component: function ShellProvider({ children }) { diff --git a/packages/compass-shell/src/plugin.tsx b/packages/compass-shell/src/plugin.tsx index 797997d5513..0637d0017f2 100644 --- a/packages/compass-shell/src/plugin.tsx +++ b/packages/compass-shell/src/plugin.tsx @@ -16,7 +16,7 @@ import reducer, { destroyCurrentRuntime, loadHistory, } from './stores/store'; -import type { ActivateHelpers } from 'hadron-app-registry'; +import type { ActivateHelpers } from '@mongodb-js/compass-app-registry'; import { Theme, ThemeProvider } from '@mongodb-js/compass-components'; const SHELL_THEME = { theme: Theme.Dark, enabled: true }; diff --git a/packages/compass-sidebar/package.json b/packages/compass-sidebar/package.json index ab716aae54a..ac044442ee9 100644 --- a/packages/compass-sidebar/package.json +++ b/packages/compass-sidebar/package.json @@ -59,7 +59,7 @@ "@mongodb-js/compass-workspaces": "^0.42.0", "@mongodb-js/connection-info": "^0.15.2", "compass-preferences-model": "^2.41.0", - "hadron-app-registry": "^9.4.11", + "@mongodb-js/compass-app-registry": "^9.4.11", "lodash": "^4.17.21", "mongodb": "^6.16.0", "mongodb-instance-model": "^12.33.0", diff --git a/packages/compass-sidebar/src/components/multiple-connections/sidebar.spec.tsx b/packages/compass-sidebar/src/components/multiple-connections/sidebar.spec.tsx index 62f0b0fe7ff..86bad50b903 100644 --- a/packages/compass-sidebar/src/components/multiple-connections/sidebar.spec.tsx +++ b/packages/compass-sidebar/src/components/multiple-connections/sidebar.spec.tsx @@ -21,7 +21,7 @@ import { CompassSidebarPlugin, } from '../../index'; import type { ConnectionInfo } from '@mongodb-js/compass-connections/provider'; -import type AppRegistry from '../../../../hadron-app-registry/dist'; +import type AppRegistry from '@mongodb-js/compass-app-registry'; const savedFavoriteConnection: ConnectionInfo = { id: '12345', diff --git a/packages/compass-sidebar/src/index.ts b/packages/compass-sidebar/src/index.ts index dfa27cb08ab..a38fec9d09b 100644 --- a/packages/compass-sidebar/src/index.ts +++ b/packages/compass-sidebar/src/index.ts @@ -1,5 +1,8 @@ -import type { ActivateHelpers } from 'hadron-app-registry'; -import { registerHadronPlugin, type AppRegistry } from 'hadron-app-registry'; +import type { ActivateHelpers } from '@mongodb-js/compass-app-registry'; +import { + registerCompassPlugin, + type AppRegistry, +} from '@mongodb-js/compass-app-registry'; import SidebarPlugin from './plugin'; import { createSidebarStore } from './stores'; import { @@ -13,7 +16,7 @@ import type { Logger } from '@mongodb-js/compass-logging/provider'; import { createLoggerLocator } from '@mongodb-js/compass-logging/provider'; import { AtlasClusterConnectionsOnly } from './components/multiple-connections/connections-navigation'; -export const CompassSidebarPlugin = registerHadronPlugin( +export const CompassSidebarPlugin = registerCompassPlugin( { name: 'CompassSidebar', component: SidebarPlugin, diff --git a/packages/compass-sidebar/src/modules/index.ts b/packages/compass-sidebar/src/modules/index.ts index 5d529d84df9..fd9a271c869 100644 --- a/packages/compass-sidebar/src/modules/index.ts +++ b/packages/compass-sidebar/src/modules/index.ts @@ -9,7 +9,7 @@ import type { ConnectionOptionsState, } from './connection-options'; import connectionOptions from './connection-options'; -import type { AppRegistry } from 'hadron-app-registry'; +import type { AppRegistry } from '@mongodb-js/compass-app-registry'; import type { IsPerformanceTabSupportedState, SetIsPerformanceTabSupportedAction, diff --git a/packages/compass-sidebar/src/modules/instance.spec.ts b/packages/compass-sidebar/src/modules/instance.spec.ts index f48f8b16047..c1c029311f5 100644 --- a/packages/compass-sidebar/src/modules/instance.spec.ts +++ b/packages/compass-sidebar/src/modules/instance.spec.ts @@ -4,7 +4,7 @@ import { spy, stub, type SinonSpy, type SinonStub } from 'sinon'; import type { DataService } from 'mongodb-data-service'; import { setupInstance } from './instance'; import type { RootState } from '.'; -import type AppRegistry from 'hadron-app-registry'; +import type AppRegistry from '@mongodb-js/compass-app-registry'; import type { Logger } from '@mongodb-js/compass-logging'; import type { MongoDBInstance, diff --git a/packages/compass-sidebar/src/stores/store.ts b/packages/compass-sidebar/src/stores/store.ts index 7bde8fdc9ab..5fa82dbe652 100644 --- a/packages/compass-sidebar/src/stores/store.ts +++ b/packages/compass-sidebar/src/stores/store.ts @@ -2,7 +2,10 @@ import { createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; import reducer from '../modules'; import { closeInstance, setupInstance } from '../modules/instance'; -import type { ActivateHelpers, AppRegistry } from 'hadron-app-registry'; +import type { + ActivateHelpers, + AppRegistry, +} from '@mongodb-js/compass-app-registry'; import type { Logger } from '@mongodb-js/compass-logging/provider'; import { type MongoDBInstancesManager, diff --git a/packages/compass-telemetry/package.json b/packages/compass-telemetry/package.json index 191314f6949..452ac3dbb86 100644 --- a/packages/compass-telemetry/package.json +++ b/packages/compass-telemetry/package.json @@ -53,7 +53,7 @@ }, "dependencies": { "@mongodb-js/compass-logging": "^1.7.2", - "hadron-app-registry": "^9.4.11", + "@mongodb-js/compass-app-registry": "^9.4.11", "hadron-ipc": "^3.5.2", "react": "^17.0.2" }, diff --git a/packages/compass-telemetry/src/provider.tsx b/packages/compass-telemetry/src/provider.tsx index 5cf4b56917c..3ff409becb3 100644 --- a/packages/compass-telemetry/src/provider.tsx +++ b/packages/compass-telemetry/src/provider.tsx @@ -1,5 +1,5 @@ import React, { useRef } from 'react'; -import { createServiceLocator } from 'hadron-app-registry'; +import { createServiceLocator } from '@mongodb-js/compass-app-registry'; import { createTrack, type TelemetryServiceOptions } from './generic-track'; import { useLogger } from '@mongodb-js/compass-logging/provider'; import type { TrackFunction } from './types'; diff --git a/packages/compass-web/package.json b/packages/compass-web/package.json index 171718dd25e..f5ba72a15e4 100644 --- a/packages/compass-web/package.json +++ b/packages/compass-web/package.json @@ -114,7 +114,7 @@ "events": "^3.3.0", "express": "^4.21.1", "express-http-proxy": "^2.0.0", - "hadron-app-registry": "^9.4.11", + "@mongodb-js/compass-app-registry": "^9.4.11", "is-ip": "^5.0.1", "lodash": "^4.17.21", "mocha": "^10.2.0", diff --git a/packages/compass-web/src/connection-storage.tsx b/packages/compass-web/src/connection-storage.tsx index f9b4584c152..584421306cb 100644 --- a/packages/compass-web/src/connection-storage.tsx +++ b/packages/compass-web/src/connection-storage.tsx @@ -8,7 +8,7 @@ import { ConnectionStorageProvider, InMemoryConnectionStorage, } from '@mongodb-js/connection-storage/provider'; -import { createServiceProvider } from 'hadron-app-registry'; +import { createServiceProvider } from '@mongodb-js/compass-app-registry'; import type { AtlasService } from '@mongodb-js/atlas-service/provider'; import { atlasServiceLocator } from '@mongodb-js/atlas-service/provider'; import { diff --git a/packages/compass-web/src/entrypoint.tsx b/packages/compass-web/src/entrypoint.tsx index 0a8b9a7e146..4598d8ab30c 100644 --- a/packages/compass-web/src/entrypoint.tsx +++ b/packages/compass-web/src/entrypoint.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useRef } from 'react'; import AppRegistry, { AppRegistryProvider, GlobalAppRegistryProvider, -} from 'hadron-app-registry'; +} from '@mongodb-js/compass-app-registry'; import type { ConnectionInfo } from '@mongodb-js/compass-connections/provider'; import { useConnectionActions } from '@mongodb-js/compass-connections/provider'; import { CompassInstanceStorePlugin } from '@mongodb-js/compass-app-stores'; diff --git a/packages/compass-welcome/package.json b/packages/compass-welcome/package.json index 6b82a1c5787..1e3384c2a67 100644 --- a/packages/compass-welcome/package.json +++ b/packages/compass-welcome/package.json @@ -55,7 +55,7 @@ "@mongodb-js/compass-telemetry": "^1.10.0", "@mongodb-js/compass-workspaces": "^0.42.0", "compass-preferences-model": "^2.41.0", - "hadron-app-registry": "^9.4.11", + "@mongodb-js/compass-app-registry": "^9.4.11", "react": "^17.0.2", "redux": "^4.2.1", "redux-thunk": "^2.4.2" diff --git a/packages/compass-welcome/src/index.ts b/packages/compass-welcome/src/index.ts index 026f8f1bf83..e0928310753 100644 --- a/packages/compass-welcome/src/index.ts +++ b/packages/compass-welcome/src/index.ts @@ -1,5 +1,5 @@ import React from 'react'; -import { registerHadronPlugin } from 'hadron-app-registry'; +import { registerCompassPlugin } from '@mongodb-js/compass-app-registry'; import { createLoggerLocator } from '@mongodb-js/compass-logging/provider'; import { workspacesServiceLocator } from '@mongodb-js/compass-workspaces/provider'; import type { WorkspacePlugin } from '@mongodb-js/compass-workspaces'; @@ -16,7 +16,7 @@ const serviceLocators = { export const DesktopWorkspaceTab: WorkspacePlugin = { name: WorkspaceName, - provider: registerHadronPlugin( + provider: registerCompassPlugin( { name: WorkspaceName, component: function WelcomeProvider({ children }) { @@ -32,7 +32,7 @@ export const DesktopWorkspaceTab: WorkspacePlugin = { export const WebWorkspaceTab: WorkspacePlugin = { name: WorkspaceName, - provider: registerHadronPlugin( + provider: registerCompassPlugin( { name: WorkspaceName, component: function WelcomeProvider({ children }) { diff --git a/packages/compass-welcome/src/stores/index.ts b/packages/compass-welcome/src/stores/index.ts index fa8f89a9417..0f54fc49d3b 100644 --- a/packages/compass-welcome/src/stores/index.ts +++ b/packages/compass-welcome/src/stores/index.ts @@ -1,4 +1,4 @@ -import type AppRegistry from 'hadron-app-registry'; +import type AppRegistry from '@mongodb-js/compass-app-registry'; import { createStore, applyMiddleware, combineReducers } from 'redux'; import thunk from 'redux-thunk'; import type { Logger } from '@mongodb-js/compass-logging/provider'; diff --git a/packages/compass-workspaces/package.json b/packages/compass-workspaces/package.json index cd27d79f7d0..fab0c46b717 100644 --- a/packages/compass-workspaces/package.json +++ b/packages/compass-workspaces/package.json @@ -56,7 +56,7 @@ "@mongodb-js/compass-connections": "^1.61.0", "@mongodb-js/compass-logging": "^1.7.2", "bson": "^6.10.3", - "hadron-app-registry": "^9.4.11", + "@mongodb-js/compass-app-registry": "^9.4.11", "compass-preferences-model": "^2.41.0", "lodash": "^4.17.21", "mongodb-collection-model": "^5.29.2", diff --git a/packages/compass-workspaces/src/components/workspace-tab-context-provider.tsx b/packages/compass-workspaces/src/components/workspace-tab-context-provider.tsx index 587e4822b2b..bb1dfc716f9 100644 --- a/packages/compass-workspaces/src/components/workspace-tab-context-provider.tsx +++ b/packages/compass-workspaces/src/components/workspace-tab-context-provider.tsx @@ -9,7 +9,7 @@ import { useTabState, WorkspaceTabStateProvider, } from './workspace-tab-state-provider'; -import { AppRegistryProvider } from 'hadron-app-registry'; +import { AppRegistryProvider } from '@mongodb-js/compass-app-registry'; import { useWorkspacePlugins } from './workspaces-provider'; function getInitialPropsForWorkspace(tab: WorkspaceTab) { diff --git a/packages/compass-workspaces/src/index.ts b/packages/compass-workspaces/src/index.ts index 61450e48a0c..e87b816e759 100644 --- a/packages/compass-workspaces/src/index.ts +++ b/packages/compass-workspaces/src/index.ts @@ -1,6 +1,6 @@ -import type AppRegistry from 'hadron-app-registry'; -import type { ActivateHelpers } from 'hadron-app-registry'; -import { registerHadronPlugin } from 'hadron-app-registry'; +import type AppRegistry from '@mongodb-js/compass-app-registry'; +import type { ActivateHelpers } from '@mongodb-js/compass-app-registry'; +import { registerCompassPlugin } from '@mongodb-js/compass-app-registry'; import type { OpenWorkspaceOptions, CollectionTabInfo, @@ -221,7 +221,7 @@ export function activateWorkspacePlugin( }; } -const WorkspacesPlugin = registerHadronPlugin( +const WorkspacesPlugin = registerCompassPlugin( { name: 'Workspaces', component: Workspaces, diff --git a/packages/compass-workspaces/src/provider.tsx b/packages/compass-workspaces/src/provider.tsx index a8f8354dc78..aa311c736c1 100644 --- a/packages/compass-workspaces/src/provider.tsx +++ b/packages/compass-workspaces/src/provider.tsx @@ -6,7 +6,7 @@ import { getActiveTab, openWorkspace as openWorkspaceAction, } from './stores/workspaces'; -import { createServiceLocator } from 'hadron-app-registry'; +import { createServiceLocator } from '@mongodb-js/compass-app-registry'; import type { CollectionSubtab, WorkspaceTab } from './types'; import type { WorkspaceDestroyHandler } from './components/workspace-close-handler'; import { useRegisterTabDestroyHandler } from './components/workspace-close-handler'; diff --git a/packages/compass-workspaces/src/stores/workspaces.ts b/packages/compass-workspaces/src/stores/workspaces.ts index bbfcb438f80..32e685d25ec 100644 --- a/packages/compass-workspaces/src/stores/workspaces.ts +++ b/packages/compass-workspaces/src/stores/workspaces.ts @@ -1,7 +1,7 @@ import type { Reducer, AnyAction, Action } from 'redux'; import type { ThunkAction } from 'redux-thunk'; import { ObjectId } from 'bson'; -import AppRegistry from 'hadron-app-registry'; +import AppRegistry from '@mongodb-js/compass-app-registry'; import toNS from 'mongodb-ns'; import type { Workspace, WorkspacesServices, CollectionSubtab } from '..'; import type { WorkspaceTab, WorkspaceTabProps } from '../types'; diff --git a/packages/compass-workspaces/src/types.ts b/packages/compass-workspaces/src/types.ts index f3bf71aff2b..2cbbc9b6458 100644 --- a/packages/compass-workspaces/src/types.ts +++ b/packages/compass-workspaces/src/types.ts @@ -1,4 +1,4 @@ -import type { HadronPluginComponent } from 'hadron-app-registry'; +import type { CompassPluginComponent } from '@mongodb-js/compass-app-registry'; import type { WorkspaceTabCoreProps } from '@mongodb-js/compass-components'; export type CollectionSubtab = @@ -106,7 +106,7 @@ export type PluginHeaderProps = export type WorkspacePlugin = { name: T; - provider: HadronPluginComponent; + provider: CompassPluginComponent; content: (props: WorkspacePluginProps) => React.ReactElement | null; header: (props: PluginHeaderProps) => React.ReactElement | null; }; diff --git a/packages/compass/package.json b/packages/compass/package.json index e80f1213a6f..6b3dbb93c30 100644 --- a/packages/compass/package.json +++ b/packages/compass/package.json @@ -256,7 +256,7 @@ "electron-mocha": "^12.2.0", "ensure-error": "^3.0.1", "glob": "^10.2.5", - "hadron-app-registry": "^9.4.11", + "@mongodb-js/compass-app-registry": "^9.4.11", "hadron-build": "^25.8.2", "hadron-ipc": "^3.5.2", "make-fetch-happen": "^10.2.1", diff --git a/packages/compass/src/app/application.tsx b/packages/compass/src/app/application.tsx index 1c4ed5df370..54e9eeee819 100644 --- a/packages/compass/src/app/application.tsx +++ b/packages/compass/src/app/application.tsx @@ -1,7 +1,7 @@ import { ipcRenderer } from 'hadron-ipc'; import * as remote from '@electron/remote'; import { webUtils, webFrame } from 'electron'; -import { globalAppRegistry } from 'hadron-app-registry'; +import { globalAppRegistry } from '@mongodb-js/compass-app-registry'; import { defaultPreferencesInstance } from 'compass-preferences-model'; import semver from 'semver'; import { CompassElectron } from './components/entrypoint'; diff --git a/packages/compass/src/app/components/entrypoint.tsx b/packages/compass/src/app/components/entrypoint.tsx index 493580e6564..6fb4c62f6fa 100644 --- a/packages/compass/src/app/components/entrypoint.tsx +++ b/packages/compass/src/app/components/entrypoint.tsx @@ -1,5 +1,5 @@ import React, { useRef } from 'react'; -import { AppRegistryProvider } from 'hadron-app-registry'; +import { AppRegistryProvider } from '@mongodb-js/compass-app-registry'; import { defaultPreferencesInstance } from 'compass-preferences-model'; import { PreferencesProvider } from 'compass-preferences-model/provider'; import { CompassAtlasAuthService } from '@mongodb-js/atlas-service/renderer'; diff --git a/packages/compass/src/app/components/home.tsx b/packages/compass/src/app/components/home.tsx index f4ef1537823..19e11af8519 100644 --- a/packages/compass/src/app/components/home.tsx +++ b/packages/compass/src/app/components/home.tsx @@ -18,7 +18,7 @@ import type { SettingsTabId } from '@mongodb-js/compass-settings'; import { CompassSettingsPlugin } from '@mongodb-js/compass-settings'; import { WelcomeModal } from '@mongodb-js/compass-welcome'; import { type ConnectionStorage } from '@mongodb-js/connection-storage/provider'; -import { AppRegistryProvider } from 'hadron-app-registry'; +import { AppRegistryProvider } from '@mongodb-js/compass-app-registry'; import React, { useCallback, useState } from 'react'; import Workspace from './workspace'; import { getExtraConnectionData } from '../utils/telemetry'; diff --git a/packages/connection-storage/package.json b/packages/connection-storage/package.json index 95baed11cfc..d9511e98d3d 100644 --- a/packages/connection-storage/package.json +++ b/packages/connection-storage/package.json @@ -64,7 +64,7 @@ "bson": "^6.10.3", "compass-preferences-model": "^2.41.0", "electron": "^36.4.0", - "hadron-app-registry": "^9.4.11", + "@mongodb-js/compass-app-registry": "^9.4.11", "hadron-ipc": "^3.5.2", "keytar": "^7.9.0", "lodash": "^4.17.21", diff --git a/packages/connection-storage/src/provider.ts b/packages/connection-storage/src/provider.ts index e13ca061ffa..c99edee9ba0 100644 --- a/packages/connection-storage/src/provider.ts +++ b/packages/connection-storage/src/provider.ts @@ -1,5 +1,5 @@ import { createContext, useContext } from 'react'; -import { createServiceLocator } from 'hadron-app-registry'; +import { createServiceLocator } from '@mongodb-js/compass-app-registry'; import { type ConnectionStorage, type ConnectionInfo, diff --git a/packages/databases-collections/package.json b/packages/databases-collections/package.json index 9541f043226..dfab17f64fb 100644 --- a/packages/databases-collections/package.json +++ b/packages/databases-collections/package.json @@ -68,7 +68,7 @@ "@mongodb-js/databases-collections-list": "^1.58.0", "@mongodb-js/my-queries-storage": "^0.28.0", "compass-preferences-model": "^2.41.0", - "hadron-app-registry": "^9.4.11", + "@mongodb-js/compass-app-registry": "^9.4.11", "lodash": "^4.17.21", "mongodb-collection-model": "^5.29.2", "mongodb-database-model": "^2.29.2", diff --git a/packages/databases-collections/src/collections-plugin.tsx b/packages/databases-collections/src/collections-plugin.tsx index e88ac113913..7e0adc48204 100644 --- a/packages/databases-collections/src/collections-plugin.tsx +++ b/packages/databases-collections/src/collections-plugin.tsx @@ -4,7 +4,7 @@ import { mongoDBInstanceLocator, } from '@mongodb-js/compass-app-stores/provider'; import { activatePlugin as activateCollectionsTabPlugin } from './stores/collections-store'; -import { registerHadronPlugin } from 'hadron-app-registry'; +import { registerCompassPlugin } from '@mongodb-js/compass-app-registry'; import { dataServiceLocator, type DataServiceLocator, @@ -13,7 +13,7 @@ import { export const CollectionsWorkspaceName = 'Collections' as const; -export const CollectionsPlugin = registerHadronPlugin( +export const CollectionsPlugin = registerCompassPlugin( { name: 'Collections' as const, component: function CollectionsProvider({ children }) { diff --git a/packages/databases-collections/src/components/create-namespace-modal.spec.tsx b/packages/databases-collections/src/components/create-namespace-modal.spec.tsx index b8c94a44972..e76e31493ce 100644 --- a/packages/databases-collections/src/components/create-namespace-modal.spec.tsx +++ b/packages/databases-collections/src/components/create-namespace-modal.spec.tsx @@ -7,7 +7,7 @@ import { waitFor, userEvent, } from '@mongodb-js/testing-library-compass'; -import type AppRegistry from 'hadron-app-registry'; +import type AppRegistry from '@mongodb-js/compass-app-registry'; import { CreateNamespacePlugin } from '../..'; import { diff --git a/packages/databases-collections/src/components/rename-collection-modal/rename-collection-modal.spec.tsx b/packages/databases-collections/src/components/rename-collection-modal/rename-collection-modal.spec.tsx index 0fb0d6eb40d..67058b5e6e0 100644 --- a/packages/databases-collections/src/components/rename-collection-modal/rename-collection-modal.spec.tsx +++ b/packages/databases-collections/src/components/rename-collection-modal/rename-collection-modal.spec.tsx @@ -10,7 +10,7 @@ import { createDefaultConnectionInfo, } from '@mongodb-js/testing-library-compass'; import { RenameCollectionPlugin } from '../..'; -import type AppRegistry from 'hadron-app-registry'; +import type AppRegistry from '@mongodb-js/compass-app-registry'; describe('RenameCollectionModal [Component]', function () { const connectionId = '12345'; diff --git a/packages/databases-collections/src/databases-plugin.tsx b/packages/databases-collections/src/databases-plugin.tsx index a44875c72fc..2112d878426 100644 --- a/packages/databases-collections/src/databases-plugin.tsx +++ b/packages/databases-collections/src/databases-plugin.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { mongoDBInstanceLocator } from '@mongodb-js/compass-app-stores/provider'; import { activatePlugin as activateDatabasesTabPlugin } from './stores/databases-store'; -import { registerHadronPlugin } from 'hadron-app-registry'; +import { registerCompassPlugin } from '@mongodb-js/compass-app-registry'; import { dataServiceLocator, type DataServiceLocator, @@ -10,7 +10,7 @@ import { export const DatabasesWorkspaceName = 'Databases' as const; -export const DatabasesPlugin = registerHadronPlugin( +export const DatabasesPlugin = registerCompassPlugin( { name: 'Databases' as const, component: function DatabasesProvider({ children }) { diff --git a/packages/databases-collections/src/index.ts b/packages/databases-collections/src/index.ts index 9ed127b5f5b..6503cdb41e7 100644 --- a/packages/databases-collections/src/index.ts +++ b/packages/databases-collections/src/index.ts @@ -1,4 +1,4 @@ -import { registerHadronPlugin } from 'hadron-app-registry'; +import { registerCompassPlugin } from '@mongodb-js/compass-app-registry'; import { createLoggerLocator } from '@mongodb-js/compass-logging/provider'; import { telemetryLocator } from '@mongodb-js/compass-telemetry/provider'; import { connectionsLocator } from '@mongodb-js/compass-connections/provider'; @@ -45,7 +45,7 @@ export const DatabasesWorkspaceTab: WorkspacePlugin< header: DatabasesPluginTitleComponent, }; -export const CreateNamespacePlugin = registerHadronPlugin( +export const CreateNamespacePlugin = registerCompassPlugin( { name: 'CreateNamespace', activate: activateCreateNamespacePlugin, @@ -60,7 +60,7 @@ export const CreateNamespacePlugin = registerHadronPlugin( } ); -export const DropNamespacePlugin = registerHadronPlugin( +export const DropNamespacePlugin = registerCompassPlugin( { name: 'DropNamespace', component: DropNamespaceComponent, @@ -73,7 +73,7 @@ export const DropNamespacePlugin = registerHadronPlugin( } ); -export const RenameCollectionPlugin = registerHadronPlugin( +export const RenameCollectionPlugin = registerCompassPlugin( { name: 'RenameCollectionPlugin', component: MappedRenameCollectionModal, diff --git a/packages/databases-collections/src/modules/databases.ts b/packages/databases-collections/src/modules/databases.ts index a337684f5b4..6eb825de633 100644 --- a/packages/databases-collections/src/modules/databases.ts +++ b/packages/databases-collections/src/modules/databases.ts @@ -1,7 +1,7 @@ import type { Action, AnyAction, Reducer } from 'redux'; import type { MongoDBInstance } from '@mongodb-js/compass-app-stores/provider'; import type { ThunkAction } from 'redux-thunk'; -import type AppRegistry from 'hadron-app-registry'; +import type AppRegistry from '@mongodb-js/compass-app-registry'; function isAction( action: AnyAction, diff --git a/packages/databases-collections/src/modules/rename-collection/rename-collection.spec.ts b/packages/databases-collections/src/modules/rename-collection/rename-collection.spec.ts index ed51e4726d7..a3f0b447dc2 100644 --- a/packages/databases-collections/src/modules/rename-collection/rename-collection.spec.ts +++ b/packages/databases-collections/src/modules/rename-collection/rename-collection.spec.ts @@ -4,7 +4,9 @@ import type { RenameCollectionRootState } from './rename-collection'; import { renameCollection, renameRequestInProgress } from './rename-collection'; import type { ThunkDispatch } from 'redux-thunk'; import type { AnyAction } from 'redux'; -import AppRegistry, { createActivateHelpers } from 'hadron-app-registry'; +import AppRegistry, { + createActivateHelpers, +} from '@mongodb-js/compass-app-registry'; import type { RenameCollectionPluginServices } from '../../stores/rename-collection'; import { activateRenameCollectionPlugin } from '../../stores/rename-collection'; diff --git a/packages/databases-collections/src/stores/collections-store.ts b/packages/databases-collections/src/stores/collections-store.ts index 5a974a85a35..1a860feff09 100644 --- a/packages/databases-collections/src/stores/collections-store.ts +++ b/packages/databases-collections/src/stores/collections-store.ts @@ -2,9 +2,9 @@ import throttle from 'lodash/throttle'; import { createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; import { collectionsReducer } from '../modules'; -import type AppRegistry from 'hadron-app-registry'; +import type AppRegistry from '@mongodb-js/compass-app-registry'; import type { DataService } from '@mongodb-js/compass-connections/provider'; -import type { ActivateHelpers } from 'hadron-app-registry'; +import type { ActivateHelpers } from '@mongodb-js/compass-app-registry'; import { collectionsChanged, instanceChanged } from '../modules/collections'; import type { MongoDBInstance, diff --git a/packages/databases-collections/src/stores/create-namespace.spec.tsx b/packages/databases-collections/src/stores/create-namespace.spec.tsx index 6bb58becb05..ba291808e9e 100644 --- a/packages/databases-collections/src/stores/create-namespace.spec.tsx +++ b/packages/databases-collections/src/stores/create-namespace.spec.tsx @@ -1,7 +1,7 @@ import React from 'react'; import Sinon from 'sinon'; import { CreateNamespacePlugin } from '../index'; -import type AppRegistry from 'hadron-app-registry'; +import type AppRegistry from '@mongodb-js/compass-app-registry'; import { expect } from 'chai'; import { type DataService } from '@mongodb-js/compass-connections/provider'; import { diff --git a/packages/databases-collections/src/stores/create-namespace.ts b/packages/databases-collections/src/stores/create-namespace.ts index 1adc54426fe..86074aca43e 100644 --- a/packages/databases-collections/src/stores/create-namespace.ts +++ b/packages/databases-collections/src/stores/create-namespace.ts @@ -1,4 +1,4 @@ -import type AppRegistry from 'hadron-app-registry'; +import type AppRegistry from '@mongodb-js/compass-app-registry'; import type { ConnectionsService } from '@mongodb-js/compass-connections/provider'; import {} from '@mongodb-js/compass-connections/provider'; import type { MongoDBInstance } from 'mongodb-instance-model'; @@ -15,7 +15,7 @@ import reducer, { } from '../modules/create-namespace'; import type toNS from 'mongodb-ns'; import type { workspacesServiceLocator } from '@mongodb-js/compass-workspaces/provider'; -import type { ActivateHelpers } from 'hadron-app-registry'; +import type { ActivateHelpers } from '@mongodb-js/compass-app-registry'; import { MongoDBInstancesManagerEvents, type MongoDBInstancesManager, diff --git a/packages/databases-collections/src/stores/databases-store.ts b/packages/databases-collections/src/stores/databases-store.ts index 73d515fdbcd..e857a55f6fb 100644 --- a/packages/databases-collections/src/stores/databases-store.ts +++ b/packages/databases-collections/src/stores/databases-store.ts @@ -5,10 +5,10 @@ import databasesReducer, { databasesChanged, instanceChanged, } from '../modules/databases'; -import type AppRegistry from 'hadron-app-registry'; +import type AppRegistry from '@mongodb-js/compass-app-registry'; import type { MongoDBInstance } from '@mongodb-js/compass-app-stores/provider'; import type { DataService } from '@mongodb-js/compass-connections/provider'; -import type { ActivateHelpers } from 'hadron-app-registry'; +import type { ActivateHelpers } from '@mongodb-js/compass-app-registry'; type DatabasesTabServices = { globalAppRegistry: AppRegistry; diff --git a/packages/databases-collections/src/stores/drop-namespace.spec.tsx b/packages/databases-collections/src/stores/drop-namespace.spec.tsx index 0de7e4daa3d..46e4ec925de 100644 --- a/packages/databases-collections/src/stores/drop-namespace.spec.tsx +++ b/packages/databases-collections/src/stores/drop-namespace.spec.tsx @@ -1,7 +1,7 @@ import React from 'react'; import Sinon from 'sinon'; import { DropNamespacePlugin } from '../index'; -import type AppRegistry from 'hadron-app-registry'; +import type AppRegistry from '@mongodb-js/compass-app-registry'; import toNS from 'mongodb-ns'; import { expect } from 'chai'; import { diff --git a/packages/databases-collections/src/stores/drop-namespace.tsx b/packages/databases-collections/src/stores/drop-namespace.tsx index 41398b3bbd3..929c671a7de 100644 --- a/packages/databases-collections/src/stores/drop-namespace.tsx +++ b/packages/databases-collections/src/stores/drop-namespace.tsx @@ -6,9 +6,9 @@ import { ToastArea, } from '@mongodb-js/compass-components'; import type { Logger } from '@mongodb-js/compass-logging/provider'; -import type AppRegistry from 'hadron-app-registry'; +import type AppRegistry from '@mongodb-js/compass-app-registry'; import toNS from 'mongodb-ns'; -import type { ActivateHelpers } from 'hadron-app-registry'; +import type { ActivateHelpers } from '@mongodb-js/compass-app-registry'; import type { TrackFunction } from '@mongodb-js/compass-telemetry'; import type { ConnectionsService } from '@mongodb-js/compass-connections/provider'; diff --git a/packages/databases-collections/src/stores/rename-collection.ts b/packages/databases-collections/src/stores/rename-collection.ts index e4d5bc228ca..7b22d3734bf 100644 --- a/packages/databases-collections/src/stores/rename-collection.ts +++ b/packages/databases-collections/src/stores/rename-collection.ts @@ -1,6 +1,6 @@ import { legacy_createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; -import type AppRegistry from 'hadron-app-registry'; +import type AppRegistry from '@mongodb-js/compass-app-registry'; import type { ConnectionsService } from '@mongodb-js/compass-connections/provider'; import reducer, { open } from '../modules/rename-collection/rename-collection'; import type { @@ -8,7 +8,7 @@ import type { PipelineStorage, } from '@mongodb-js/my-queries-storage/provider'; import { type MongoDBInstancesManager } from '@mongodb-js/compass-app-stores/provider'; -import type { ActivateHelpers } from 'hadron-app-registry'; +import type { ActivateHelpers } from '@mongodb-js/compass-app-registry'; export type RenameCollectionPluginServices = { globalAppRegistry: AppRegistry; diff --git a/packages/hadron-ipc/README.md b/packages/hadron-ipc/README.md index 0c330c7737c..425fc5fc2b1 100644 --- a/packages/hadron-ipc/README.md +++ b/packages/hadron-ipc/README.md @@ -8,7 +8,7 @@ Simplified wrapper around Electron's IPC events. process.env.DEBUG = 'hadron-*'; const ipc = require('hadron-ipc'); -const AppRegistry = require('hadron-app-registry'); +const AppRegistry = require('@mongodb-js/compass-app-registry'); const globalAppRegistry = new AppRegistry(); @@ -158,11 +158,11 @@ npm install hadron-ipc - [Electron's ipcMain][ipc-main] - [Electron's ipcRenderer][ipc-renderer] - [Hadron App][hadron-app] -- [Hadron App Registry][hadron-app-registry] +- [Compass App Registry][compass-app-registry] [npm_img]: https://img.shields.io/npm/v/hadron-ipc.svg [npm_url]: https://npmjs.org/package/hadron-ipc [ipc-renderer]: https://electronjs.org/docs/api/ipc-renderer [ipc-main]: https://electronjs.org/docs/api/ipc-mai://electronjs.org/docs/api/ipc-main -[hadron-app]: https://github.com/mongodb-js/hadron-app -[hadron-app-registry]: https://github.com/mongodb-js/hadron-app-registr://github.com/mongodb-js/hadron-app-registry +[hadron-app]: https://github.com/mongodb-js/compass/tree/main/packages/hadron-app +[compass-app-registry]: https://github.com/mongodb-js/compass/tree/main/packages/compass-app-registry diff --git a/packages/my-queries-storage/package.json b/packages/my-queries-storage/package.json index 7eb8941191f..872bfdff88d 100644 --- a/packages/my-queries-storage/package.json +++ b/packages/my-queries-storage/package.json @@ -74,7 +74,7 @@ "@mongodb-js/compass-editor": "^0.41.0", "@mongodb-js/compass-user-data": "^0.7.2", "bson": "^6.10.3", - "hadron-app-registry": "^9.4.11", + "@mongodb-js/compass-app-registry": "^9.4.11", "react": "^17.0.2" } } diff --git a/packages/my-queries-storage/src/provider.ts b/packages/my-queries-storage/src/provider.ts index 1b83eb7ccbb..1d90f958b16 100644 --- a/packages/my-queries-storage/src/provider.ts +++ b/packages/my-queries-storage/src/provider.ts @@ -2,7 +2,7 @@ import { createContext, useContext } from 'react'; import type { QueryStorageOptions } from './compass-query-storage'; import type { PipelineStorage } from './pipeline-storage'; import type { FavoriteQueryStorage, RecentQueryStorage } from './query-storage'; -import { createServiceLocator } from 'hadron-app-registry'; +import { createServiceLocator } from '@mongodb-js/compass-app-registry'; export type { PipelineStorage, FavoriteQueryStorage, RecentQueryStorage }; diff --git a/scripts/create-workspace.js b/scripts/create-workspace.js index 70aa3d72d8f..3514be17dbe 100644 --- a/scripts/create-workspace.js +++ b/scripts/create-workspace.js @@ -296,7 +296,7 @@ async function createWorkspace({ typescript: '*', ...(isPublic && { 'gen-esm-wrapper': '*' }), ...(isPlugin && { - 'hadron-app-registry': '*', + '@mongodb-js/compass-app-registry': '*', 'xvfb-maybe': '*', }), }, @@ -388,9 +388,9 @@ module.exports = { const indexSrcPath = path.join(indexSrcDir, 'index.ts'); const indexSrcContent = isPlugin ? ` -import { registerHadronPlugin } from "hadron-app-registry"; +import { registerCompassPlugin } from '@mongodb-js/compass-app-registry'; -const Plugin = registerHadronPlugin({ +const Plugin = registerCompassPlugin({ name: 'Plugin', component: () => null, activate(initialProps, services, activateHelpers) { From 19578f2ee60427e571ae33a0f259a061d8102371 Mon Sep 17 00:00:00 2001 From: "mongodb-devtools-bot[bot]" <189715634+mongodb-devtools-bot[bot]@users.noreply.github.com> Date: Thu, 19 Jun 2025 07:13:15 +0000 Subject: [PATCH 68/76] chore: update AUTHORS, THIRD-PARTY-NOTICES, Security Test Summary --- THIRD-PARTY-NOTICES.md | 2 +- docs/tracking-plan.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/THIRD-PARTY-NOTICES.md b/THIRD-PARTY-NOTICES.md index 8b01b2c54b2..71cd855d15c 100644 --- a/THIRD-PARTY-NOTICES.md +++ b/THIRD-PARTY-NOTICES.md @@ -1,5 +1,5 @@ The following third-party software is used by and included in **Mongodb Compass**. -This document was automatically generated on Wed Jun 18 2025. +This document was automatically generated on Thu Jun 19 2025. ## List of dependencies diff --git a/docs/tracking-plan.md b/docs/tracking-plan.md index bce23c72e62..8facbb105bd 100644 --- a/docs/tracking-plan.md +++ b/docs/tracking-plan.md @@ -6,7 +6,7 @@ > the tracking plan for the specific Compass version you can use the following > URL: `https://github.com/mongodb-js/compass/blob//docs/tracking-plan.md` -Generated on Wed, Jun 18, 2025 +Generated on Thu, Jun 19, 2025 ## Table of Contents From cb9d596bf67e9502003ecae8e96d9135ed2e5d06 Mon Sep 17 00:00:00 2001 From: Gagik Amaryan Date: Thu, 19 Jun 2025 09:52:36 +0200 Subject: [PATCH 69/76] feat(compass-context-menu): add a headless context menu package COMPASS-9386 (#6937) --- package-lock.json | 104 +++++++ packages/compass-context-menu/.depcheckrc | 8 + packages/compass-context-menu/.eslintignore | 2 + packages/compass-context-menu/.eslintrc.js | 9 + packages/compass-context-menu/.mocharc.js | 2 + packages/compass-context-menu/package.json | 72 +++++ .../src/context-menu-content.ts | 24 ++ .../src/context-menu-provider.tsx | 86 ++++++ packages/compass-context-menu/src/index.ts | 7 + packages/compass-context-menu/src/types.ts | 26 ++ .../src/use-context-menu.spec.tsx | 260 ++++++++++++++++++ .../src/use-context-menu.tsx | 61 ++++ .../compass-context-menu/tsconfig-lint.json | 5 + packages/compass-context-menu/tsconfig.json | 8 + 14 files changed, 674 insertions(+) create mode 100644 packages/compass-context-menu/.depcheckrc create mode 100644 packages/compass-context-menu/.eslintignore create mode 100644 packages/compass-context-menu/.eslintrc.js create mode 100644 packages/compass-context-menu/.mocharc.js create mode 100644 packages/compass-context-menu/package.json create mode 100644 packages/compass-context-menu/src/context-menu-content.ts create mode 100644 packages/compass-context-menu/src/context-menu-provider.tsx create mode 100644 packages/compass-context-menu/src/index.ts create mode 100644 packages/compass-context-menu/src/types.ts create mode 100644 packages/compass-context-menu/src/use-context-menu.spec.tsx create mode 100644 packages/compass-context-menu/src/use-context-menu.tsx create mode 100644 packages/compass-context-menu/tsconfig-lint.json create mode 100644 packages/compass-context-menu/tsconfig.json diff --git a/package-lock.json b/package-lock.json index a3533acc313..3bafa1def9b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8338,6 +8338,10 @@ "resolved": "packages/compass-connections-navigation", "link": true }, + "node_modules/@mongodb-js/compass-context-menu": { + "resolved": "packages/compass-context-menu", + "link": true + }, "node_modules/@mongodb-js/compass-crud": { "resolved": "packages/compass-crud", "link": true @@ -43977,6 +43981,62 @@ "node": ">=0.3.1" } }, + "packages/compass-context-menu": { + "name": "@mongodb-js/compass-context-menu", + "version": "0.0.1", + "license": "SSPL", + "dependencies": { + "react": "^17.0.2" + }, + "devDependencies": { + "@mongodb-js/eslint-config-compass": "^1.3.8", + "@mongodb-js/mocha-config-compass": "^1.6.8", + "@mongodb-js/prettier-config-compass": "^1.2.8", + "@mongodb-js/testing-library-compass": "^1.3.1", + "@mongodb-js/tsconfig-compass": "^1.2.8", + "@types/chai": "^4.2.21", + "@types/mocha": "^9.0.0", + "@types/react": "^17.0.5", + "@types/sinon-chai": "^3.2.5", + "chai": "^4.3.6", + "depcheck": "^1.4.1", + "gen-esm-wrapper": "^1.1.0", + "mocha": "^10.2.0", + "nyc": "^15.1.0", + "sinon": "^9.2.3", + "typescript": "^5.0.4" + } + }, + "packages/compass-context-menu/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "packages/compass-context-menu/node_modules/sinon": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.4.tgz", + "integrity": "sha512-zljcULZQsJxVra28qIAL6ow1Z9tpattkCTEJR4RBP3TGc00FcttsP5pK284Nas5WjMZU5Yzy3kAIp3B3KRf5Yg==", + "deprecated": "16.1.1", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^1.8.1", + "@sinonjs/fake-timers": "^6.0.1", + "@sinonjs/samsam": "^5.3.1", + "diff": "^4.0.2", + "nise": "^4.0.4", + "supports-color": "^7.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, "packages/compass-crud": { "name": "@mongodb-js/compass-crud", "version": "13.61.0", @@ -57062,6 +57122,50 @@ } } }, + "@mongodb-js/compass-context-menu": { + "version": "file:packages/compass-context-menu", + "requires": { + "@mongodb-js/eslint-config-compass": "^1.3.8", + "@mongodb-js/mocha-config-compass": "^1.6.8", + "@mongodb-js/prettier-config-compass": "^1.2.8", + "@mongodb-js/testing-library-compass": "^1.3.1", + "@mongodb-js/tsconfig-compass": "^1.2.8", + "@types/chai": "^4.2.21", + "@types/mocha": "^9.0.0", + "@types/react": "^17.0.5", + "@types/sinon-chai": "^3.2.5", + "chai": "^4.3.6", + "depcheck": "^1.4.1", + "gen-esm-wrapper": "^1.1.0", + "mocha": "^10.2.0", + "nyc": "^15.1.0", + "react": "^17.0.2", + "sinon": "^9.2.3", + "typescript": "^5.0.4" + }, + "dependencies": { + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "sinon": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.4.tgz", + "integrity": "sha512-zljcULZQsJxVra28qIAL6ow1Z9tpattkCTEJR4RBP3TGc00FcttsP5pK284Nas5WjMZU5Yzy3kAIp3B3KRf5Yg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.8.1", + "@sinonjs/fake-timers": "^6.0.1", + "@sinonjs/samsam": "^5.3.1", + "diff": "^4.0.2", + "nise": "^4.0.4", + "supports-color": "^7.1.0" + } + } + } + }, "@mongodb-js/compass-crud": { "version": "file:packages/compass-crud", "requires": { diff --git a/packages/compass-context-menu/.depcheckrc b/packages/compass-context-menu/.depcheckrc new file mode 100644 index 00000000000..ab0ef21b740 --- /dev/null +++ b/packages/compass-context-menu/.depcheckrc @@ -0,0 +1,8 @@ +ignores: + - '@mongodb-js/prettier-config-compass' + - '@mongodb-js/tsconfig-compass' + - '@types/chai' + - '@types/sinon-chai' + - 'sinon' +ignore-patterns: + - 'dist' diff --git a/packages/compass-context-menu/.eslintignore b/packages/compass-context-menu/.eslintignore new file mode 100644 index 00000000000..85a8a75e68c --- /dev/null +++ b/packages/compass-context-menu/.eslintignore @@ -0,0 +1,2 @@ +.nyc-output +dist diff --git a/packages/compass-context-menu/.eslintrc.js b/packages/compass-context-menu/.eslintrc.js new file mode 100644 index 00000000000..9c3ab95632f --- /dev/null +++ b/packages/compass-context-menu/.eslintrc.js @@ -0,0 +1,9 @@ +'use strict'; +module.exports = { + root: true, + extends: ['@mongodb-js/eslint-config-compass'], + parserOptions: { + tsconfigRootDir: __dirname, + project: ['./tsconfig-lint.json'], + }, +}; diff --git a/packages/compass-context-menu/.mocharc.js b/packages/compass-context-menu/.mocharc.js new file mode 100644 index 00000000000..5a33f216327 --- /dev/null +++ b/packages/compass-context-menu/.mocharc.js @@ -0,0 +1,2 @@ +'use strict'; +module.exports = require('@mongodb-js/mocha-config-compass/react'); diff --git a/packages/compass-context-menu/package.json b/packages/compass-context-menu/package.json new file mode 100644 index 00000000000..67099115f92 --- /dev/null +++ b/packages/compass-context-menu/package.json @@ -0,0 +1,72 @@ +{ + "name": "@mongodb-js/compass-context-menu", + "author": { + "name": "MongoDB Inc", + "email": "compass@mongodb.com" + }, + "publishConfig": { + "access": "public" + }, + "bugs": { + "url": "https://jira.mongodb.org/projects/COMPASS/issues", + "email": "compass@mongodb.com" + }, + "homepage": "https://github.com/mongodb-js/compass", + "version": "0.0.1", + "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", + "prepublishOnly": "npm run compile && compass-scripts check-exports-exist", + "compile": "tsc -p tsconfig.json && gen-esm-wrapper . ./dist/.esm-wrapper.mjs", + "typecheck": "tsc -p tsconfig-lint.json --noEmit", + "eslint": "eslint-compass", + "prettier": "prettier-compass", + "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-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", + "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." + }, + "dependencies": { + "react": "^17.0.2" + }, + "devDependencies": { + "@mongodb-js/eslint-config-compass": "^1.3.8", + "@mongodb-js/mocha-config-compass": "^1.6.8", + "@mongodb-js/prettier-config-compass": "^1.2.8", + "@mongodb-js/testing-library-compass": "^1.3.1", + "@mongodb-js/tsconfig-compass": "^1.2.8", + "@types/chai": "^4.2.21", + "@types/mocha": "^9.0.0", + "@types/react": "^17.0.5", + "@types/sinon-chai": "^3.2.5", + "chai": "^4.3.6", + "depcheck": "^1.4.1", + "gen-esm-wrapper": "^1.1.0", + "mocha": "^10.2.0", + "nyc": "^15.1.0", + "sinon": "^9.2.3", + "typescript": "^5.0.4" + } +} diff --git a/packages/compass-context-menu/src/context-menu-content.ts b/packages/compass-context-menu/src/context-menu-content.ts new file mode 100644 index 00000000000..c301983a679 --- /dev/null +++ b/packages/compass-context-menu/src/context-menu-content.ts @@ -0,0 +1,24 @@ +import type { ContextMenuItemGroup } from './types'; + +const CONTEXT_MENUS_SYMBOL = Symbol('context_menus'); + +export type EnhancedMouseEvent = MouseEvent & { + [CONTEXT_MENUS_SYMBOL]?: ContextMenuItemGroup[]; +}; + +export function getContextMenuContent( + event: EnhancedMouseEvent +): ContextMenuItemGroup[] { + return event[CONTEXT_MENUS_SYMBOL] ?? []; +} + +export function appendContextMenuContent( + event: EnhancedMouseEvent, + content: ContextMenuItemGroup +) { + // Initialize if not already patched + if (!event[CONTEXT_MENUS_SYMBOL]) { + event[CONTEXT_MENUS_SYMBOL] = []; + } + event[CONTEXT_MENUS_SYMBOL].push(content); +} diff --git a/packages/compass-context-menu/src/context-menu-provider.tsx b/packages/compass-context-menu/src/context-menu-provider.tsx new file mode 100644 index 00000000000..01b4e6b338c --- /dev/null +++ b/packages/compass-context-menu/src/context-menu-provider.tsx @@ -0,0 +1,86 @@ +import React, { + useCallback, + useEffect, + useState, + useMemo, + createContext, +} from 'react'; +import type { ContextMenuContextType, ContextMenuState } from './types'; +import type { EnhancedMouseEvent } from './context-menu-content'; +import { getContextMenuContent } from './context-menu-content'; + +export const ContextMenuContext = createContext( + null +); + +export function ContextMenuProvider({ + children, + menuWrapper, +}: { + children: React.ReactNode; + menuWrapper: React.ComponentType<{ + menu: ContextMenuState & { close: () => void }; + }>; +}) { + const [menu, setMenu] = useState({ + isOpen: false, + itemGroups: [], + position: { x: 0, y: 0 }, + }); + const close = useCallback(() => setMenu({ ...menu, isOpen: false }), [menu]); + + const handleClosingEvent = useCallback( + (event: Event) => { + if (!event.defaultPrevented) { + setMenu({ ...menu, isOpen: false }); + } + }, + [menu] + ); + + useEffect(() => { + function handleContextMenu(event: MouseEvent) { + event.preventDefault(); + + const itemGroups = getContextMenuContent(event as EnhancedMouseEvent); + + if (itemGroups.length === 0) { + return; + } + + setMenu({ + isOpen: true, + itemGroups, + position: { + // TODO: Fix handling offset while scrolling + x: event.clientX, + y: event.clientY, + }, + }); + } + + document.addEventListener('contextmenu', handleContextMenu); + window.addEventListener('resize', handleClosingEvent); + + return () => { + document.removeEventListener('contextmenu', handleContextMenu); + window.removeEventListener('resize', handleClosingEvent); + }; + }, [handleClosingEvent]); + + const value = useMemo( + () => ({ + close, + }), + [close] + ); + + const Wrapper = menuWrapper ?? React.Fragment; + + return ( + + {children} + + + ); +} diff --git a/packages/compass-context-menu/src/index.ts b/packages/compass-context-menu/src/index.ts new file mode 100644 index 00000000000..75d933ef767 --- /dev/null +++ b/packages/compass-context-menu/src/index.ts @@ -0,0 +1,7 @@ +export { useContextMenu } from './use-context-menu'; +export { ContextMenuProvider } from './context-menu-provider'; +export type { + ContextMenuItem, + ContextMenuItemGroup, + ContextMenuWrapperProps, +} from './types'; diff --git a/packages/compass-context-menu/src/types.ts b/packages/compass-context-menu/src/types.ts new file mode 100644 index 00000000000..163abe56132 --- /dev/null +++ b/packages/compass-context-menu/src/types.ts @@ -0,0 +1,26 @@ +export interface ContextMenuItemGroup { + items: ContextMenuItem[]; + originListener: (event: MouseEvent) => void; +} + +export type ContextMenuState = { + isOpen: boolean; + itemGroups: ContextMenuItemGroup[]; + position: { + x: number; + y: number; + }; +}; + +export type ContextMenuWrapperProps = { + menu: ContextMenuState & { close: () => void }; +}; + +export type ContextMenuContextType = { + close(): void; +}; + +export type ContextMenuItem = { + label: string; + onAction: (event: React.KeyboardEvent | React.MouseEvent) => void; +}; diff --git a/packages/compass-context-menu/src/use-context-menu.spec.tsx b/packages/compass-context-menu/src/use-context-menu.spec.tsx new file mode 100644 index 00000000000..cfe0bfc7ef4 --- /dev/null +++ b/packages/compass-context-menu/src/use-context-menu.spec.tsx @@ -0,0 +1,260 @@ +import React from 'react'; +import { render, screen, userEvent } from '@mongodb-js/testing-library-compass'; +import { expect } from 'chai'; +import sinon from 'sinon'; +import { useContextMenu } from './use-context-menu'; +import { ContextMenuProvider } from './context-menu-provider'; +import type { ContextMenuItem, ContextMenuWrapperProps } from './types'; + +describe('useContextMenu', function () { + const TestMenu: React.FC = ({ menu }) => ( +
+ {menu.itemGroups.flatMap((group, groupIdx) => + group.items.map((item, idx) => ( +
item.onAction?.(event)} + onKeyDown={(event) => { + if (event.key === 'Enter') { + item.onAction?.(event); + } + }} + > + {item.label} +
+ )) + )} +
+ ); + + const TestComponent = ({ + onRegister, + onAction, + }: { + onRegister?: (ref: any) => void; + onAction?: (id: number) => void; + }) => { + const contextMenu = useContextMenu(); + const items: ContextMenuItem[] = [ + { + label: 'Test Item', + onAction: () => onAction?.(1), + }, + ]; + const ref = contextMenu.registerItems(items); + + React.useEffect(() => { + onRegister?.(ref); + }, [ref, onRegister]); + + return ( +
+ Test Component +
+ ); + }; + + const ParentComponent = ({ + onAction, + children, + }: { + onAction?: (id: number) => void; + children?: React.ReactNode; + }) => { + const contextMenu = useContextMenu(); + const parentItems: ContextMenuItem[] = [ + { + label: 'Parent Item 1', + onAction: () => onAction?.(1), + }, + { + label: 'Parent Item 2', + onAction: () => onAction?.(2), + }, + ]; + const ref = contextMenu.registerItems(parentItems); + + return ( +
+
Parent Component
+ {children} +
+ ); + }; + + const ChildComponent = ({ + onAction, + }: { + onAction?: (id: number) => void; + }) => { + const contextMenu = useContextMenu(); + const childItems: ContextMenuItem[] = [ + { + label: 'Child Item 1', + onAction: () => onAction?.(1), + }, + { + label: 'Child Item 2', + onAction: () => onAction?.(2), + }, + ]; + const ref = contextMenu.registerItems(childItems); + + return ( +
+ Child Component +
+ ); + }; + + describe('when used outside provider', function () { + it('throws an error', function () { + expect(() => { + render(); + }).to.throw('useContextMenu called outside of the provider'); + }); + }); + + describe('with a valid provider', function () { + beforeEach(() => { + // Create the container for the context menu portal + const container = document.createElement('div'); + container.id = 'context-menu-container'; + document.body.appendChild(container); + }); + + afterEach(() => { + // Clean up the container + const container = document.getElementById('context-menu-container'); + if (container) { + document.body.removeChild(container); + } + }); + + it('renders without error', function () { + render( + + + + ); + + expect(screen.getByTestId('test-trigger')).to.exist; + }); + + it('registers context menu event listener', function () { + const onRegister = sinon.spy(); + + render( + + + + ); + + expect(onRegister).to.have.been.calledOnce; + expect(onRegister.firstCall.args[0]).to.be.a('function'); + }); + + it('shows context menu on right click', function () { + render( + + + + ); + + const trigger = screen.getByTestId('test-trigger'); + userEvent.click(trigger, { button: 2 }); + + // The menu should be rendered in the portal + expect(screen.getByTestId('menu-item-Test Item')).to.exist; + }); + + describe('with nested context menus', function () { + it('shows only parent items when right clicking parent area', function () { + render( + + + + ); + + const parentTrigger = screen.getByTestId('parent-trigger'); + userEvent.click(parentTrigger, { button: 2 }); + + // Should show parent items + expect(screen.getByTestId('menu-item-Parent Item 1')).to.exist; + expect(screen.getByTestId('menu-item-Parent Item 2')).to.exist; + + // Should not show child items + expect(() => screen.getByTestId('menu-item-Child Item 1')).to.throw; + expect(() => screen.getByTestId('menu-item-Child Item 2')).to.throw; + }); + + it('shows both parent and child items when right clicking child area', function () { + render( + + + + + + ); + + const childTrigger = screen.getByTestId('child-trigger'); + userEvent.click(childTrigger, { button: 2 }); + + // Should show both parent and child items + expect(screen.getByTestId('menu-item-Parent Item 1')).to.exist; + expect(screen.getByTestId('menu-item-Parent Item 2')).to.exist; + expect(screen.getByTestId('menu-item-Child Item 1')).to.exist; + expect(screen.getByTestId('menu-item-Child Item 2')).to.exist; + }); + + it('triggers only the child action when clicking child menu item', function () { + const parentOnAction = sinon.spy(); + const childOnAction = sinon.spy(); + + render( + + + + + + ); + + const childTrigger = screen.getByTestId('child-trigger'); + userEvent.click(childTrigger, { button: 2 }); + + const childItem1 = screen.getByTestId('menu-item-Child Item 1'); + userEvent.click(childItem1); + + expect(childOnAction).to.have.been.calledOnceWithExactly(1); + expect(parentOnAction).to.not.have.been.called; + expect(() => screen.getByTestId('test-menu')).to.throw; + }); + + it('triggers only the parent action when clicking a parent menu item from child context', function () { + const parentOnAction = sinon.spy(); + const childOnAction = sinon.spy(); + + render( + + + + + + ); + + const childTrigger = screen.getByTestId('child-trigger'); + userEvent.click(childTrigger, { button: 2 }); + + const parentItem1 = screen.getByTestId('menu-item-Parent Item 1'); + userEvent.click(parentItem1); + + expect(parentOnAction).to.have.been.calledOnceWithExactly(1); + expect(childOnAction).to.not.have.been.called; + expect(() => screen.getByTestId('test-menu')).to.throw; + }); + }); + }); +}); diff --git a/packages/compass-context-menu/src/use-context-menu.tsx b/packages/compass-context-menu/src/use-context-menu.tsx new file mode 100644 index 00000000000..a0b874b656c --- /dev/null +++ b/packages/compass-context-menu/src/use-context-menu.tsx @@ -0,0 +1,61 @@ +import type { RefCallback } from 'react'; +import { useContext, useMemo, useRef } from 'react'; +import { ContextMenuContext } from './context-menu-provider'; +import { appendContextMenuContent } from './context-menu-content'; +import type { ContextMenuItem } from './types'; + +export type ContextMenuMethods = { + /** + * Close the context menu. + */ + close: () => void; + /** + * Register the menu items for the context menu. + * @returns a callback ref to be passed onto the element responsible for triggering the menu. + */ + registerItems: (items: T[]) => RefCallback; +}; + +export function useContextMenu< + T extends ContextMenuItem = ContextMenuItem +>(): ContextMenuMethods { + const context = useContext(ContextMenuContext); + const previous = useRef void]>( + null + ); + + return useMemo(() => { + if (!context) { + throw new Error('useContextMenu called outside of the provider'); + } + + return { + close: context.close.bind(context), + /** + * @returns a callback ref, passed onto the element responsible for triggering the menu. + */ + registerItems(items: ContextMenuItem[]) { + function listener(event: MouseEvent): void { + appendContextMenuContent(event, { + items, + originListener: listener, + }); + } + + return (trigger: HTMLElement | null) => { + if (previous.current) { + const [previousTrigger, previousListener] = previous.current; + previousTrigger.removeEventListener( + 'contextmenu', + previousListener + ); + } + if (trigger) { + trigger.addEventListener('contextmenu', listener); + previous.current = [trigger, listener]; + } + }; + }, + }; + }, [context]); +} diff --git a/packages/compass-context-menu/tsconfig-lint.json b/packages/compass-context-menu/tsconfig-lint.json new file mode 100644 index 00000000000..6bdef84f322 --- /dev/null +++ b/packages/compass-context-menu/tsconfig-lint.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.json", + "include": ["**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/compass-context-menu/tsconfig.json b/packages/compass-context-menu/tsconfig.json new file mode 100644 index 00000000000..79bc84584ce --- /dev/null +++ b/packages/compass-context-menu/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@mongodb-js/tsconfig-compass/tsconfig.react.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": ["src/**/*"], + "exclude": ["./src/**/*.spec.*"] +} From 6dadf25564cdfa7708ae962c16f31642e1e53124 Mon Sep 17 00:00:00 2001 From: gagik Date: Thu, 19 Jun 2025 10:12:09 +0200 Subject: [PATCH 70/76] fix: correct wrapper use --- .../src/components/context-menu.spec.tsx | 2 +- .../src/components/context-menu.tsx | 2 +- .../src/context-menu-provider.spec.tsx | 6 +++--- .../src/use-context-menu.spec.tsx | 14 +++++++------- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/compass-components/src/components/context-menu.spec.tsx b/packages/compass-components/src/components/context-menu.spec.tsx index d2ecaa63cdf..aa4aee700df 100644 --- a/packages/compass-components/src/components/context-menu.spec.tsx +++ b/packages/compass-components/src/components/context-menu.spec.tsx @@ -38,7 +38,7 @@ describe('useContextMenuItems', function () { expect(() => { render( - + ); diff --git a/packages/compass-components/src/components/context-menu.tsx b/packages/compass-components/src/components/context-menu.tsx index c12ff5fdd3e..0cb8d38b6a3 100644 --- a/packages/compass-components/src/components/context-menu.tsx +++ b/packages/compass-components/src/components/context-menu.tsx @@ -14,7 +14,7 @@ export function ContextMenuProvider({ children: React.ReactNode; }) { return ( - + {children} ); diff --git a/packages/compass-context-menu/src/context-menu-provider.spec.tsx b/packages/compass-context-menu/src/context-menu-provider.spec.tsx index 7e39fdee800..0cd6f23e492 100644 --- a/packages/compass-context-menu/src/context-menu-provider.spec.tsx +++ b/packages/compass-context-menu/src/context-menu-provider.spec.tsx @@ -17,9 +17,9 @@ describe('ContextMenuProvider', function () { it('throws an error when providers are nested', function () { expect(() => { render( - +
- +
@@ -34,7 +34,7 @@ describe('ContextMenuProvider', function () { describe('when not nested', function () { it('renders without error', function () { render( - + ); diff --git a/packages/compass-context-menu/src/use-context-menu.spec.tsx b/packages/compass-context-menu/src/use-context-menu.spec.tsx index cfe0bfc7ef4..fb17fa0e66a 100644 --- a/packages/compass-context-menu/src/use-context-menu.spec.tsx +++ b/packages/compass-context-menu/src/use-context-menu.spec.tsx @@ -136,7 +136,7 @@ describe('useContextMenu', function () { it('renders without error', function () { render( - + ); @@ -148,7 +148,7 @@ describe('useContextMenu', function () { const onRegister = sinon.spy(); render( - + ); @@ -159,7 +159,7 @@ describe('useContextMenu', function () { it('shows context menu on right click', function () { render( - + ); @@ -174,7 +174,7 @@ describe('useContextMenu', function () { describe('with nested context menus', function () { it('shows only parent items when right clicking parent area', function () { render( - + ); @@ -193,7 +193,7 @@ describe('useContextMenu', function () { it('shows both parent and child items when right clicking child area', function () { render( - + @@ -215,7 +215,7 @@ describe('useContextMenu', function () { const childOnAction = sinon.spy(); render( - + @@ -238,7 +238,7 @@ describe('useContextMenu', function () { const childOnAction = sinon.spy(); render( - + From 77568452dab0e5bb5b3fdde0a3a2a45e7d4162bd Mon Sep 17 00:00:00 2001 From: Gagik Amaryan Date: Thu, 19 Jun 2025 11:55:09 +0200 Subject: [PATCH 71/76] fix: correct Compass Context Menu wrapper usage (#7042) --- .../src/context-menu-provider.spec.tsx | 45 +++++++++++++++++++ .../src/context-menu-provider.tsx | 11 +++++ .../src/use-context-menu.spec.tsx | 14 +++--- 3 files changed, 63 insertions(+), 7 deletions(-) create mode 100644 packages/compass-context-menu/src/context-menu-provider.spec.tsx diff --git a/packages/compass-context-menu/src/context-menu-provider.spec.tsx b/packages/compass-context-menu/src/context-menu-provider.spec.tsx new file mode 100644 index 00000000000..0cd6f23e492 --- /dev/null +++ b/packages/compass-context-menu/src/context-menu-provider.spec.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { render } from '@mongodb-js/testing-library-compass'; +import { expect } from 'chai'; +import { ContextMenuProvider } from './context-menu-provider'; +import type { ContextMenuWrapperProps } from './types'; + +describe('ContextMenuProvider', function () { + const TestMenu: React.FC = () => ( +
Test Menu
+ ); + + const TestComponent = () => ( +
Test Content
+ ); + + describe('when nested', function () { + it('throws an error when providers are nested', function () { + expect(() => { + render( + +
+ + + +
+
+ ); + }).to.throw( + 'Duplicated ContextMenuProvider found. Please remove the nested provider.' + ); + }); + }); + + describe('when not nested', function () { + it('renders without error', function () { + render( + + + + ); + + expect(document.querySelector('[data-testid="test-content"]')).to.exist; + }); + }); +}); diff --git a/packages/compass-context-menu/src/context-menu-provider.tsx b/packages/compass-context-menu/src/context-menu-provider.tsx index 01b4e6b338c..aac3cd92494 100644 --- a/packages/compass-context-menu/src/context-menu-provider.tsx +++ b/packages/compass-context-menu/src/context-menu-provider.tsx @@ -4,6 +4,7 @@ import React, { useState, useMemo, createContext, + useContext, } from 'react'; import type { ContextMenuContextType, ContextMenuState } from './types'; import type { EnhancedMouseEvent } from './context-menu-content'; @@ -22,6 +23,16 @@ export function ContextMenuProvider({ menu: ContextMenuState & { close: () => void }; }>; }) { + // Check if there's already a parent context menu provider + const parentContext = useContext(ContextMenuContext); + + // Prevent accidental nested providers + if (parentContext) { + throw new Error( + 'Duplicated ContextMenuProvider found. Please remove the nested provider.' + ); + } + const [menu, setMenu] = useState({ isOpen: false, itemGroups: [], diff --git a/packages/compass-context-menu/src/use-context-menu.spec.tsx b/packages/compass-context-menu/src/use-context-menu.spec.tsx index cfe0bfc7ef4..fb17fa0e66a 100644 --- a/packages/compass-context-menu/src/use-context-menu.spec.tsx +++ b/packages/compass-context-menu/src/use-context-menu.spec.tsx @@ -136,7 +136,7 @@ describe('useContextMenu', function () { it('renders without error', function () { render( - + ); @@ -148,7 +148,7 @@ describe('useContextMenu', function () { const onRegister = sinon.spy(); render( - + ); @@ -159,7 +159,7 @@ describe('useContextMenu', function () { it('shows context menu on right click', function () { render( - + ); @@ -174,7 +174,7 @@ describe('useContextMenu', function () { describe('with nested context menus', function () { it('shows only parent items when right clicking parent area', function () { render( - + ); @@ -193,7 +193,7 @@ describe('useContextMenu', function () { it('shows both parent and child items when right clicking child area', function () { render( - + @@ -215,7 +215,7 @@ describe('useContextMenu', function () { const childOnAction = sinon.spy(); render( - + @@ -238,7 +238,7 @@ describe('useContextMenu', function () { const childOnAction = sinon.spy(); render( - + From 4dafa7be32311839954ddecda6ef78b4c10e2374 Mon Sep 17 00:00:00 2001 From: gagik Date: Thu, 19 Jun 2025 17:55:34 +0200 Subject: [PATCH 72/76] fix: use render directly for compass-context-menu --- package-lock.json | 5 ++++- packages/compass-components/src/components/context-menu.tsx | 2 +- packages/compass-context-menu/package.json | 1 + .../compass-context-menu/src/context-menu-provider.spec.tsx | 2 +- packages/compass-context-menu/src/render.ts | 5 +++++ packages/compass-context-menu/src/use-context-menu.spec.tsx | 3 ++- 6 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 packages/compass-context-menu/src/render.ts diff --git a/package-lock.json b/package-lock.json index 3bafa1def9b..74c7fb5a9dd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13059,6 +13059,7 @@ "version": "12.1.5", "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-12.1.5.tgz", "integrity": "sha512-OfTXCJUFgjd/digLUuPxa0+/3ZxsQmE7ub9kcbW/wi96Bh3o/p5vrETcBGfP17NWPGqeYYl5LTRpwyGoMC4ysg==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.12.5", "@testing-library/dom": "^8.0.0", @@ -43994,6 +43995,7 @@ "@mongodb-js/prettier-config-compass": "^1.2.8", "@mongodb-js/testing-library-compass": "^1.3.1", "@mongodb-js/tsconfig-compass": "^1.2.8", + "@testing-library/react": "^12.1.5", "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/react": "^17.0.5", @@ -57130,6 +57132,7 @@ "@mongodb-js/prettier-config-compass": "^1.2.8", "@mongodb-js/testing-library-compass": "^1.3.1", "@mongodb-js/tsconfig-compass": "^1.2.8", + "@testing-library/react": "^12.1.5", "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/react": "^17.0.5", @@ -64989,7 +64992,7 @@ "requires": { "@babel/runtime": "^7.12.5", "@testing-library/dom": "^8.0.0", - "@types/react-dom": "^17.0.25" + "@types/react-dom": "<18.0.0" } }, "@testing-library/react-hooks": { diff --git a/packages/compass-components/src/components/context-menu.tsx b/packages/compass-components/src/components/context-menu.tsx index 0cb8d38b6a3..b15715e128c 100644 --- a/packages/compass-components/src/components/context-menu.tsx +++ b/packages/compass-components/src/components/context-menu.tsx @@ -6,7 +6,7 @@ import { ContextMenuProvider as ContextMenuProviderBase } from '@mongodb-js/comp import type { ContextMenuItemGroup, ContextMenuWrapperProps, -} from '@mongodb-js/compass-context-menu/dist/types'; +} from '@mongodb-js/compass-context-menu'; export function ContextMenuProvider({ children, diff --git a/packages/compass-context-menu/package.json b/packages/compass-context-menu/package.json index 67099115f92..b810c1a2aff 100644 --- a/packages/compass-context-menu/package.json +++ b/packages/compass-context-menu/package.json @@ -57,6 +57,7 @@ "@mongodb-js/prettier-config-compass": "^1.2.8", "@mongodb-js/testing-library-compass": "^1.3.1", "@mongodb-js/tsconfig-compass": "^1.2.8", + "@testing-library/react": "^12.1.5", "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/react": "^17.0.5", diff --git a/packages/compass-context-menu/src/context-menu-provider.spec.tsx b/packages/compass-context-menu/src/context-menu-provider.spec.tsx index 0cd6f23e492..3173b88f7f6 100644 --- a/packages/compass-context-menu/src/context-menu-provider.spec.tsx +++ b/packages/compass-context-menu/src/context-menu-provider.spec.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { render } from '@mongodb-js/testing-library-compass'; +import { render } from './render'; import { expect } from 'chai'; import { ContextMenuProvider } from './context-menu-provider'; import type { ContextMenuWrapperProps } from './types'; diff --git a/packages/compass-context-menu/src/render.ts b/packages/compass-context-menu/src/render.ts new file mode 100644 index 00000000000..35e6c857ea9 --- /dev/null +++ b/packages/compass-context-menu/src/render.ts @@ -0,0 +1,5 @@ +// We need to import from testing-library/react directly because the wrapping done +// by testing-library-compass already sets up the context menu provider which is not +// useful for our tests. +// eslint-disable-next-line @typescript-eslint/no-restricted-imports +export { render } from '@testing-library/react'; diff --git a/packages/compass-context-menu/src/use-context-menu.spec.tsx b/packages/compass-context-menu/src/use-context-menu.spec.tsx index fb17fa0e66a..fadaffd73a6 100644 --- a/packages/compass-context-menu/src/use-context-menu.spec.tsx +++ b/packages/compass-context-menu/src/use-context-menu.spec.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { render, screen, userEvent } from '@mongodb-js/testing-library-compass'; +import { render } from './render'; +import { screen, userEvent } from '@mongodb-js/testing-library-compass'; import { expect } from 'chai'; import sinon from 'sinon'; import { useContextMenu } from './use-context-menu'; From 915c59763c60398ca89103d673b78cdc039b03ff Mon Sep 17 00:00:00 2001 From: gagik Date: Thu, 19 Jun 2025 18:21:53 +0200 Subject: [PATCH 73/76] fix: use testingLibrary's render This reverts commit 4dafa7be32311839954ddecda6ef78b4c10e2374 and uses testing-library instead. --- package-lock.json | 5 +---- packages/compass-context-menu/package.json | 1 - .../src/context-menu-provider.spec.tsx | 5 ++++- packages/compass-context-menu/src/render.ts | 5 ----- .../compass-context-menu/src/use-context-menu.spec.tsx | 10 ++++++++-- 5 files changed, 13 insertions(+), 13 deletions(-) delete mode 100644 packages/compass-context-menu/src/render.ts diff --git a/package-lock.json b/package-lock.json index 74c7fb5a9dd..3bafa1def9b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13059,7 +13059,6 @@ "version": "12.1.5", "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-12.1.5.tgz", "integrity": "sha512-OfTXCJUFgjd/digLUuPxa0+/3ZxsQmE7ub9kcbW/wi96Bh3o/p5vrETcBGfP17NWPGqeYYl5LTRpwyGoMC4ysg==", - "license": "MIT", "dependencies": { "@babel/runtime": "^7.12.5", "@testing-library/dom": "^8.0.0", @@ -43995,7 +43994,6 @@ "@mongodb-js/prettier-config-compass": "^1.2.8", "@mongodb-js/testing-library-compass": "^1.3.1", "@mongodb-js/tsconfig-compass": "^1.2.8", - "@testing-library/react": "^12.1.5", "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/react": "^17.0.5", @@ -57132,7 +57130,6 @@ "@mongodb-js/prettier-config-compass": "^1.2.8", "@mongodb-js/testing-library-compass": "^1.3.1", "@mongodb-js/tsconfig-compass": "^1.2.8", - "@testing-library/react": "^12.1.5", "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/react": "^17.0.5", @@ -64992,7 +64989,7 @@ "requires": { "@babel/runtime": "^7.12.5", "@testing-library/dom": "^8.0.0", - "@types/react-dom": "<18.0.0" + "@types/react-dom": "^17.0.25" } }, "@testing-library/react-hooks": { diff --git a/packages/compass-context-menu/package.json b/packages/compass-context-menu/package.json index b810c1a2aff..67099115f92 100644 --- a/packages/compass-context-menu/package.json +++ b/packages/compass-context-menu/package.json @@ -57,7 +57,6 @@ "@mongodb-js/prettier-config-compass": "^1.2.8", "@mongodb-js/testing-library-compass": "^1.3.1", "@mongodb-js/tsconfig-compass": "^1.2.8", - "@testing-library/react": "^12.1.5", "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/react": "^17.0.5", diff --git a/packages/compass-context-menu/src/context-menu-provider.spec.tsx b/packages/compass-context-menu/src/context-menu-provider.spec.tsx index 3173b88f7f6..53284272555 100644 --- a/packages/compass-context-menu/src/context-menu-provider.spec.tsx +++ b/packages/compass-context-menu/src/context-menu-provider.spec.tsx @@ -1,9 +1,12 @@ import React from 'react'; -import { render } from './render'; +import { testingLibrary } from '@mongodb-js/testing-library-compass'; import { expect } from 'chai'; import { ContextMenuProvider } from './context-menu-provider'; import type { ContextMenuWrapperProps } from './types'; +// We need to import from testing-library-compass directly to avoid the extra wrapping. +const { render } = testingLibrary; + describe('ContextMenuProvider', function () { const TestMenu: React.FC = () => (
Test Menu
diff --git a/packages/compass-context-menu/src/render.ts b/packages/compass-context-menu/src/render.ts deleted file mode 100644 index 35e6c857ea9..00000000000 --- a/packages/compass-context-menu/src/render.ts +++ /dev/null @@ -1,5 +0,0 @@ -// We need to import from testing-library/react directly because the wrapping done -// by testing-library-compass already sets up the context menu provider which is not -// useful for our tests. -// eslint-disable-next-line @typescript-eslint/no-restricted-imports -export { render } from '@testing-library/react'; diff --git a/packages/compass-context-menu/src/use-context-menu.spec.tsx b/packages/compass-context-menu/src/use-context-menu.spec.tsx index fadaffd73a6..9459f924b05 100644 --- a/packages/compass-context-menu/src/use-context-menu.spec.tsx +++ b/packages/compass-context-menu/src/use-context-menu.spec.tsx @@ -1,12 +1,18 @@ import React from 'react'; -import { render } from './render'; -import { screen, userEvent } from '@mongodb-js/testing-library-compass'; +import { + screen, + userEvent, + testingLibrary, +} from '@mongodb-js/testing-library-compass'; import { expect } from 'chai'; import sinon from 'sinon'; import { useContextMenu } from './use-context-menu'; import { ContextMenuProvider } from './context-menu-provider'; import type { ContextMenuItem, ContextMenuWrapperProps } from './types'; +// We need to import from testing-library-compass directly to avoid the extra wrapping. +const { render } = testingLibrary; + describe('useContextMenu', function () { const TestMenu: React.FC = ({ menu }) => (
From 8f306e41cb8d22e29164bcd379cf62e41069e646 Mon Sep 17 00:00:00 2001 From: gagik Date: Thu, 19 Jun 2025 18:27:56 +0200 Subject: [PATCH 74/76] feat: memoize items for context menu --- configs/eslint-config-compass/index.js | 2 +- .../src/components/context-menu.spec.tsx | 2 +- .../compass-components/src/components/context-menu.tsx | 8 +++++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/configs/eslint-config-compass/index.js b/configs/eslint-config-compass/index.js index a7f2479d9b6..21572f894e9 100644 --- a/configs/eslint-config-compass/index.js +++ b/configs/eslint-config-compass/index.js @@ -46,7 +46,7 @@ const tsxRules = { 'react-hooks/exhaustive-deps': [ 'warn', { - additionalHooks: 'useTrackOnChange', + additionalHooks: '(useTrackOnChange|useContextMenuItems)', }, ], }; diff --git a/packages/compass-components/src/components/context-menu.spec.tsx b/packages/compass-components/src/components/context-menu.spec.tsx index aa4aee700df..51ff62d6b6d 100644 --- a/packages/compass-components/src/components/context-menu.spec.tsx +++ b/packages/compass-components/src/components/context-menu.spec.tsx @@ -18,7 +18,7 @@ describe('useContextMenuItems', function () { children?: React.ReactNode; 'data-testid'?: string; }) => { - const ref = useContextMenuItems(items); + const ref = useContextMenuItems(() => items, [items]); return (
diff --git a/packages/compass-components/src/components/context-menu.tsx b/packages/compass-components/src/components/context-menu.tsx index b15715e128c..99abb6472c2 100644 --- a/packages/compass-components/src/components/context-menu.tsx +++ b/packages/compass-components/src/components/context-menu.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useMemo } from 'react'; import { Menu, MenuItem, MenuSeparator } from './leafygreen'; import type { ContextMenuItem } from '@mongodb-js/compass-context-menu'; import { useContextMenu } from '@mongodb-js/compass-context-menu'; @@ -88,8 +88,10 @@ export function ContextMenu({ menu }: ContextMenuWrapperProps) { } export function useContextMenuItems( - items: ContextMenuItem[] + getItems: () => ContextMenuItem[], + dependencies: React.DependencyList | undefined ): React.RefCallback { + const memoizedItems = useMemo(getItems, dependencies); const contextMenu = useContextMenu(); - return contextMenu.registerItems(items); + return contextMenu.registerItems(memoizedItems); } From aa52fb7f0153bd61a847d47b2883d2bb7a8c66ce Mon Sep 17 00:00:00 2001 From: gagik Date: Thu, 19 Jun 2025 18:42:02 +0200 Subject: [PATCH 75/76] fix: adjust dependencies --- package-lock.json | 2 ++ packages/compass-components/package.json | 1 + packages/compass-components/src/components/context-menu.tsx | 1 + 3 files changed, 4 insertions(+) diff --git a/package-lock.json b/package-lock.json index 3bafa1def9b..286d3a3f9e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43583,6 +43583,7 @@ "@leafygreen-ui/tokens": "^2.11.3", "@leafygreen-ui/tooltip": "^13.0.2", "@leafygreen-ui/typography": "^20.0.2", + "@mongodb-js/compass-context-menu": "^0.0.1", "@react-aria/interactions": "^3.9.1", "@react-aria/utils": "^3.13.1", "@react-aria/visually-hidden": "^3.3.1", @@ -56777,6 +56778,7 @@ "@leafygreen-ui/tokens": "^2.11.3", "@leafygreen-ui/tooltip": "^13.0.2", "@leafygreen-ui/typography": "^20.0.2", + "@mongodb-js/compass-context-menu": "^0.0.1", "@mongodb-js/eslint-config-compass": "^1.3.10", "@mongodb-js/mocha-config-compass": "^1.6.8", "@mongodb-js/prettier-config-compass": "^1.2.8", diff --git a/packages/compass-components/package.json b/packages/compass-components/package.json index 14af3a7b98b..e5f44908ba0 100644 --- a/packages/compass-components/package.json +++ b/packages/compass-components/package.json @@ -75,6 +75,7 @@ "@leafygreen-ui/tokens": "^2.11.3", "@leafygreen-ui/tooltip": "^13.0.2", "@leafygreen-ui/typography": "^20.0.2", + "@mongodb-js/compass-context-menu": "^0.0.1", "@react-aria/interactions": "^3.9.1", "@react-aria/utils": "^3.13.1", "@react-aria/visually-hidden": "^3.3.1", diff --git a/packages/compass-components/src/components/context-menu.tsx b/packages/compass-components/src/components/context-menu.tsx index 99abb6472c2..0346589e318 100644 --- a/packages/compass-components/src/components/context-menu.tsx +++ b/packages/compass-components/src/components/context-menu.tsx @@ -91,6 +91,7 @@ export function useContextMenuItems( getItems: () => ContextMenuItem[], dependencies: React.DependencyList | undefined ): React.RefCallback { + // eslint-disable-next-line react-hooks/exhaustive-deps const memoizedItems = useMemo(getItems, dependencies); const contextMenu = useContextMenu(); return contextMenu.registerItems(memoizedItems); From fb9c5996eb87cf7d539363d08a0a941faad57f3f Mon Sep 17 00:00:00 2001 From: gagik Date: Thu, 19 Jun 2025 19:01:40 +0200 Subject: [PATCH 76/76] fix: support nesting --- .../src/components/context-menu.spec.tsx | 17 +++++++---- .../src/context-menu-provider.spec.tsx | 29 ++++++++++--------- .../src/context-menu-provider.tsx | 17 ++++++----- 3 files changed, 36 insertions(+), 27 deletions(-) diff --git a/packages/compass-components/src/components/context-menu.spec.tsx b/packages/compass-components/src/components/context-menu.spec.tsx index 51ff62d6b6d..13fd851ec0c 100644 --- a/packages/compass-components/src/components/context-menu.spec.tsx +++ b/packages/compass-components/src/components/context-menu.spec.tsx @@ -28,7 +28,7 @@ describe('useContextMenuItems', function () { ); }; - it('errors if the component is double wrapped', function () { + it('works with nested providers, using the parent provider', function () { const items = [ { label: 'Test Item', @@ -36,15 +36,20 @@ describe('useContextMenuItems', function () { }, ]; - expect(() => { - render( + const { container } = render( + - ); - }).to.throw( - 'Duplicated ContextMenuProvider found. Please remove the nested provider.' + ); + + // Should only find one context menu (from the parent provider) + expect( + container.querySelectorAll('[data-testid="context-menu"]') + ).to.have.length(1); + // Should still render the trigger + expect(screen.getByTestId(menuTestTriggerId)).to.exist; }); it('renders without error', function () { diff --git a/packages/compass-context-menu/src/context-menu-provider.spec.tsx b/packages/compass-context-menu/src/context-menu-provider.spec.tsx index 53284272555..88d85c1fbcf 100644 --- a/packages/compass-context-menu/src/context-menu-provider.spec.tsx +++ b/packages/compass-context-menu/src/context-menu-provider.spec.tsx @@ -17,20 +17,23 @@ describe('ContextMenuProvider', function () { ); describe('when nested', function () { - it('throws an error when providers are nested', function () { - expect(() => { - render( - -
- - - -
-
- ); - }).to.throw( - 'Duplicated ContextMenuProvider found. Please remove the nested provider.' + it('uses parent provider and does not render duplicate menu wrapper', function () { + const { container } = render( + +
+ + + +
+
); + + // Should only find one test-menu element (from the parent provider) + expect( + container.querySelectorAll('[data-testid="test-menu"]') + ).to.have.length(1); + // Should still render the content + expect(container.querySelector('[data-testid="test-content"]')).to.exist; }); }); diff --git a/packages/compass-context-menu/src/context-menu-provider.tsx b/packages/compass-context-menu/src/context-menu-provider.tsx index aac3cd92494..0c2134f7ec4 100644 --- a/packages/compass-context-menu/src/context-menu-provider.tsx +++ b/packages/compass-context-menu/src/context-menu-provider.tsx @@ -26,13 +26,6 @@ export function ContextMenuProvider({ // Check if there's already a parent context menu provider const parentContext = useContext(ContextMenuContext); - // Prevent accidental nested providers - if (parentContext) { - throw new Error( - 'Duplicated ContextMenuProvider found. Please remove the nested provider.' - ); - } - const [menu, setMenu] = useState({ isOpen: false, itemGroups: [], @@ -50,6 +43,9 @@ export function ContextMenuProvider({ ); useEffect(() => { + // Don't set up event listeners if we have a parent context + if (parentContext) return; + function handleContextMenu(event: MouseEvent) { event.preventDefault(); @@ -77,7 +73,7 @@ export function ContextMenuProvider({ document.removeEventListener('contextmenu', handleContextMenu); window.removeEventListener('resize', handleClosingEvent); }; - }, [handleClosingEvent]); + }, [handleClosingEvent, parentContext]); const value = useMemo( () => ({ @@ -86,6 +82,11 @@ export function ContextMenuProvider({ [close] ); + // If we have a parent context, just render children without the wrapper + if (parentContext) { + return <>{children}; + } + const Wrapper = menuWrapper ?? React.Fragment; return (