diff --git a/package-lock.json b/package-lock.json index 4c733176db1..246954559ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,9 +11,6 @@ "configs/*", "scripts" ], - "dependencies": { - "electron": "^25.9.4" - }, "devDependencies": { "@babel/core": "7.16.0", "@babel/parser": "7.16.0", @@ -44103,8 +44100,6 @@ "mongodb-ns": "^2.4.0", "nyc": "^15.1.0", "prettier": "^2.7.1", - "react-redux": "^8.0.5", - "redux": "^4.2.1", "sinon": "^9.2.3", "xvfb-maybe": "^0.2.1" }, @@ -46029,6 +46024,7 @@ "@mongodb-js/compass-logging": "^1.2.6", "@mongodb-js/compass-settings": "^0.21.1", "@mongodb-js/compass-shell": "^3.19.1", + "@mongodb-js/compass-sidebar": "^5.19.1", "@mongodb-js/compass-welcome": "^0.18.1", "@mongodb-js/connection-storage": "^0.6.6", "compass-preferences-model": "^2.15.6", @@ -46073,6 +46069,7 @@ "@mongodb-js/compass-logging": "^1.2.6", "@mongodb-js/compass-settings": "^0.21.1", "@mongodb-js/compass-shell": "^3.19.1", + "@mongodb-js/compass-sidebar": "^5.19.1", "@mongodb-js/compass-welcome": "^0.18.1", "@mongodb-js/connection-storage": "^0.6.6", "compass-preferences-model": "^2.15.6", @@ -47172,6 +47169,7 @@ "version": "5.19.1", "license": "SSPL", "dependencies": { + "@mongodb-js/compass-app-stores": "^7.6.1", "@mongodb-js/compass-components": "^1.19.0", "@mongodb-js/compass-databases-navigation": "^1.19.1", "@mongodb-js/compass-logging": "^1.2.6", @@ -47180,6 +47178,8 @@ "@mongodb-js/connection-storage": "^0.6.6", "@mongodb-js/mongodb-redux-common": "^2.0.15", "compass-preferences-model": "^2.15.6", + "hadron-app-registry": "^9.0.14", + "mongodb-data-service": "^22.15.1", "mongodb-instance-model": "^12.15.1" }, "devDependencies": { @@ -47200,7 +47200,6 @@ "debug": "^4.2.0", "depcheck": "^1.4.1", "eslint": "^7.25.0", - "hadron-app-registry": "^9.0.14", "lodash": "^4.17.21", "mocha": "^10.2.0", "mongodb-ns": "^2.4.0", @@ -47215,6 +47214,7 @@ "xvfb-maybe": "^0.2.1" }, "peerDependencies": { + "@mongodb-js/compass-app-stores": "^7.6.1", "@mongodb-js/compass-components": "^1.19.0", "@mongodb-js/compass-databases-navigation": "^1.19.1", "@mongodb-js/compass-logging": "^1.2.6", @@ -47223,6 +47223,8 @@ "@mongodb-js/connection-storage": "^0.6.6", "@mongodb-js/mongodb-redux-common": "^2.0.15", "compass-preferences-model": "^2.15.6", + "hadron-app-registry": "^9.0.14", + "mongodb-data-service": "^22.15.1", "mongodb-instance-model": "^12.15.1", "react": "^17.0.2" } @@ -47814,7 +47816,8 @@ "@mongodb-js/compass-logging": "^1.2.6", "@mongodb-js/databases-collections-list": "^1.19.1", "compass-preferences-model": "^2.15.6", - "hadron-app-registry": "^9.0.14" + "hadron-app-registry": "^9.0.14", + "mongodb-data-service": "^22.15.1" }, "devDependencies": { "@mongodb-js/eslint-config-compass": "^1.0.11", @@ -47834,7 +47837,6 @@ "mocha": "^10.2.0", "mongodb": "^6.0.0", "mongodb-collection-model": "^5.15.1", - "mongodb-data-service": "^22.15.1", "mongodb-instance-model": "^12.15.1", "mongodb-ns": "^2.4.0", "mongodb-query-parser": "^3.1.3", @@ -47854,6 +47856,7 @@ "@mongodb-js/databases-collections-list": "^1.19.1", "compass-preferences-model": "^2.15.6", "hadron-app-registry": "^9.0.14", + "mongodb-data-service": "^22.15.1", "react": "^17.0.2" } }, @@ -58229,8 +58232,6 @@ "mongodb-ns": "^2.4.0", "nyc": "^15.1.0", "prettier": "^2.7.1", - "react-redux": "^8.0.5", - "redux": "^4.2.1", "sinon": "^9.2.3", "xvfb-maybe": "^0.2.1" }, @@ -59196,6 +59197,7 @@ "@mongodb-js/compass-logging": "^1.2.6", "@mongodb-js/compass-settings": "^0.21.1", "@mongodb-js/compass-shell": "^3.19.1", + "@mongodb-js/compass-sidebar": "^5.19.1", "@mongodb-js/compass-welcome": "^0.18.1", "@mongodb-js/connection-storage": "^0.6.6", "@mongodb-js/eslint-config-compass": "^1.0.11", @@ -60228,6 +60230,7 @@ "@mongodb-js/compass-sidebar": { "version": "file:packages/compass-sidebar", "requires": { + "@mongodb-js/compass-app-stores": "^7.6.1", "@mongodb-js/compass-components": "^1.19.0", "@mongodb-js/compass-databases-navigation": "^1.19.1", "@mongodb-js/compass-logging": "^1.2.6", @@ -60256,6 +60259,7 @@ "hadron-app-registry": "^9.0.14", "lodash": "^4.17.21", "mocha": "^10.2.0", + "mongodb-data-service": "^22.15.1", "mongodb-instance-model": "^12.15.1", "mongodb-ns": "^2.4.0", "nyc": "^15.1.0", diff --git a/packages/compass-app-stores/src/provider.tsx b/packages/compass-app-stores/src/provider.ts similarity index 84% rename from packages/compass-app-stores/src/provider.tsx rename to packages/compass-app-stores/src/provider.ts index 6d2c828f6a1..8c59b5f0e7f 100644 --- a/packages/compass-app-stores/src/provider.tsx +++ b/packages/compass-app-stores/src/provider.ts @@ -3,7 +3,7 @@ import { createContext, useContext } from 'react'; export const InstanceContext = createContext(null); -export const useMongoDBInstance = (): MongoDBInstance => { +export const mongoDBInstanceLocator = (): MongoDBInstance => { const instance = useContext(InstanceContext); if (!instance) { throw new Error('No MongoDBInstance available in this context'); diff --git a/packages/compass-home/package.json b/packages/compass-home/package.json index f683a2eed50..16201200d91 100644 --- a/packages/compass-home/package.json +++ b/packages/compass-home/package.json @@ -47,6 +47,7 @@ "@mongodb-js/compass-find-in-page": "^4.19.1", "@mongodb-js/compass-import-export": "^7.19.1", "@mongodb-js/compass-shell": "^3.19.1", + "@mongodb-js/compass-sidebar": "^5.19.1", "compass-preferences-model": "^2.15.6", "hadron-app-registry": "^9.0.14", "hadron-ipc": "^3.2.4", @@ -66,6 +67,7 @@ "@mongodb-js/compass-find-in-page": "^4.19.1", "@mongodb-js/compass-import-export": "^7.19.1", "@mongodb-js/compass-shell": "^3.19.1", + "@mongodb-js/compass-sidebar": "^5.19.1", "compass-preferences-model": "^2.15.6", "hadron-app-registry": "^9.0.14", "hadron-ipc": "^3.2.4", diff --git a/packages/compass-home/src/components/home.spec.tsx b/packages/compass-home/src/components/home.spec.tsx index 040320fa101..93b97d21cdd 100644 --- a/packages/compass-home/src/components/home.spec.tsx +++ b/packages/compass-home/src/components/home.spec.tsx @@ -35,7 +35,7 @@ const createDataService = () => ({ getLastSeenTopology() { return { type: 'Unknown', - servers: [], + servers: ['localhost:27020'], setName: 'foo', }; }, @@ -58,8 +58,6 @@ describe('Home [Component]', function () { 'Collection.Workspace', 'Database.Workspace', 'Instance.Workspace', - 'Sidebar.Component', - 'Global.Shell', ].forEach((name) => testAppRegistry.registerComponent(name, getComponent(name)) ); diff --git a/packages/compass-home/src/components/home.tsx b/packages/compass-home/src/components/home.tsx index 6652d73d087..d4535ff94c3 100644 --- a/packages/compass-home/src/components/home.tsx +++ b/packages/compass-home/src/components/home.tsx @@ -211,8 +211,10 @@ function Home({ }); } + const [connectionInfo, setConnectionInfo] = useState(); const onConnected = useCallback( (connectionInfo: ConnectionInfo, dataService: DataService) => { + setConnectionInfo(connectionInfo); appRegistry.emit( 'data-service-connected', null, // No error connecting. @@ -360,7 +362,10 @@ function Home({ - + diff --git a/packages/compass-home/src/components/workspace.tsx b/packages/compass-home/src/components/workspace.tsx index 517546d70f7..4590352301f 100644 --- a/packages/compass-home/src/components/workspace.tsx +++ b/packages/compass-home/src/components/workspace.tsx @@ -3,7 +3,8 @@ import { css } from '@mongodb-js/compass-components'; import WorkspaceContent from './workspace-content'; import type Namespace from '../types/namespace'; -import { useAppRegistryComponent } from 'hadron-app-registry'; +import type { ConnectionInfo } from '@mongodb-js/connection-storage/renderer'; +import { CompassSidebarPlugin } from '@mongodb-js/compass-sidebar'; import { CompassShellPlugin } from '@mongodb-js/compass-shell'; const verticalSplitStyles = css({ @@ -37,17 +38,17 @@ const shellContainerStyles = css({ export default function Workspace({ namespace, + connectionInfo, }: { namespace: Namespace; + connectionInfo: ConnectionInfo | null | undefined; }): React.ReactElement { - const SidebarComponent = useAppRegistryComponent('Sidebar.Component'); - return ( <>
- {SidebarComponent && } +
diff --git a/packages/compass-sidebar/package.json b/packages/compass-sidebar/package.json index 44fa9b97af2..8d4e2168ea2 100644 --- a/packages/compass-sidebar/package.json +++ b/packages/compass-sidebar/package.json @@ -57,6 +57,7 @@ "reformat": "npm run prettier -- --write . && npm run eslint . --fix" }, "peerDependencies": { + "@mongodb-js/compass-app-stores": "^7.6.1", "@mongodb-js/compass-components": "^1.19.0", "@mongodb-js/compass-databases-navigation": "^1.19.1", "@mongodb-js/compass-logging": "^1.2.6", @@ -65,10 +66,13 @@ "@mongodb-js/connection-storage": "^0.6.6", "@mongodb-js/mongodb-redux-common": "^2.0.15", "compass-preferences-model": "^2.15.6", + "hadron-app-registry": "^9.0.14", + "mongodb-data-service": "^22.15.1", "mongodb-instance-model": "^12.15.1", "react": "^17.0.2" }, "dependencies": { + "@mongodb-js/compass-app-stores": "^7.6.1", "@mongodb-js/compass-components": "^1.19.0", "@mongodb-js/compass-databases-navigation": "^1.19.1", "@mongodb-js/compass-logging": "^1.2.6", @@ -77,6 +81,8 @@ "@mongodb-js/connection-storage": "^0.6.6", "@mongodb-js/mongodb-redux-common": "^2.0.15", "compass-preferences-model": "^2.15.6", + "hadron-app-registry": "^9.0.14", + "mongodb-data-service": "^22.15.1", "mongodb-instance-model": "^12.15.1" }, "devDependencies": { @@ -97,7 +103,6 @@ "debug": "^4.2.0", "depcheck": "^1.4.1", "eslint": "^7.25.0", - "hadron-app-registry": "^9.0.14", "lodash": "^4.17.21", "mocha": "^10.2.0", "mongodb-ns": "^2.4.0", diff --git a/packages/compass-sidebar/src/components/navigation-items.spec.tsx b/packages/compass-sidebar/src/components/navigation-items.spec.tsx index bd7b4e2510e..4c7874f4cfe 100644 --- a/packages/compass-sidebar/src/components/navigation-items.spec.tsx +++ b/packages/compass-sidebar/src/components/navigation-items.spec.tsx @@ -1,14 +1,17 @@ import React from 'react'; import { expect } from 'chai'; -import { render, screen } from '@testing-library/react'; +import { render, screen, cleanup } from '@testing-library/react'; import { Provider } from 'react-redux'; +import { createStore, applyMiddleware } from 'redux'; +import thunk from 'redux-thunk'; +import reducer from '../modules'; import { NavigationItems } from './navigation-items'; -import store from '../stores/store'; function renderNavigationItems( props?: Partial> ) { + const store = createStore(reducer, applyMiddleware(thunk)); return render( void; setIsExpanded: (isExpanded: boolean) => void; + setConnectionIsCSFLEEnabled: (enabled: boolean) => void; isGenuine?: boolean; csfleMode?: 'enabled' | 'disabled' | 'unavailable'; }) { @@ -140,13 +143,6 @@ export function Sidebar({ setIsCSFLEModalVisible(!isCSFLEModalVisible); }, [setIsCSFLEModalVisible, isCSFLEModalVisible]); - const setConnectionIsCSFLEEnabled = useCallback( - (enabled: boolean) => { - globalAppRegistryEmit('sidebar-toggle-csfle-enabled', enabled); - }, - [globalAppRegistryEmit] - ); - return ( MongoDBInstance; + dataService: () => DataService; + } +>( + { + name: 'CompassSidebar', + component: SidebarPlugin, + activate( + { connectionInfo }: SidebarPluginProps, + { + globalAppRegistry, + instance, + dataService, + }: { + globalAppRegistry: AppRegistry; + instance: MongoDBInstance; + dataService: DataService; + } + ) { + const { store, deactivate } = createSidebarStore({ + globalAppRegistry, + instance, + dataService, + connectionInfo, + }); + return { + store, + deactivate, + }; + }, + }, + { + instance: mongoDBInstanceLocator, + dataService: dataServiceLocator, + } +); + export { activate, deactivate }; export { default as metadata } from '../package.json'; diff --git a/packages/compass-sidebar/src/modules/data-service.ts b/packages/compass-sidebar/src/modules/data-service.ts new file mode 100644 index 00000000000..1936154016c --- /dev/null +++ b/packages/compass-sidebar/src/modules/data-service.ts @@ -0,0 +1,44 @@ +import type { DataService } from 'mongodb-data-service'; +import type { RootAction } from '.'; + +export const SET_DATASERVICE = 'sidebar/SET_DATASERVICE' as const; +interface SetDataServiceAction { + type: typeof SET_DATASERVICE; + dataService: DataService; +} +export const SET_CSFLE_ENABLED = 'sidebar/SET_CSFLE_ENABLED' as const; +interface SetCSFLEEnabledAction { + type: typeof SET_CSFLE_ENABLED; + enable: boolean; +} +export type DataServiceAction = SetDataServiceAction | SetCSFLEEnabledAction; +export type DataServiceState = DataService | null; + +export const INITIAL_STATE: DataServiceState = null; + +export default function reducer( + state: DataServiceState = INITIAL_STATE, + action: RootAction +): DataServiceState { + if (action.type === SET_DATASERVICE) { + return action.dataService; + } + if (action.type === SET_CSFLE_ENABLED) { + state?.setCSFLEEnabled(action.enable); + } + return state; +} + +export const setDataService = ( + dataService: DataService +): SetDataServiceAction => ({ + type: SET_DATASERVICE, + dataService, +}); + +export const setConnectionIsCSFLEEnabled = ( + enable: boolean +): SetCSFLEEnabledAction => ({ + type: SET_CSFLE_ENABLED, + enable, +}); diff --git a/packages/compass-sidebar/src/modules/index.ts b/packages/compass-sidebar/src/modules/index.ts index 09c9fa8c312..cd57482a29d 100644 --- a/packages/compass-sidebar/src/modules/index.ts +++ b/packages/compass-sidebar/src/modules/index.ts @@ -42,12 +42,18 @@ import location, { INITIAL_STATE as LOCATION_IS } from './location'; import type { IsExpandedAction, IsExpandedState } from './is-expanded'; import isExpanded, { INITIAL_STATE as IS_EXPANDED_IS } from './is-expanded'; import type { AppRegistry } from 'hadron-app-registry'; +import type { DataServiceAction, DataServiceState } from './data-service'; +import dataService, { + INITIAL_STATE as DATA_SERVICE_IS, + SET_CSFLE_ENABLED, +} from './data-service'; export interface RootState { appRegistry: { globalAppRegistry: AppRegistry | null; localAppRegistry: AppRegistry | null; }; + dataService: DataServiceState; connectionInfo: ConnectionInfoState; connectionOptions: ConnectionOptionsState; databases: DatabaseState; @@ -67,7 +73,8 @@ export type RootAction = | IsGenuineMongoDBVisibleAction | LocationAction | IsExpandedAction - | ResetAction; + | ResetAction + | DataServiceAction; /** * The reducer. @@ -75,6 +82,7 @@ export type RootAction = const reducer = combineReducers({ appRegistry, databases, + dataService, connectionInfo, connectionOptions, instance, @@ -100,6 +108,7 @@ const rootReducer = ( return { appRegistry: { globalAppRegistry: null, localAppRegistry: null }, ...state, + dataService: DATA_SERVICE_IS, connectionInfo: CONNECTION_INFO_IS, connectionOptions: CONNECTION_OPTIONS_IS, databases: DATABASES_INITIAL_STATE, @@ -110,6 +119,10 @@ const rootReducer = ( isExpanded: IS_EXPANDED_IS, }; } + if (action.type === SET_CSFLE_ENABLED) { + const { globalAppRegistry } = state.appRegistry; + queueMicrotask(() => globalAppRegistry?.emit('refresh-data')); + } return reducer(state, action); }; diff --git a/packages/compass-sidebar/src/modules/location.ts b/packages/compass-sidebar/src/modules/location.ts index 27bdece9d5a..d4171fea0ef 100644 --- a/packages/compass-sidebar/src/modules/location.ts +++ b/packages/compass-sidebar/src/modules/location.ts @@ -11,7 +11,7 @@ export type Location = keyof typeof locations; export const CHANGE_LOCATION = 'sidebar/navigation/CHANGE_LOCATION' as const; interface ChangeLocationAction { type: typeof CHANGE_LOCATION; - location: Location; + location: Location | null; } export type LocationAction = ChangeLocationAction; export type LocationState = null | Location; @@ -28,7 +28,9 @@ export default function reducer( return state; } -export const changeLocation = (location: Location): ChangeLocationAction => ({ +export const changeLocation = ( + location: Location | null +): ChangeLocationAction => ({ type: CHANGE_LOCATION, location, }); diff --git a/packages/compass-sidebar/src/plugin.tsx b/packages/compass-sidebar/src/plugin.tsx index 37b766f978d..86093fe87cb 100644 --- a/packages/compass-sidebar/src/plugin.tsx +++ b/packages/compass-sidebar/src/plugin.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import { Provider } from 'react-redux'; import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging'; import { ErrorBoundary, @@ -9,8 +8,6 @@ import { import Sidebar from './components/sidebar'; -import store from './stores'; - const { log, mongoLogId } = createLoggerAndTelemetry( 'mongodb-compass:compass-sidebar:plugin' ); @@ -19,29 +16,22 @@ const errorBoundaryStyles = css({ width: defaultSidebarWidth, }); -/** - * Connect the Plugin to the store and render. - * - * @returns {React.Component} The rendered component. - */ function SidebarPlugin() { return ( - - { - log.error( - mongoLogId(1001000148), - 'Sidebar', - 'Rendering sidebar failed', - { error: error.message, errorInfo } - ); - }} - > - - - + { + log.error( + mongoLogId(1001000148), + 'Sidebar', + 'Rendering sidebar failed', + { error: error.message, errorInfo } + ); + }} + > + + ); } diff --git a/packages/compass-sidebar/src/stores/index.ts b/packages/compass-sidebar/src/stores/index.ts index 5807f2790c8..8f81fa6be81 100644 --- a/packages/compass-sidebar/src/stores/index.ts +++ b/packages/compass-sidebar/src/stores/index.ts @@ -1,4 +1 @@ -import SidebarStore from './store'; - -export default SidebarStore; -export { SidebarStore }; +export { createSidebarStore } from './store'; diff --git a/packages/compass-sidebar/src/stores/store.spec.ts b/packages/compass-sidebar/src/stores/store.spec.ts index 324aae53804..9c845beb558 100644 --- a/packages/compass-sidebar/src/stores/store.spec.ts +++ b/packages/compass-sidebar/src/stores/store.spec.ts @@ -1,7 +1,6 @@ import AppRegistry from 'hadron-app-registry'; import { expect } from 'chai'; -import store from '.'; -import { reset } from '../modules/reset'; +import { createSidebarStore } from '.'; import { createInstance } from '../../test/helpers'; import type { Database } from '../modules/databases'; import type { MongoDBInstance } from 'mongodb-instance-model'; @@ -27,96 +26,82 @@ function getDatabases(_instance: MongoDBInstance) { } describe('SidebarStore [Store]', function () { + const globalAppRegistry = new AppRegistry(); + let store: ReturnType['store']; + let deactivate: () => void; + beforeEach(function () { - store.dispatch(reset()); + ({ store, deactivate } = createSidebarStore({ + globalAppRegistry, + dataService: { + getConnectionOptions() { + return {}; + }, + }, + instance, + } as any)); }); afterEach(function () { - store.dispatch(reset()); + deactivate(); }); - describe('#onActivated', function () { - const appRegistry = new AppRegistry(); - - before(function () { - store.onActivated(appRegistry); - }); + context('when instance created', function () { + it('updates the instance and databases state', function () { + const state = store.getState(); - context('when instance created', function () { - beforeEach(function () { - expect(store.getState().instance).to.deep.equal(null); // initial state - expect(store.getState().databases).to.deep.equal({ - databases: [], - filteredDatabases: [], + expect(state) + .to.have.property('instance') + .deep.equal({ + build: {}, + csfleMode: 'unavailable', + dataLake: { + isDataLake: false, + }, + databasesStatus: 'initial', + env: 'on-prem', + genuineMongoDB: { + isGenuine: true, + }, + isAtlas: false, + isLocalAtlas: false, + isWritable: false, + refreshingStatus: 'initial', + topologyDescription: { + servers: [], + setName: 'foo', + type: 'Unknown', + }, + }); + expect(state) + .to.have.property('databases') + .deep.equal({ + databases: getDatabases(instance), + filteredDatabases: getDatabases(instance), + activeNamespace: '', expandedDbList: {}, filterRegex: null, - activeNamespace: '', - }); // initial state - appRegistry.emit('instance-created', { instance }); - }); - - afterEach(function () { - appRegistry.emit('instance-destroyed'); - }); - - it('updates the instance and databases state', function () { - const state = store.getState(); - - expect(state) - .to.have.property('instance') - .deep.equal({ - build: {}, - csfleMode: 'unavailable', - dataLake: { - isDataLake: false, - }, - databasesStatus: 'initial', - env: 'on-prem', - genuineMongoDB: { - isGenuine: true, - }, - isAtlas: false, - isLocalAtlas: false, - isWritable: false, - refreshingStatus: 'initial', - topologyDescription: { - servers: [], - setName: 'foo', - type: 'Unknown', - }, - }); - expect(state) - .to.have.property('databases') - .deep.equal({ - databases: getDatabases(instance), - filteredDatabases: getDatabases(instance), - activeNamespace: '', - expandedDbList: {}, - filterRegex: null, - }); - }); + }); }); + }); - context('when collection changes', function () { - beforeEach(function () { - expect(store.getState().databases.activeNamespace).to.equal(''); - appRegistry.emit('select-namespace', { namespace: 'test.coll' }); - }); - it('updates databases.activeNamespace', function () { - expect(store.getState().databases.activeNamespace).to.equal( - 'test.coll' - ); - }); + context('when collection changes', function () { + beforeEach(function () { + expect(store.getState().databases.activeNamespace).to.equal(''); + globalAppRegistry.emit('select-namespace', { namespace: 'test.coll' }); }); + it('updates databases.activeNamespace', function () { + expect(store.getState().databases.activeNamespace).to.equal('test.coll'); + }); + }); - context('when db changes', function () { - beforeEach(function () { - expect(store.getState().databases.activeNamespace).to.equal(''); - appRegistry.emit('select-database', 'test'); - }); - it('updates databases.activeNamespace', function () { - expect(store.getState().databases.activeNamespace).to.equal('test'); - }); + context('when db changes', function () { + beforeEach(function () { + expect(store.getState().databases.activeNamespace).to.equal(''); + globalAppRegistry.emit('select-database', 'test'); + }); + it('updates databases.activeNamespace', function () { + expect(store.getState().databases.activeNamespace).to.equal('test'); }); }); }); diff --git a/packages/compass-sidebar/src/stores/store.ts b/packages/compass-sidebar/src/stores/store.ts index 030e4d288d5..2e4fbb405fb 100644 --- a/packages/compass-sidebar/src/stores/store.ts +++ b/packages/compass-sidebar/src/stores/store.ts @@ -6,6 +6,7 @@ import { globalAppRegistryActivated } from '@mongodb-js/mongodb-redux-common/app import type { RootAction, RootState } from '../modules'; import reducer from '../modules'; import { changeInstance } from '../modules/instance'; +import type { Location } from '../modules/location'; import { changeLocation } from '../modules/location'; import type { Database } from '../modules/databases'; import { changeActiveNamespace, changeDatabases } from '../modules/databases'; @@ -13,21 +14,35 @@ import { reset } from '../modules/reset'; import { toggleIsGenuineMongoDBVisible } from '../modules/is-genuine-mongodb-visible'; import { changeConnectionInfo } from '../modules/connection-info'; import { changeConnectionOptions } from '../modules/connection-options'; +import { setDataService } from '../modules/data-service'; import { toggleSidebar } from '../modules/is-expanded'; -import type AppRegistry from 'hadron-app-registry'; +import type { AppRegistry } from 'hadron-app-registry'; import type { MongoDBInstance } from 'mongodb-instance-model'; +import type { DataService } from 'mongodb-data-service'; +import type { ConnectionInfo } from '@mongodb-js/connection-storage/renderer'; +import type toNS from 'mongodb-ns'; +type NS = ReturnType; + +export function createSidebarStore({ + globalAppRegistry, + instance, + dataService, + connectionInfo, +}: { + globalAppRegistry: AppRegistry; + instance: MongoDBInstance; + dataService: DataService; + connectionInfo: ConnectionInfo | null | undefined; +}): { + store: Store; + deactivate: () => void; +} { + const cleanup: (() => void)[] = []; + const store: Store = createStore( + reducer, + applyMiddleware(thunk) + ); -// We use these symbols so that nothing from outside can access these values on -// the store -const kInstance = Symbol('instance'); - -const store: Store & { - [kInstance]: null | MongoDBInstance; -} = Object.assign(createStore(reducer, applyMiddleware(thunk)), { - [kInstance]: null, -}); - -const onActivated = (appRegistry: AppRegistry) => { const onInstanceChangeNow = (instance: any /* MongoDBInstance */) => { store.dispatch( changeInstance({ @@ -80,138 +95,140 @@ const onActivated = (appRegistry: AppRegistry) => { store.dispatch(changeDatabases(dbs)); }, 300); - store.dispatch(globalAppRegistryActivated(appRegistry)); + store.dispatch(globalAppRegistryActivated(globalAppRegistry)); + + function on( + eventEmitter: { + on(ev: string, l: (...args: any[]) => void): void; + removeListener(ev: string, l: (...args: any[]) => void): void; + }, + ev: string, + listener: (...args: any[]) => void + ) { + eventEmitter.on(ev, listener); + cleanup.push(() => eventEmitter.removeListener(ev, listener)); + } + const onAppRegistryEvent = (ev: string, listener: (...args: any[]) => void) => + on(globalAppRegistry, ev, listener); + const onInstanceEvent = (ev: string, listener: (...args: any[]) => void) => + on(instance, ev, listener); - appRegistry.on('data-service-connected', (_, dataService, connectionInfo) => { - store.dispatch(changeConnectionInfo(connectionInfo)); - const connectionOptions = dataService.getConnectionOptions(); - store.dispatch(changeConnectionOptions(connectionOptions)); // stores ssh tunnel status + store.dispatch(setDataService(dataService)); + if (connectionInfo) store.dispatch(changeConnectionInfo(connectionInfo)); + const connectionOptions = dataService.getConnectionOptions(); + store.dispatch(changeConnectionOptions(connectionOptions)); // stores ssh tunnel status - appRegistry.removeAllListeners('sidebar-toggle-csfle-enabled'); - appRegistry.on('sidebar-toggle-csfle-enabled', (enabled) => { - dataService.setCSFLEEnabled(enabled); - appRegistry.emit('refresh-data'); - }); - }); + onInstanceChangeNow(instance); + onDatabasesChange(instance.databases); - appRegistry.on('instance-destroyed', () => { - const instance = store[kInstance] as any; - if (instance) { - instance.off(); - instance.build.off(); - instance.dataLake.off(); - instance.genuineMongoDB.off(); - } - store[kInstance] = null; - onInstanceChange.cancel(); - onDatabasesChange.cancel(); + onInstanceEvent('change:csfleMode', () => { + onInstanceChange(instance); }); - appRegistry.on('instance-created', ({ instance }) => { - if (store[kInstance]) { - // we should probably throw in this case - return; - } - - store[kInstance] = instance; - + onInstanceEvent('change:refreshingStatus', () => { + // This will always fire when we start fetching the instance details which + // will cause a 300ms throttle before any instance details can update if + // we send it though the throttled update. That's long enough for the + // sidebar to display that we're connected to a standalone instance when + // we're really connected to dataLake. onInstanceChangeNow(instance); - onDatabasesChange(instance.databases); - - instance.on('change:csfleMode', () => { - onInstanceChange(instance); - }); - - instance.on('change:refreshingStatus', () => { - // This will always fire when we start fetching the instance details which - // will cause a 300ms throttle before any instance details can update if - // we send it though the throttled update. That's long enough for the - // sidebar to display that we're connected to a standalone instance when - // we're really connected to dataLake. - onInstanceChangeNow(instance); - }); - - instance.on('change:databasesStatus', () => { - onInstanceChange(instance); - onDatabasesChange(instance.databases); - }); - - instance.on('change:databases', () => { - onDatabasesChange(instance.databases); - }); + }); - instance.on('change:databases.collectionsStatus', () => { - onDatabasesChange(instance.databases); - }); + onInstanceEvent('change:databasesStatus', () => { + onInstanceChange(instance); + onDatabasesChange(instance.databases); + }); - instance.build.on('change:isEnterprise', () => { - onInstanceChange(instance); - }); + onInstanceEvent('change:databases', () => { + onDatabasesChange(instance.databases); + }); - instance.build.on('change:version', () => { - onInstanceChange(instance); - }); + onInstanceEvent('change:databases.collectionsStatus', () => { + onDatabasesChange(instance.databases); + }); - instance.dataLake.on('change:isDataLake', () => { - onInstanceChange(instance); - }); + on(instance.build as any, 'change:isEnterprise', () => { + onInstanceChange(instance); + }); - instance.dataLake.on('change:version', () => { - onInstanceChange(instance); - }); + on(instance.build as any, 'change:version', () => { + onInstanceChange(instance); + }); - store.dispatch( - toggleIsGenuineMongoDBVisible(!instance.genuineMongoDB.isGenuine) - ); + on(instance.dataLake as any, 'change:isDataLake', () => { + onInstanceChange(instance); + }); - instance.genuineMongoDB.on( - 'change:isGenuine', - (model: unknown, isGenuine: boolean) => { - onInstanceChange(instance); // isGenuineMongoDB is part of instance state - store.dispatch(toggleIsGenuineMongoDBVisible(!isGenuine)); - } - ); + on(instance.dataLake as any, 'change:version', () => { + onInstanceChange(instance); + }); - instance.on('change:topologyDescription', () => { - onInstanceChange(instance); - }); + store.dispatch( + toggleIsGenuineMongoDBVisible(!instance.genuineMongoDB.isGenuine) + ); - instance.on('change:isWritable', () => { - onInstanceChange(instance); - }); + on( + instance.genuineMongoDB as any, + 'change:isGenuine', + (model: unknown, isGenuine: boolean) => { + onInstanceChange(instance); // isGenuineMongoDB is part of instance state + store.dispatch(toggleIsGenuineMongoDBVisible(!isGenuine)); + } + ); - instance.on('change:env', () => { - onInstanceChange(instance); - }); + onInstanceEvent('change:topologyDescription', () => { + onInstanceChange(instance); }); - appRegistry.on('select-namespace', ({ namespace }) => { - store.dispatch(changeActiveNamespace(namespace)); - store.dispatch(changeLocation('collection')); + onInstanceEvent('change:isWritable', () => { + onInstanceChange(instance); }); - appRegistry.on('open-namespace-in-new-tab', ({ namespace }) => { - store.dispatch(changeActiveNamespace(namespace)); - store.dispatch(changeLocation('collection')); + onInstanceEvent('change:env', () => { + onInstanceChange(instance); }); - appRegistry.on('select-database', (dbName) => { + onAppRegistryEvent( + 'select-namespace', + ({ namespace }: { namespace: string | NS }) => { + store.dispatch(changeActiveNamespace(namespace)); + store.dispatch(changeLocation('collection')); + } + ); + + onAppRegistryEvent( + 'open-namespace-in-new-tab', + ({ namespace }: { namespace: string | NS }) => { + store.dispatch(changeActiveNamespace(namespace)); + store.dispatch(changeLocation('collection')); + } + ); + + onAppRegistryEvent('select-database', (dbName: string) => { store.dispatch(changeActiveNamespace(dbName)); store.dispatch(changeLocation('database')); }); - appRegistry.on('open-instance-workspace', (tabName = null) => { - store.dispatch(changeActiveNamespace('')); - store.dispatch(changeLocation(tabName)); - }); + onAppRegistryEvent( + 'open-instance-workspace', + (tabName: Location | null = null) => { + store.dispatch(changeActiveNamespace('')); + store.dispatch(changeLocation(tabName)); + } + ); - appRegistry.on('data-service-disconnected', () => { + onAppRegistryEvent('data-service-disconnected', () => { store.dispatch(reset()); }); - appRegistry.on('toggle-sidebar', () => { + onAppRegistryEvent('toggle-sidebar', () => { store.dispatch(toggleSidebar()); }); -}; -export default Object.assign(store, { onActivated }); + return { + store, + deactivate() { + for (const cleaner of cleanup) cleaner(); + }, + }; +} diff --git a/packages/instance-model/index.d.ts b/packages/instance-model/index.d.ts index d019b0a0134..51e3cad8547 100644 --- a/packages/instance-model/index.d.ts +++ b/packages/instance-model/index.d.ts @@ -100,6 +100,7 @@ declare class MongoDBInstance extends MongoDBInstanceProps { }): Promise; removeAllListeners(): void; on(evt: string, fn: (...args: any) => void); + removeListener(evt: string, fn: (...args: any) => void); toJSON(opts?: { derived?: boolean }): this; }