diff --git a/package-lock.json b/package-lock.json
index 9644ade5334..39c877dd9a5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,9 +11,6 @@
"configs/*",
"scripts"
],
- "dependencies": {
- "electron": "^25.9.3"
- },
"devDependencies": {
"@babel/core": "7.16.0",
"@babel/parser": "7.16.0",
@@ -46040,6 +46037,7 @@
"@mongodb-js/compass-components": "^1.19.0",
"@mongodb-js/compass-connections": "^1.20.0",
"@mongodb-js/compass-logging": "^1.2.5",
+ "@mongodb-js/compass-settings": "^0.21.0",
"@mongodb-js/compass-welcome": "^0.18.0",
"@mongodb-js/connection-storage": "^0.6.5",
"compass-preferences-model": "^2.15.5",
@@ -46077,6 +46075,7 @@
"@mongodb-js/compass-components": "^1.19.0",
"@mongodb-js/compass-connections": "^1.20.0",
"@mongodb-js/compass-logging": "^1.2.5",
+ "@mongodb-js/compass-settings": "^0.21.0",
"@mongodb-js/compass-welcome": "^0.18.0",
"@mongodb-js/connection-storage": "^0.6.5",
"compass-preferences-model": "^2.15.5",
@@ -46426,7 +46425,8 @@
"dependencies": {
"debug": "^4.3.4",
"is-electron-renderer": "^2.0.1",
- "mongodb-log-writer": "^1.3.0"
+ "mongodb-log-writer": "^1.3.0",
+ "react": "^17.0.2"
},
"devDependencies": {
"@mongodb-js/eslint-config-compass": "^1.0.11",
@@ -47120,7 +47120,6 @@
"enzyme": "^3.11.0",
"eslint": "^7.25.0",
"hadron-app-registry": "^9.0.13",
- "hadron-ipc": "^3.2.4",
"mocha": "^10.2.0",
"nyc": "^15.1.0",
"prop-types": "^15.7.2",
@@ -47833,7 +47832,6 @@
"eslint": "^7.25.0",
"hadron-app": "^5.15.0",
"hadron-app-registry": "^9.0.13",
- "hadron-ipc": "^3.2.4",
"lodash": "^4.17.21",
"mocha": "^10.2.0",
"mongodb": "^6.0.0",
@@ -48074,6 +48072,9 @@
"dependencies": {
"debug": "^4.2.0",
"eventemitter3": "^4.0.0",
+ "react": "^17.0.2",
+ "react-redux": "^8.0.5",
+ "redux": "^4.2.1",
"reflux": "^0.4.1"
},
"devDependencies": {
@@ -48081,6 +48082,7 @@
"@mongodb-js/mocha-config-compass": "^1.3.2",
"@mongodb-js/prettier-config-compass": "^1.0.1",
"@mongodb-js/tsconfig-compass": "^1.0.3",
+ "@testing-library/react": "^12.1.4",
"@types/chai": "^4.2.21",
"@types/mocha": "^9.0.0",
"@types/reflux": "^6.4.3",
@@ -48090,6 +48092,7 @@
"eslint-config-mongodb-js": "^5.0.3",
"mocha": "^10.2.0",
"prettier": "^2.7.1",
+ "reflux-state-mixin": "github:mongodb-js/reflux-state-mixin",
"sinon": "^9.0.0",
"typescript": "^5.0.4"
}
@@ -58683,7 +58686,6 @@
"eslint": "^7.25.0",
"hadron-app": "^5.15.0",
"hadron-app-registry": "^9.0.13",
- "hadron-ipc": "^3.2.4",
"lodash": "^4.17.21",
"mocha": "^10.2.0",
"mongodb": "^6.0.0",
@@ -59188,6 +59190,7 @@
"@mongodb-js/compass-components": "^1.19.0",
"@mongodb-js/compass-connections": "^1.20.0",
"@mongodb-js/compass-logging": "^1.2.5",
+ "@mongodb-js/compass-settings": "^0.21.0",
"@mongodb-js/compass-welcome": "^0.18.0",
"@mongodb-js/connection-storage": "^0.6.5",
"@mongodb-js/eslint-config-compass": "^1.0.11",
@@ -59500,6 +59503,7 @@
"mongodb-log-writer": "^1.3.0",
"nyc": "^15.1.0",
"prettier": "^2.7.1",
+ "react": "^17.0.2",
"sinon": "^9.2.3",
"typescript": "^5.0.4"
},
@@ -60185,7 +60189,6 @@
"enzyme": "^3.11.0",
"eslint": "^7.25.0",
"hadron-app-registry": "^9.0.13",
- "hadron-ipc": "^3.2.4",
"mocha": "^10.2.0",
"nyc": "^15.1.0",
"prop-types": "^15.7.2",
@@ -78452,6 +78455,7 @@
"@mongodb-js/mocha-config-compass": "^1.3.2",
"@mongodb-js/prettier-config-compass": "^1.0.1",
"@mongodb-js/tsconfig-compass": "^1.0.3",
+ "@testing-library/react": "^12.1.4",
"@types/chai": "^4.2.21",
"@types/mocha": "^9.0.0",
"@types/reflux": "^6.4.3",
@@ -78463,7 +78467,11 @@
"eventemitter3": "^4.0.0",
"mocha": "^10.2.0",
"prettier": "^2.7.1",
+ "react": "^17.0.2",
+ "react-redux": "^8.0.5",
+ "redux": "^4.2.1",
"reflux": "^0.4.1",
+ "reflux-state-mixin": "github:mongodb-js/reflux-state-mixin",
"sinon": "^9.0.0",
"typescript": "^5.0.4"
},
diff --git a/packages/compass-aggregations/src/components/pipeline-builder-workspace/pipeline-builder-ui-workspace/dnd-wrapper.tsx b/packages/compass-aggregations/src/components/pipeline-builder-workspace/pipeline-builder-ui-workspace/dnd-wrapper.tsx
index 88f87998bb6..00692363edb 100644
--- a/packages/compass-aggregations/src/components/pipeline-builder-workspace/pipeline-builder-ui-workspace/dnd-wrapper.tsx
+++ b/packages/compass-aggregations/src/components/pipeline-builder-workspace/pipeline-builder-ui-workspace/dnd-wrapper.tsx
@@ -8,7 +8,7 @@ import {
DragOverlay,
} from '@dnd-kit/core';
import type { DragEndEvent, DragStartEvent } from '@dnd-kit/core';
-import createLoggerAndTelemetry from '@mongodb-js/compass-logging';
+import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging';
import {
indexFromDroppableId,
diff --git a/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-ai.tsx b/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-ai.tsx
index 62f17b47501..5da37d731c1 100644
--- a/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-ai.tsx
+++ b/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-ai.tsx
@@ -2,7 +2,7 @@ import React, { useRef, useEffect } from 'react';
import { openToast } from '@mongodb-js/compass-components';
import { GenerativeAIInput } from '@mongodb-js/compass-generative-ai';
import { connect } from 'react-redux';
-import createLoggerAndTelemetry from '@mongodb-js/compass-logging';
+import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging';
import { usePreference } from 'compass-preferences-model';
import {
diff --git a/packages/compass-aggregations/src/components/stage-preview/atlas-stage-preview.tsx b/packages/compass-aggregations/src/components/stage-preview/atlas-stage-preview.tsx
index 0f4bdca51d5..0c52d9b7812 100644
--- a/packages/compass-aggregations/src/components/stage-preview/atlas-stage-preview.tsx
+++ b/packages/compass-aggregations/src/components/stage-preview/atlas-stage-preview.tsx
@@ -6,7 +6,7 @@ import {
AtlasNavGraphic,
Body,
} from '@mongodb-js/compass-components';
-import createLoggerAndTelemetry from '@mongodb-js/compass-logging';
+import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging';
const { track } = createLoggerAndTelemetry('COMPASS-AGGREGATIONS-UI');
const ATLAS_LINK =
diff --git a/packages/compass-aggregations/src/modules/focus-mode.ts b/packages/compass-aggregations/src/modules/focus-mode.ts
index 888861c3505..a5c051cc822 100644
--- a/packages/compass-aggregations/src/modules/focus-mode.ts
+++ b/packages/compass-aggregations/src/modules/focus-mode.ts
@@ -2,7 +2,7 @@ import type { AnyAction } from 'redux';
import type { PipelineBuilderThunkAction } from '.';
import { isAction } from '../utils/is-action';
import { addStage, pipelineFromStore } from './pipeline-builder/stage-editor';
-import createLoggerAndTelemetry from '@mongodb-js/compass-logging';
+import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging';
const { track } = createLoggerAndTelemetry('COMPASS-AGGREGATIONS-UI');
diff --git a/packages/compass-aggregations/src/modules/pipeline-builder/pipeline-ai.ts b/packages/compass-aggregations/src/modules/pipeline-builder/pipeline-ai.ts
index a843fca0370..8128a4787ac 100644
--- a/packages/compass-aggregations/src/modules/pipeline-builder/pipeline-ai.ts
+++ b/packages/compass-aggregations/src/modules/pipeline-builder/pipeline-ai.ts
@@ -1,5 +1,5 @@
import type { Reducer } from 'redux';
-import createLoggerAndTelemetry from '@mongodb-js/compass-logging';
+import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging';
import { getSimplifiedSchema } from 'mongodb-schema';
import toNS from 'mongodb-ns';
import preferences from 'compass-preferences-model';
diff --git a/packages/compass-collection/src/components/collection-tab.tsx b/packages/compass-collection/src/components/collection-tab.tsx
index e89885fff1d..c2663e6662f 100644
--- a/packages/compass-collection/src/components/collection-tab.tsx
+++ b/packages/compass-collection/src/components/collection-tab.tsx
@@ -12,7 +12,7 @@ import {
} from '../modules/collection-tab';
import { css, ErrorBoundary, TabNavBar } from '@mongodb-js/compass-components';
import CollectionHeader from './collection-header';
-import createLoggerAndTelemetry from '@mongodb-js/compass-logging';
+import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging';
const { log, mongoLogId, track } = createLoggerAndTelemetry(
'COMPASS-COLLECTION-TAB-UI'
diff --git a/packages/compass-collection/src/stores/collection-tab.ts b/packages/compass-collection/src/stores/collection-tab.ts
index 12f7ea4e1c3..b2da69a6fc2 100644
--- a/packages/compass-collection/src/stores/collection-tab.ts
+++ b/packages/compass-collection/src/stores/collection-tab.ts
@@ -46,9 +46,9 @@ export function configureStore(options: CollectionTabOptions) {
throw new Error('Expected to get instance from App.InstanceStore');
}
- const configureFieldStore = globalAppRegistry.getStore('Field.Store') as (
- ...args: any
- ) => void | undefined; // our handcrafted d.ts file doesn't match the actual code
+ const configureFieldStore = globalAppRegistry.getStore(
+ 'Field.Store'
+ ) as unknown as (...args: any) => void | undefined; // Field.Store is odd because it registers a configure method, not the actual store
configureFieldStore?.({
localAppRegistry: localAppRegistry,
diff --git a/packages/compass-connections/src/components/form-help/form-help.tsx b/packages/compass-connections/src/components/form-help/form-help.tsx
index d953a9d3ca0..6b32a35925a 100644
--- a/packages/compass-connections/src/components/form-help/form-help.tsx
+++ b/packages/compass-connections/src/components/form-help/form-help.tsx
@@ -13,7 +13,7 @@ import {
useDarkMode,
} from '@mongodb-js/compass-components';
-import createLoggerAndTelemetry from '@mongodb-js/compass-logging';
+import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging';
const { track } = createLoggerAndTelemetry('COMPASS-CONNECT-UI');
const formHelpContainerStyles = css({
diff --git a/packages/compass-connections/src/modules/connection-attempt.ts b/packages/compass-connections/src/modules/connection-attempt.ts
index 4a8adaee70c..a4362469189 100644
--- a/packages/compass-connections/src/modules/connection-attempt.ts
+++ b/packages/compass-connections/src/modules/connection-attempt.ts
@@ -1,4 +1,4 @@
-import createLoggerAndTelemetry from '@mongodb-js/compass-logging';
+import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging';
import { isCancelError, raceWithAbort } from '@mongodb-js/compass-utils';
import type { ConnectionOptions, DataService } from 'mongodb-data-service';
import { connect } from 'mongodb-data-service';
diff --git a/packages/compass-crud/src/components/insert-document-dialog.tsx b/packages/compass-crud/src/components/insert-document-dialog.tsx
index 72e542ade24..17d66a72274 100644
--- a/packages/compass-crud/src/components/insert-document-dialog.tsx
+++ b/packages/compass-crud/src/components/insert-document-dialog.tsx
@@ -17,7 +17,7 @@ import InsertCSFLEWarningBanner from './insert-csfle-warning-banner';
import InsertJsonDocument from './insert-json-document';
import InsertDocument from './insert-document';
import type { LoggerAndTelemetry } from '@mongodb-js/compass-logging';
-import { withLoggerAndTelemetry } from '@mongodb-js/compass-logging';
+import { withLoggerAndTelemetry } from '@mongodb-js/compass-logging/provider';
/**
* The insert invalid message.
@@ -341,8 +341,4 @@ class InsertDocumentDialog extends React.PureComponent<
}
}
-export default withLoggerAndTelemetry(
- InsertDocumentDialog,
- 'COMPASS-CRUD-UI',
- React
-);
+export default withLoggerAndTelemetry(InsertDocumentDialog, 'COMPASS-CRUD-UI');
diff --git a/packages/compass-crud/src/stores/crud-store.ts b/packages/compass-crud/src/stores/crud-store.ts
index 5aeac32d95b..e887186b244 100644
--- a/packages/compass-crud/src/stores/crud-store.ts
+++ b/packages/compass-crud/src/stores/crud-store.ts
@@ -9,7 +9,7 @@ import type { Element } from 'hadron-document';
import { Document } from 'hadron-document';
import HadronDocument from 'hadron-document';
import _parseShellBSON, { ParseMode } from 'ejson-shell-parser';
-import createLoggerAndTelemetry from '@mongodb-js/compass-logging';
+import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging';
import { capMaxTimeMSAtPreferenceLimit } from 'compass-preferences-model';
import type { Stage } from '@mongodb-js/explain-plan-helper';
import { ExplainPlan } from '@mongodb-js/explain-plan-helper';
diff --git a/packages/compass-crud/src/utils/cancellable-queries.ts b/packages/compass-crud/src/utils/cancellable-queries.ts
index de0b8f0ad8c..56aecea8256 100644
--- a/packages/compass-crud/src/utils/cancellable-queries.ts
+++ b/packages/compass-crud/src/utils/cancellable-queries.ts
@@ -1,9 +1,11 @@
-import createLogger from '@mongodb-js/compass-logging';
+import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging';
import { capMaxTimeMSAtPreferenceLimit } from 'compass-preferences-model';
import type { DataService } from 'mongodb-data-service';
import type { BSONObject } from '../stores/crud-store';
-const { log, mongoLogId, debug } = createLogger('cancellable-queries');
+const { log, mongoLogId, debug } = createLoggerAndTelemetry(
+ 'COMPASS-CANCELLABLE-QUERIES'
+);
export async function countDocuments(
dataService: DataService,
diff --git a/packages/compass-explain-plan/src/stores/explain-plan-modal-store.ts b/packages/compass-explain-plan/src/stores/explain-plan-modal-store.ts
index dc662477432..4f6debad684 100644
--- a/packages/compass-explain-plan/src/stores/explain-plan-modal-store.ts
+++ b/packages/compass-explain-plan/src/stores/explain-plan-modal-store.ts
@@ -8,7 +8,7 @@ import type { Action, AnyAction, Reducer } from 'redux';
import { applyMiddleware, createStore } from 'redux';
import type { ThunkAction } from 'redux-thunk';
import thunk from 'redux-thunk';
-import createLoggerAndTelemetry from '@mongodb-js/compass-logging';
+import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging';
const { log, mongoLogId, track } =
createLoggerAndTelemetry('COMPASS-EXPLAIN-UI');
diff --git a/packages/compass-home/package.json b/packages/compass-home/package.json
index 0a33ba775b5..35f11992b17 100644
--- a/packages/compass-home/package.json
+++ b/packages/compass-home/package.json
@@ -38,6 +38,7 @@
"@mongodb-js/compass-components": "^1.19.0",
"@mongodb-js/compass-connections": "^1.20.0",
"@mongodb-js/compass-logging": "^1.2.5",
+ "@mongodb-js/compass-settings": "^0.21.0",
"@mongodb-js/compass-welcome": "^0.18.0",
"@mongodb-js/connection-storage": "^0.6.5",
"compass-preferences-model": "^2.15.5",
@@ -49,6 +50,7 @@
"@mongodb-js/compass-components": "^1.19.0",
"@mongodb-js/compass-connections": "^1.20.0",
"@mongodb-js/compass-logging": "^1.2.5",
+ "@mongodb-js/compass-settings": "^0.21.0",
"@mongodb-js/compass-welcome": "^0.18.0",
"@mongodb-js/connection-storage": "^0.6.5",
"compass-preferences-model": "^2.15.5",
diff --git a/packages/compass-home/src/components/home.spec.tsx b/packages/compass-home/src/components/home.spec.tsx
index 7f55c011ec4..f06804044ab 100644
--- a/packages/compass-home/src/components/home.spec.tsx
+++ b/packages/compass-home/src/components/home.spec.tsx
@@ -2,10 +2,9 @@ import React from 'react';
import { once } from 'events';
import { cleanup, render, screen, waitFor } from '@testing-library/react';
import { expect } from 'chai';
-import AppRegistry from 'hadron-app-registry';
-import ipc from 'hadron-ipc';
+import AppRegistry, { AppRegistryProvider } from 'hadron-app-registry';
+import { ipcRenderer } from 'hadron-ipc';
import sinon from 'sinon';
-import AppRegistryContext from '../contexts/app-registry-context';
import Home from '.';
const getComponent = (name: string) => {
@@ -61,9 +60,9 @@ describe('Home [Component]', function () {
describe('is not connected', function () {
beforeEach(function () {
render(
-
+
-
+
);
});
@@ -82,9 +81,9 @@ describe('Home [Component]', function () {
connectionOptions = { connectionString: 'mongodb+srv://mongodb.net/' }
) {
render(
-
+
-
+
);
testAppRegistry.emit('data-service-connected', null, dataService, {
connectionOptions,
@@ -128,16 +127,16 @@ describe('Home [Component]', function () {
describe('on `app:disconnect`', function () {
// Skip disconnect testing when we're not running in a renderer instance.
// eslint-disable-next-line mocha/no-setup-in-describe
- if (!ipc.ipcRenderer) {
+ if (!ipcRenderer) {
// eslint-disable-next-line mocha/no-setup-in-describe, no-console
console.warn(
'Skipping "app:disconnect" ipc event tests on non-renderer environment.'
);
- return;
+ return this;
}
beforeEach(async function () {
- ipc.ipcRenderer.emit('app:disconnect');
+ ipcRenderer?.emit('app:disconnect');
await once(testAppRegistry, 'data-service-disconnected');
});
@@ -159,9 +158,9 @@ describe('Home [Component]', function () {
describe('when rendered', function () {
beforeEach(function () {
render(
-
+
-
+
);
});
@@ -184,9 +183,9 @@ describe('Home [Component]', function () {
describe('on dismount', function () {
beforeEach(function () {
const { unmount } = render(
-
+
-
+
);
unmount();
});
diff --git a/packages/compass-home/src/components/home.tsx b/packages/compass-home/src/components/home.tsx
index 40afc536284..aa8010875e5 100644
--- a/packages/compass-home/src/components/home.tsx
+++ b/packages/compass-home/src/components/home.tsx
@@ -33,16 +33,13 @@ import React, {
} from 'react';
import preferences from 'compass-preferences-model';
import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging';
-import {
- AppRegistryRoles,
- useAppRegistryContext,
- useAppRegistryRole,
-} from '../contexts/app-registry-context';
+import { useLocalAppRegistry, useAppRegistryRole } from 'hadron-app-registry';
import updateTitle from '../modules/update-title';
import Workspace from './workspace';
import { SignalHooksProvider } from '@mongodb-js/compass-components';
import { AtlasSignIn } from '@mongodb-js/atlas-service/renderer';
import type { CollectionMetadata } from 'mongodb-collection-model';
+import { CompassSettingsPlugin } from '@mongodb-js/compass-settings';
const { track } = createLoggerAndTelemetry('COMPASS-HOME-UI');
@@ -169,7 +166,7 @@ function Home({
appName: string;
getAutoConnectInfo?: () => Promise;
}): React.ReactElement | null {
- const appRegistry = useAppRegistryContext();
+ const appRegistry = useLocalAppRegistry();
const connectedDataService = useRef();
const [
@@ -320,7 +317,7 @@ function Home({
};
}, [appRegistry, onDataServiceDisconnected]);
- const globalModals = useAppRegistryRole(AppRegistryRoles.GLOBAL_MODAL);
+ const globalModals = useAppRegistryRole('Global.Modal');
return (
{
return ;
})}
+
);
@@ -380,7 +378,7 @@ function ThemedHome(
): ReturnType {
const [scrollbarsContainerRef, setScrollbarsContainerRef] =
useState(null);
- const appRegistry = useAppRegistryContext();
+ const appRegistry = useLocalAppRegistry();
const [theme, setTheme] = useState({
theme: getCurrentTheme(),
diff --git a/packages/compass-home/src/components/workspace-content.spec.tsx b/packages/compass-home/src/components/workspace-content.spec.tsx
index ebdea683fa7..a0aec357ef5 100644
--- a/packages/compass-home/src/components/workspace-content.spec.tsx
+++ b/packages/compass-home/src/components/workspace-content.spec.tsx
@@ -1,8 +1,7 @@
import React from 'react';
import { cleanup, render, screen } from '@testing-library/react';
import { expect } from 'chai';
-import AppRegistry from 'hadron-app-registry';
-import AppRegistryContext from '../contexts/app-registry-context';
+import AppRegistry, { AppRegistryProvider } from 'hadron-app-registry';
import WorkspaceContent from './workspace-content';
const getComponent = (name: string) => {
@@ -39,9 +38,9 @@ describe('WorkspaceContent [Component]', function () {
describe('namespace is unset', function () {
beforeEach(function () {
render(
-
+
-
+
);
});
@@ -55,9 +54,9 @@ describe('WorkspaceContent [Component]', function () {
describe('namespace has a db', function () {
beforeEach(function () {
render(
-
+
-
+
);
});
@@ -71,9 +70,9 @@ describe('WorkspaceContent [Component]', function () {
describe('namespace has db and collection', function () {
beforeEach(function () {
render(
-
+
-
+
);
});
diff --git a/packages/compass-home/src/components/workspace-content.tsx b/packages/compass-home/src/components/workspace-content.tsx
index 17d5d59f10b..6c9ec835b6b 100644
--- a/packages/compass-home/src/components/workspace-content.tsx
+++ b/packages/compass-home/src/components/workspace-content.tsx
@@ -1,9 +1,6 @@
/* eslint-disable react/prop-types */
import React from 'react';
-import {
- AppRegistryComponents,
- useAppRegistryComponent,
-} from '../contexts/app-registry-context';
+import { useAppRegistryComponent } from 'hadron-app-registry';
import type Namespace from '../types/namespace';
const EmptyComponent: React.FunctionComponent = () => null;
@@ -12,14 +9,11 @@ const WorkspaceContent: React.FunctionComponent<{ namespace: Namespace }> = ({
namespace,
}) => {
const Collection =
- useAppRegistryComponent(AppRegistryComponents.COLLECTION_WORKSPACE) ??
- EmptyComponent;
+ useAppRegistryComponent('Collection.Workspace') ?? EmptyComponent;
const Database =
- useAppRegistryComponent(AppRegistryComponents.DATABASE_WORKSPACE) ??
- EmptyComponent;
+ useAppRegistryComponent('Database.Workspace') ?? EmptyComponent;
const Instance =
- useAppRegistryComponent(AppRegistryComponents.INSTANCE_WORKSPACE) ??
- EmptyComponent;
+ useAppRegistryComponent('Instance.Workspace') ?? EmptyComponent;
if (namespace.collection) {
return ;
diff --git a/packages/compass-home/src/components/workspace.tsx b/packages/compass-home/src/components/workspace.tsx
index b51f75b143c..63a0b79970f 100644
--- a/packages/compass-home/src/components/workspace.tsx
+++ b/packages/compass-home/src/components/workspace.tsx
@@ -3,10 +3,7 @@ import { css } from '@mongodb-js/compass-components';
import WorkspaceContent from './workspace-content';
import type Namespace from '../types/namespace';
-import {
- AppRegistryComponents,
- useAppRegistryComponent,
-} from '../contexts/app-registry-context';
+import { useAppRegistryComponent } from 'hadron-app-registry';
const verticalSplitStyles = css({
width: '100vw',
@@ -42,12 +39,8 @@ export default function Workspace({
}: {
namespace: Namespace;
}): React.ReactElement {
- const SidebarComponent = useAppRegistryComponent(
- AppRegistryComponents.SIDEBAR_COMPONENT
- );
- const GlobalShellComponent = useAppRegistryComponent(
- AppRegistryComponents.SHELL_COMPONENT
- );
+ const SidebarComponent = useAppRegistryComponent('Sidebar.Component');
+ const GlobalShellComponent = useAppRegistryComponent('Global.Shell');
return (
<>
diff --git a/packages/compass-home/src/contexts/app-registry-context.ts b/packages/compass-home/src/contexts/app-registry-context.ts
deleted file mode 100644
index edea88865a8..00000000000
--- a/packages/compass-home/src/contexts/app-registry-context.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-import { createContext, useContext, useState } from 'react';
-import type React from 'react';
-import AppRegistry from 'hadron-app-registry';
-import createLoggerAndTelemetry from '@mongodb-js/compass-logging';
-
-const { debug } = createLoggerAndTelemetry('COMPASS-HOME-UI');
-
-const AppRegistryContext = createContext(new AppRegistry());
-export default AppRegistryContext;
-
-export enum AppRegistryRoles {
- APPLICATION_CONNECT = 'Application.Connect',
- GLOBAL_MODAL = 'Global.Modal',
-}
-
-export enum AppRegistryComponents {
- SIDEBAR_COMPONENT = 'Sidebar.Component',
- SHELL_COMPONENT = 'Global.Shell',
- COLLECTION_WORKSPACE = 'Collection.Workspace',
- DATABASE_WORKSPACE = 'Database.Workspace',
- INSTANCE_WORKSPACE = 'Instance.Workspace',
-}
-
-export const useAppRegistryContext = (): AppRegistry =>
- useContext(AppRegistryContext);
-export const useAppRegistryComponent = (
- componentName: AppRegistryComponents
-): React.JSXElementConstructor | null => {
- const appRegistry = useContext(AppRegistryContext);
-
- const [component] = useState(() => {
- const newComponent = appRegistry.getComponent(componentName);
- if (!newComponent) {
- debug(
- `home plugin loading component, but ${String(componentName)} is NULL`
- );
- }
- return newComponent;
- });
-
- return component ? component : null;
-};
-
-export function useAppRegistryRole(
- roleName: AppRegistryRoles.APPLICATION_CONNECT | AppRegistryRoles.GLOBAL_MODAL
-):
- | {
- component: React.JSXElementConstructor;
- name: string;
- }[]
- | null;
-export function useAppRegistryRole(roleName: AppRegistryRoles):
- | {
- component: React.JSXElementConstructor<{
- isDataLake?: boolean;
- }>;
- }[]
- | null {
- const appRegistry = useContext(AppRegistryContext);
-
- const [role] = useState(() => {
- const newRole = appRegistry.getRole(roleName);
- if (!newRole) {
- debug(`home plugin loading role, but ${String(roleName)} is NULL`);
- }
- return newRole;
- });
-
- return role ? role : null;
-}
diff --git a/packages/compass-home/src/index.ts b/packages/compass-home/src/index.ts
index 094fe4ecf63..a0e553e4015 100644
--- a/packages/compass-home/src/index.ts
+++ b/packages/compass-home/src/index.ts
@@ -1,22 +1,37 @@
-import type AppRegistry from 'hadron-app-registry';
-import HomePlugin from './plugin';
+import { registerHadronPlugin } from 'hadron-app-registry';
+import Home from './components/home';
/**
* Activate all the components in the Home package.
- * @param {Object} appRegistry - The Hadron appRegisrty to activate this plugin with.
**/
-function activate(appRegistry: AppRegistry): void {
- appRegistry.registerComponent('Home.Home', HomePlugin);
+function activate(): void {
+ // noop
}
/**
* Deactivate all the components in the Home package.
- * @param {Object} appRegistry - The Hadron appRegisrty to deactivate this plugin with.
**/
-function deactivate(appRegistry: AppRegistry): void {
- appRegistry.deregisterComponent('Home.Home');
+function deactivate(): void {
+ // noop
}
-export default HomePlugin;
+export const CompassHomePlugin = registerHadronPlugin({
+ name: 'CompassHome',
+ component: Home,
+ activate(/* ..., { globalAppRegistry, localAppRegistry } */) {
+ // TODO: This is where we should be subscribing to appRegistry events
+ // instead of passing it directly to the Home component. Keeping it as-is
+ // as cleaning up compass-home is a bigger refactor that is not in scope
+ return {
+ store: {
+ state: {},
+ },
+ deactivate() {
+ /* noop */
+ },
+ };
+ },
+});
+
export { activate, deactivate };
export { default as metadata } from '../package.json';
diff --git a/packages/compass-home/src/plugin.tsx b/packages/compass-home/src/plugin.tsx
deleted file mode 100644
index a0b4b854085..00000000000
--- a/packages/compass-home/src/plugin.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import React from 'react';
-import type AppRegistry from 'hadron-app-registry';
-import Home from './components/home';
-import AppRegistryContext from './contexts/app-registry-context';
-
-function Plugin({
- appRegistry,
- ...homeProps
-}: {
- appRegistry: AppRegistry;
-} & React.ComponentProps): React.ReactElement {
- return (
-
-
-
- );
-}
-
-Plugin.displayName = 'HomePlugin';
-
-export default Plugin;
diff --git a/packages/compass-import-export/src/components/export-modal.tsx b/packages/compass-import-export/src/components/export-modal.tsx
index 791842997bb..548ef708bb5 100644
--- a/packages/compass-import-export/src/components/export-modal.tsx
+++ b/packages/compass-import-export/src/components/export-modal.tsx
@@ -17,7 +17,7 @@ import {
spacing,
createElectronFileInputBackend,
} from '@mongodb-js/compass-components';
-import { useTrackOnChange } from '@mongodb-js/compass-logging';
+import { useTrackOnChange } from '@mongodb-js/compass-logging/provider';
import {
closeExport,
@@ -153,8 +153,7 @@ function ExportModal({
}
},
[isOpen],
- undefined,
- React
+ undefined
);
const onClickBack = useCallback(() => {
diff --git a/packages/compass-import-export/src/components/import-modal.tsx b/packages/compass-import-export/src/components/import-modal.tsx
index fc669218c10..f8c75257285 100644
--- a/packages/compass-import-export/src/components/import-modal.tsx
+++ b/packages/compass-import-export/src/components/import-modal.tsx
@@ -16,7 +16,7 @@ import {
palette,
useDarkMode,
} from '@mongodb-js/compass-components';
-import { useTrackOnChange } from '@mongodb-js/compass-logging';
+import { useTrackOnChange } from '@mongodb-js/compass-logging/provider';
import { FINISHED_STATUSES, STARTED } from '../constants/process-status';
import type { ProcessStatus } from '../constants/process-status';
@@ -162,8 +162,7 @@ function ImportModal({
}
},
[isOpen],
- undefined,
- React
+ undefined
);
if (isOpen && !fileName && errors.length === 0) {
diff --git a/packages/compass-import-export/src/components/import-toast.tsx b/packages/compass-import-export/src/components/import-toast.tsx
index 9120c0ad4da..5eaaf7653be 100644
--- a/packages/compass-import-export/src/components/import-toast.tsx
+++ b/packages/compass-import-export/src/components/import-toast.tsx
@@ -1,7 +1,7 @@
import React from 'react';
import { Body, css, openToast } from '@mongodb-js/compass-components';
import path from 'path';
-import createLoggerAndTelemetry from '@mongodb-js/compass-logging';
+import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging';
import { ToastBody } from './toast-body';
import { openFile } from '../utils/open-file';
diff --git a/packages/compass-import-export/src/modules/export.ts b/packages/compass-import-export/src/modules/export.ts
index 923e8e61461..b9295aa1707 100644
--- a/packages/compass-import-export/src/modules/export.ts
+++ b/packages/compass-import-export/src/modules/export.ts
@@ -1,7 +1,7 @@
import type { Action, AnyAction, Reducer } from 'redux';
import { combineReducers } from 'redux';
import type { ThunkAction } from 'redux-thunk';
-import createLoggerAndTelemetry from '@mongodb-js/compass-logging';
+import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging';
import fs from 'fs';
import _ from 'lodash';
diff --git a/packages/compass-import-export/src/modules/import.ts b/packages/compass-import-export/src/modules/import.ts
index 628ecfb6341..49864582e88 100644
--- a/packages/compass-import-export/src/modules/import.ts
+++ b/packages/compass-import-export/src/modules/import.ts
@@ -11,7 +11,7 @@ import path from 'path';
import { combineReducers } from 'redux';
import type { Action, AnyAction, Reducer } from 'redux';
import type { ThunkAction, ThunkDispatch } from 'redux-thunk';
-import createLoggerAndTelemetry from '@mongodb-js/compass-logging';
+import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging';
import PROCESS_STATUS from '../constants/process-status';
import FILE_TYPES from '../constants/file-types';
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 b6f5cacf2fb..80e8d246943 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
@@ -1,6 +1,6 @@
import React, { useCallback } from 'react';
import { connect } from 'react-redux';
-import { useTrackOnChange } from '@mongodb-js/compass-logging';
+import { useTrackOnChange } from '@mongodb-js/compass-logging/provider';
import {
Modal,
ModalFooter,
@@ -59,8 +59,7 @@ function CreateIndexModal({
}
},
[isVisible],
- undefined,
- React
+ undefined
);
return (
diff --git a/packages/compass-indexes/src/components/drop-index-modal/index.tsx b/packages/compass-indexes/src/components/drop-index-modal/index.tsx
index 746857afdd8..82eeffb60e3 100644
--- a/packages/compass-indexes/src/components/drop-index-modal/index.tsx
+++ b/packages/compass-indexes/src/components/drop-index-modal/index.tsx
@@ -20,7 +20,7 @@ import { dropIndex } from '../../modules/drop-index';
import { resetForm } from '../../modules/reset-form';
import type { RootState } from '../../modules/drop-index';
-import { useTrackOnChange } from '@mongodb-js/compass-logging';
+import { useTrackOnChange } from '@mongodb-js/compass-logging/provider';
const messageStyles = css({
display: 'flex',
@@ -127,8 +127,7 @@ export function DropIndexModal({
}
},
[isVisible],
- undefined,
- React
+ undefined
);
return (
diff --git a/packages/compass-indexes/src/components/search-indexes-modals/base-search-index-modal.tsx b/packages/compass-indexes/src/components/search-indexes-modals/base-search-index-modal.tsx
index 63c92bf0623..27d089ec2ef 100644
--- a/packages/compass-indexes/src/components/search-indexes-modals/base-search-index-modal.tsx
+++ b/packages/compass-indexes/src/components/search-indexes-modals/base-search-index-modal.tsx
@@ -32,7 +32,7 @@ import {
import type { EditorRef } from '@mongodb-js/compass-editor';
import _parseShellBSON, { ParseMode } from 'ejson-shell-parser';
import type { Document } from 'mongodb';
-import { useTrackOnChange } from '@mongodb-js/compass-logging';
+import { useTrackOnChange } from '@mongodb-js/compass-logging/provider';
import { SearchIndexTemplateDropdown } from '../search-index-template-dropdown';
import type { SearchTemplate } from '@mongodb-js/mongodb-constants';
import type { Field } from '../../modules/fields';
@@ -160,8 +160,7 @@ export const BaseSearchIndexModal: React.FunctionComponent<
}
},
[isModalOpen, mode],
- undefined,
- React
+ undefined
);
useEffect(() => {
diff --git a/packages/compass-logging/package.json b/packages/compass-logging/package.json
index 446d3f818c0..37ae4496859 100644
--- a/packages/compass-logging/package.json
+++ b/packages/compass-logging/package.json
@@ -19,16 +19,22 @@
"url": "https://github.com/mongodb-js/compass.git"
},
"files": [
- "dist"
+ "dist",
+ "provier.js"
],
"license": "SSPL",
"peerDependencies": {
"hadron-ipc": "^3.2.4"
},
"main": "dist/index.js",
+ "exports": {
+ ".": "./dist/index.js",
+ "./provider": "./dist/provider.js"
+ },
"compass:main": "src/index.ts",
"compass:exports": {
- ".": "./src/index.ts"
+ ".": "./src/index.ts",
+ "./provider": "./src/provider.ts"
},
"types": "./dist/index.d.ts",
"scripts": {
@@ -50,7 +56,8 @@
"dependencies": {
"debug": "^4.3.4",
"is-electron-renderer": "^2.0.1",
- "mongodb-log-writer": "^1.3.0"
+ "mongodb-log-writer": "^1.3.0",
+ "react": "^17.0.2"
},
"devDependencies": {
"@mongodb-js/eslint-config-compass": "^1.0.11",
diff --git a/packages/compass-logging/provider.d.ts b/packages/compass-logging/provider.d.ts
new file mode 100644
index 00000000000..6084d76ab26
--- /dev/null
+++ b/packages/compass-logging/provider.d.ts
@@ -0,0 +1 @@
+export * from './dist/provider.d';
diff --git a/packages/compass-logging/src/index.spec.ts b/packages/compass-logging/src/index.spec.ts
index 2e0e2aa7c10..a87a59bf310 100644
--- a/packages/compass-logging/src/index.spec.ts
+++ b/packages/compass-logging/src/index.spec.ts
@@ -1,4 +1,4 @@
-import createLoggerAndTelemetry from './';
+import { createLoggerAndTelemetry } from './';
import { once } from 'events';
import { expect } from 'chai';
diff --git a/packages/compass-logging/src/index.ts b/packages/compass-logging/src/index.ts
index 6486a3216ff..da53760fa07 100644
--- a/packages/compass-logging/src/index.ts
+++ b/packages/compass-logging/src/index.ts
@@ -1,11 +1,5 @@
-export { createLoggerAndTelemetry } from './logger';
-export { createLoggerAndTelemetry as default } from './logger';
+export { createLoggerAndTelemetry } from './ipc-logger';
export type { LoggerAndTelemetry } from './logger';
-export {
- useLoggerAndTelemetry,
- useTrackOnChange,
- withLoggerAndTelemetry,
-} from './react';
export { mongoLogId } from 'mongodb-log-writer';
import createDebug from 'debug';
export const debug = createDebug('mongodb-compass');
diff --git a/packages/compass-logging/src/ipc-logger.ts b/packages/compass-logging/src/ipc-logger.ts
new file mode 100644
index 00000000000..1c548d7e378
--- /dev/null
+++ b/packages/compass-logging/src/ipc-logger.ts
@@ -0,0 +1,25 @@
+import isElectronRenderer from 'is-electron-renderer';
+import type { HadronIpcRenderer } from 'hadron-ipc';
+import { createGenericLoggerAndTelemetry } from './logger';
+
+function emit(
+ ipc: HadronIpcRenderer | null | undefined,
+ event: string,
+ data: Record
+): void {
+ // We use ipc.callQuiet instead of ipc.call because we already
+ // print debugging messages below
+ void ipc?.callQuiet?.(event, data);
+ if (typeof process !== 'undefined' && typeof process.emit === 'function') {
+ (process as any).emit(event, data);
+ }
+}
+
+export function createLoggerAndTelemetry(component: string) {
+ // This application may not be running in an Node.js/Electron context.
+ const ipc: HadronIpcRenderer | null | undefined = isElectronRenderer
+ ? // eslint-disable-next-line @typescript-eslint/no-var-requires
+ require('hadron-ipc').ipcRenderer
+ : null;
+ return createGenericLoggerAndTelemetry(component, emit.bind(null, ipc));
+}
diff --git a/packages/compass-logging/src/logger.ts b/packages/compass-logging/src/logger.ts
index 57ecaef9119..0d4cddd3b49 100644
--- a/packages/compass-logging/src/logger.ts
+++ b/packages/compass-logging/src/logger.ts
@@ -1,9 +1,7 @@
import type { MongoLogEntry } from 'mongodb-log-writer';
import { MongoLogWriter, mongoLogId } from 'mongodb-log-writer';
-import isElectronRenderer from 'is-electron-renderer';
import createDebug from 'debug';
import type { Writable } from 'stream';
-import type { HadronIpcRenderer } from 'hadron-ipc';
let preferences: {
getPreferences(): { trackUsageStatistics: boolean };
@@ -12,19 +10,6 @@ let preferences: {
type TrackProps = Record | (() => Record);
type TrackFunction = (event: string, properties?: TrackProps) => void;
-function emit(
- ipc: HadronIpcRenderer | null | undefined,
- event: string,
- data: Record
-): void {
- // We use ipc.callQuiet instead of ipc.call because we already
- // print debugging messages below
- void ipc?.callQuiet?.(event, data);
- if (typeof process !== 'undefined' && typeof process.emit === 'function') {
- (process as any).emit(event, data);
- }
-}
-
export type LoggerAndTelemetry = {
log: ReturnType;
mongoLogId: typeof mongoLogId;
@@ -32,22 +17,17 @@ export type LoggerAndTelemetry = {
track: TrackFunction;
};
-export function createLoggerAndTelemetry(
- component: string
+export function createGenericLoggerAndTelemetry(
+ component: string,
+ emit: (event: string, arg: any) => void
): LoggerAndTelemetry {
- // This application may not be running in an Node.js/Electron context.
- const ipc: HadronIpcRenderer | null | undefined = isElectronRenderer
- ? // eslint-disable-next-line @typescript-eslint/no-var-requires
- require('hadron-ipc').ipcRenderer
- : null;
-
// Do not create an actual Writable stream here, since the callback
// logic in Node.js streams would mean that two writes from the
// same event loop tick would not be written synchronously,
// allowing another logger's write to be written out-of-order.
const target = {
write(line: string, callback: () => void) {
- emit(ipc, 'compass:log', { line });
+ emit('compass:log', { line });
callback();
},
end(callback: () => void) {
@@ -99,7 +79,7 @@ export function createLoggerAndTelemetry(
// for instance if we can't fetch host information,
// we track a new error indicating the failure.
// This is so that we are aware of which events might be misreported.
- emit(ipc, 'compass:track', {
+ emit('compass:track', {
event: 'Error Fetching Attributes',
properties: {
event_name: event,
@@ -118,7 +98,7 @@ export function createLoggerAndTelemetry(
return;
}
}
- emit(ipc, 'compass:track', data);
+ emit('compass:track', data);
};
const debug = createDebug(`mongodb-compass:${component.toLowerCase()}`);
@@ -136,5 +116,3 @@ export function createLoggerAndTelemetry(
track,
};
}
-
-export default createLoggerAndTelemetry;
diff --git a/packages/compass-logging/src/provider.ts b/packages/compass-logging/src/provider.ts
new file mode 100644
index 00000000000..2836f6295a5
--- /dev/null
+++ b/packages/compass-logging/src/provider.ts
@@ -0,0 +1,71 @@
+import React from 'react';
+import type { LoggerAndTelemetry } from './logger';
+export type { LoggerAndTelemetry } from './logger';
+
+function defaultCreateLoggerAndTelemetry(component: string) {
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
+ return require('./logger').createGenericLoggerAndTelemetry(component, () => {
+ /* ignore */
+ });
+}
+
+const LoggerAndTelemetryContext = React.createContext<
+ (component: string) => LoggerAndTelemetry
+>(defaultCreateLoggerAndTelemetry);
+
+export const LoggerAndTelemetryProvider = LoggerAndTelemetryContext.Provider;
+
+export function createLoggerAndTelemetryLocator(component: string) {
+ return useLoggerAndTelemetry.bind(null, component);
+}
+
+export function useLoggerAndTelemetry(component: string): LoggerAndTelemetry {
+ const createLoggerAndTelemetry = React.useContext(LoggerAndTelemetryContext);
+ if (!createLoggerAndTelemetry) {
+ throw new Error('LoggerAndTelemetry service is missing from React context');
+ }
+ const loggerRef = React.createRef();
+ if (!loggerRef.current) {
+ (loggerRef as any).current = createLoggerAndTelemetry(component);
+ }
+ return loggerRef.current!;
+}
+
+export function useTrackOnChange(
+ component: string,
+ onChange: (track: LoggerAndTelemetry['track']) => void,
+ dependencies: unknown[],
+ options: { skipOnMount: boolean } = { skipOnMount: false }
+) {
+ const onChangeRef = React.useRef(onChange);
+ onChangeRef.current = onChange;
+ const { track } = useLoggerAndTelemetry(component);
+ let initial = true;
+ React.useEffect(() => {
+ if (options.skipOnMount && initial) {
+ initial = false;
+ return;
+ }
+ onChangeRef.current(track);
+ }, [...dependencies, track]);
+}
+
+type FirstArgument = F extends (...args: [infer A, ...any]) => any
+ ? A
+ : F extends { new (...args: [infer A, ...any]): any }
+ ? A
+ : never;
+export function withLoggerAndTelemetry<
+ T extends ((...args: any[]) => any) | { new (...args: any[]): any }
+>(
+ ReactComponent: T,
+ component: string
+): React.FunctionComponent, 'logger'>> {
+ const WithLoggerAndTelemetry = (
+ props: Omit, 'logger'> & React.Attributes
+ ) => {
+ const logger = useLoggerAndTelemetry(component);
+ return React.createElement(ReactComponent, { ...props, logger });
+ };
+ return WithLoggerAndTelemetry;
+}
diff --git a/packages/compass-logging/src/react.ts b/packages/compass-logging/src/react.ts
deleted file mode 100644
index 63eafc30a9f..00000000000
--- a/packages/compass-logging/src/react.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import createLoggerAndTelemetry from './logger';
-import type { LoggerAndTelemetry } from './logger';
-
-export function useLoggerAndTelemetry(
- component: string,
- React: { useRef: any }
-): LoggerAndTelemetry {
- const loggerRef = React.useRef();
- if (!loggerRef.current) {
- loggerRef.current = createLoggerAndTelemetry(component);
- }
- return loggerRef.current as LoggerAndTelemetry;
-}
-
-export function useTrackOnChange(
- component: string,
- onChange: (track: LoggerAndTelemetry['track']) => void,
- dependencies: unknown[],
- options: { skipOnMount: boolean } = { skipOnMount: false },
- React: { useRef: any; useEffect: any }
-) {
- const onChangeRef = React.useRef(onChange);
- onChangeRef.current = onChange;
- const { track } = useLoggerAndTelemetry(component, React);
- let initial = true;
- React.useEffect(() => {
- if (options.skipOnMount && initial) {
- initial = false;
- return;
- }
- onChangeRef.current(track);
- }, [...dependencies, track]);
-}
-
-type ComponentProps = T extends (props: infer P) => any
- ? P
- : T extends { new (props: infer P): any }
- ? P
- : never;
-
-type ComponentReturnType = T extends (...args: any[]) => infer R
- ? R
- : T extends { new (...args: any[]): { render(...args: any[]): infer R } }
- ? R
- : never;
-
-export function withLoggerAndTelemetry(
- ReactComponent: T,
- component: string,
- React: any
-) {
- const WithLoggerAndTelemetry = (
- props: Omit, 'logger'>
- ): ComponentReturnType => {
- const logger = useLoggerAndTelemetry(component, React);
- return React.createElement(ReactComponent, { ...props, logger });
- };
- return WithLoggerAndTelemetry;
-}
diff --git a/packages/compass-query-bar/src/components/query-ai.tsx b/packages/compass-query-bar/src/components/query-ai.tsx
index 289620d9ffa..70ccca4a936 100644
--- a/packages/compass-query-bar/src/components/query-ai.tsx
+++ b/packages/compass-query-bar/src/components/query-ai.tsx
@@ -2,7 +2,7 @@ import React from 'react';
import { openToast } from '@mongodb-js/compass-components';
import { GenerativeAIInput } from '@mongodb-js/compass-generative-ai';
import { connect } from 'react-redux';
-import createLoggerAndTelemetry from '@mongodb-js/compass-logging';
+import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging';
import { usePreference } from 'compass-preferences-model';
import type { RootState } from '../stores/query-bar-store';
diff --git a/packages/compass-query-bar/src/components/query-history-button-popover.tsx b/packages/compass-query-bar/src/components/query-history-button-popover.tsx
index 2c7320051ce..7ddadcdcb8e 100644
--- a/packages/compass-query-bar/src/components/query-history-button-popover.tsx
+++ b/packages/compass-query-bar/src/components/query-history-button-popover.tsx
@@ -7,7 +7,7 @@ import {
focusRing,
spacing,
} from '@mongodb-js/compass-components';
-import { useTrackOnChange } from '@mongodb-js/compass-logging';
+import { useTrackOnChange } from '@mongodb-js/compass-logging/provider';
import QueryHistory from './query-history';
import { fetchSavedQueries } from '../stores/query-bar-reducer';
@@ -51,8 +51,7 @@ const QueryHistoryButtonPopover = ({
}
},
[isOpen],
- undefined,
- React
+ undefined
);
const setOpen = useCallback(
diff --git a/packages/compass-query-bar/src/components/query-history/index.tsx b/packages/compass-query-bar/src/components/query-history/index.tsx
index 7b26b68e321..d89304f16fd 100644
--- a/packages/compass-query-bar/src/components/query-history/index.tsx
+++ b/packages/compass-query-bar/src/components/query-history/index.tsx
@@ -7,7 +7,7 @@ import FavoriteList from './favorite-list';
import { connect } from 'react-redux';
import type { RootState } from '../../stores/query-bar-store';
-import { useTrackOnChange } from '@mongodb-js/compass-logging';
+import { useTrackOnChange } from '@mongodb-js/compass-logging/provider';
const containerStyle = css({
display: 'flex',
@@ -42,8 +42,7 @@ const QueryHistory = ({ namespace }: QueryHistoryProps) => {
}
},
[tab],
- undefined,
- React
+ undefined
);
return (
diff --git a/packages/compass-query-bar/src/stores/ai-query-reducer.ts b/packages/compass-query-bar/src/stores/ai-query-reducer.ts
index aae74f7fda3..e04579268f1 100644
--- a/packages/compass-query-bar/src/stores/ai-query-reducer.ts
+++ b/packages/compass-query-bar/src/stores/ai-query-reducer.ts
@@ -1,5 +1,5 @@
import type { Reducer } from 'redux';
-import createLoggerAndTelemetry from '@mongodb-js/compass-logging';
+import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging';
import { getSimplifiedSchema } from 'mongodb-schema';
import toNS from 'mongodb-ns';
import preferences from 'compass-preferences-model';
diff --git a/packages/compass-schema/src/modules/schema-analysis.ts b/packages/compass-schema/src/modules/schema-analysis.ts
index 18d19db31b1..48f7f3f2563 100644
--- a/packages/compass-schema/src/modules/schema-analysis.ts
+++ b/packages/compass-schema/src/modules/schema-analysis.ts
@@ -1,6 +1,6 @@
import { isInternalFieldPath } from 'hadron-document';
import type { AggregateOptions, Filter, Document } from 'mongodb';
-import createLoggerAndTelemetry from '@mongodb-js/compass-logging';
+import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging';
import type { DataService } from 'mongodb-data-service';
import mongodbSchema from 'mongodb-schema';
import type {
diff --git a/packages/compass-settings/src/components/index.tsx b/packages/compass-settings/src/components/index.tsx
index cd7526f7e5d..60573fbce09 100644
--- a/packages/compass-settings/src/components/index.tsx
+++ b/packages/compass-settings/src/components/index.tsx
@@ -1,16 +1,14 @@
import React from 'react';
import SettingsModal from './modal';
-import { Provider } from 'react-redux';
-import store from '../stores';
import { ConfirmationModalArea } from '@mongodb-js/compass-components';
const SettingsModalWithStore: React.FunctionComponent = () => {
return (
-
+ <>
-
+ >
);
};
diff --git a/packages/compass-settings/src/components/modal.spec.tsx b/packages/compass-settings/src/components/modal.spec.tsx
index a08c9660101..4ae832b0b88 100644
--- a/packages/compass-settings/src/components/modal.spec.tsx
+++ b/packages/compass-settings/src/components/modal.spec.tsx
@@ -13,7 +13,7 @@ import { expect } from 'chai';
import { Provider } from 'react-redux';
import userEvent from '@testing-library/user-event';
-import store from '../stores';
+import { configureStore } from '../stores';
import { SettingsModal } from './modal';
describe('SettingsModal', function () {
@@ -29,6 +29,7 @@ describe('SettingsModal', function () {
fetchSettingsSpy = stub().resolves();
onSaveSpy = spy();
+ const store = configureStore({ logger: stub() as any });
renderSettingsModal = (
props: Partial> = {}
) => {
diff --git a/packages/compass-settings/src/components/settings/general.spec.tsx b/packages/compass-settings/src/components/settings/general.spec.tsx
index acb0c83c8c1..6f9b99bc4ca 100644
--- a/packages/compass-settings/src/components/settings/general.spec.tsx
+++ b/packages/compass-settings/src/components/settings/general.spec.tsx
@@ -2,6 +2,7 @@ import React from 'react';
import { cleanup, render, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { expect } from 'chai';
+import { stub } from 'sinon';
import { Provider } from 'react-redux';
import { GeneralSettings } from './general';
import { configureStore } from '../../stores';
@@ -16,7 +17,7 @@ describe('GeneralSettings', function () {
}
beforeEach(async function () {
- store = configureStore();
+ store = configureStore({ logger: stub() as any });
await store.dispatch(fetchSettings());
const component = () => (
diff --git a/packages/compass-settings/src/components/settings/oidc-settings.spec.tsx b/packages/compass-settings/src/components/settings/oidc-settings.spec.tsx
index 5e983fc1aec..72af1f46310 100644
--- a/packages/compass-settings/src/components/settings/oidc-settings.spec.tsx
+++ b/packages/compass-settings/src/components/settings/oidc-settings.spec.tsx
@@ -2,6 +2,7 @@ import React from 'react';
import { cleanup, render, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { expect } from 'chai';
+import { stub } from 'sinon';
import { Provider } from 'react-redux';
import { OIDCSettings } from './oidc-settings';
import { configureStore } from '../../stores';
@@ -16,7 +17,7 @@ describe('OIDCSettings', function () {
}
beforeEach(async function () {
- store = configureStore();
+ store = configureStore({ logger: stub() as any });
await store.dispatch(fetchSettings());
const component = () => (
diff --git a/packages/compass-settings/src/components/settings/privacy.spec.tsx b/packages/compass-settings/src/components/settings/privacy.spec.tsx
index ab97991ff99..a4772d4be33 100644
--- a/packages/compass-settings/src/components/settings/privacy.spec.tsx
+++ b/packages/compass-settings/src/components/settings/privacy.spec.tsx
@@ -2,6 +2,7 @@ import React from 'react';
import { cleanup, render, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { expect } from 'chai';
+import { stub } from 'sinon';
import { Provider } from 'react-redux';
import { PrivacySettings } from './privacy';
import { configureStore } from '../../stores';
@@ -29,7 +30,7 @@ describe('PrivacySettings', function () {
}
beforeEach(async function () {
- store = configureStore();
+ store = configureStore({ logger: stub() as any });
await store.dispatch(fetchSettings());
});
diff --git a/packages/compass-settings/src/index.ts b/packages/compass-settings/src/index.ts
index c68341a6817..2225ee39bfb 100644
--- a/packages/compass-settings/src/index.ts
+++ b/packages/compass-settings/src/index.ts
@@ -1,21 +1,24 @@
-import type AppRegistry from 'hadron-app-registry';
+import { registerHadronPlugin } from 'hadron-app-registry';
+import { createLoggerAndTelemetryLocator } from '@mongodb-js/compass-logging/provider';
import SettingsPlugin from './components/index';
-import SettingsStore from './stores';
+import { onActivated } from './stores';
-const ROLE = {
- name: 'SettingsModal',
- component: SettingsPlugin,
-};
-
-function activate(appRegistry: AppRegistry): void {
- appRegistry.registerRole('Global.Modal', ROLE);
- appRegistry.registerStore('Settings.Store', SettingsStore);
+function activate(): void {
+ // noop
}
-function deactivate(appRegistry: AppRegistry): void {
- appRegistry.deregisterRole('Global.Modal', ROLE);
- appRegistry.deregisterStore('Settings.Store');
+function deactivate(): void {
+ // noop
}
+export const CompassSettingsPlugin = registerHadronPlugin(
+ {
+ name: 'CompassSettings',
+ component: SettingsPlugin,
+ activate: onActivated,
+ },
+ { logger: createLoggerAndTelemetryLocator('COMPASS-SETTINGS') }
+);
+
export { activate, deactivate };
export { default as metadata } from '../package.json';
diff --git a/packages/compass-settings/src/stores/index.ts b/packages/compass-settings/src/stores/index.ts
index 02992afd2f6..578674f6da5 100644
--- a/packages/compass-settings/src/stores/index.ts
+++ b/packages/compass-settings/src/stores/index.ts
@@ -13,18 +13,22 @@ import atlasLoginReducer, {
atlasServiceTokenRefreshFailed,
atlasServiceUserConfigChanged,
} from './atlas-login';
+import type { LoggerAndTelemetry } from '@mongodb-js/compass-logging/provider';
export type Public = { [K in keyof T]: T[K] };
-export function configureStore({
- preferencesSandbox,
- atlasService,
-}: {
- preferencesSandbox?: Public;
- atlasService?: Public;
-} = {}) {
- preferencesSandbox ??= new PreferencesSandbox();
- atlasService ??= new AtlasService();
+type ThunkExtraArg = {
+ preferencesSandbox: Public;
+ atlasService: Public;
+ logger: LoggerAndTelemetry;
+};
+
+export function configureStore(
+ options: Pick & Partial
+) {
+ const preferencesSandbox =
+ options?.preferencesSandbox ?? new PreferencesSandbox();
+ const atlasService = options?.atlasService ?? new AtlasService();
const store = createStore(
combineReducers({
@@ -35,7 +39,11 @@ export function configureStore({
atlasLogin: ReturnType;
}>, // combineReducers CombinedState return type is broken, have to remove the EmptyObject from the union that it returns
applyMiddleware(
- thunk.withExtraArgument({ preferencesSandbox, atlasService })
+ thunk.withExtraArgument({
+ preferencesSandbox,
+ atlasService,
+ logger: options.logger,
+ })
)
);
@@ -58,32 +66,38 @@ export function configureStore({
return store;
}
-const store = configureStore();
-
-export type RootState = ReturnType;
+export type RootState = ReturnType<
+ ReturnType['getState']
+>;
export type SettingsThunkAction<
R,
A extends AnyAction = AnyAction
-> = ThunkAction<
- R,
- RootState,
+> = ThunkAction;
+
+const onActivated = (
+ _: unknown,
{
- preferencesSandbox: Public;
- atlasService: Public;
- },
- A
->;
+ globalAppRegistry,
+ logger,
+ }: { globalAppRegistry: AppRegistry; logger: LoggerAndTelemetry }
+) => {
+ const store = configureStore({ logger });
-(store as any).onActivated = (appRegistry: AppRegistry) => {
- appRegistry.on('open-compass-settings', () => {
+ const onOpenSettings = () => {
void store.dispatch(openModal());
- });
- ipcRenderer?.on('window:show-settings', () => {
- void store.dispatch(openModal());
- });
-};
+ };
+
+ globalAppRegistry.on('open-compass-settings', onOpenSettings);
+ ipcRenderer?.on('window:show-settings', onOpenSettings);
-export default store as typeof store & {
- onActivated(appRegistry: AppRegistry): void;
+ return {
+ store,
+ deactivate() {
+ globalAppRegistry.removeListener('open-compass-settings', onOpenSettings);
+ ipcRenderer?.removeListener('window:show-settings', onOpenSettings);
+ },
+ };
};
+
+export { onActivated };
diff --git a/packages/compass-settings/src/stores/settings.ts b/packages/compass-settings/src/stores/settings.ts
index ff986877077..d48b7ce3d0a 100644
--- a/packages/compass-settings/src/stores/settings.ts
+++ b/packages/compass-settings/src/stores/settings.ts
@@ -1,14 +1,11 @@
import type { Reducer } from 'redux';
import type { SettingsThunkAction } from '.';
-import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging';
import type {
PreferenceStateInformation,
UserConfigurablePreferences,
} from 'compass-preferences-model';
import { cancelAtlasLoginAttempt } from './atlas-login';
-const { log, mongoLogId } = createLoggerAndTelemetry('COMPASS-SETTINGS');
-
export type State = { isModalOpen: boolean } & (
| {
loadingState: 'loading';
@@ -123,15 +120,15 @@ export const fetchSettings = (): SettingsThunkAction> => {
return async (
dispatch,
_getState,
- { preferencesSandbox: sandbox }
+ { preferencesSandbox: sandbox, logger }
): Promise => {
try {
dispatch({ type: ActionTypes.SettingsFetchedStart });
await sandbox.setupSandbox();
await dispatch(syncSandboxStateToStore());
} catch (e) {
- log.warn(
- mongoLogId(1_001_000_145),
+ logger.log.warn(
+ logger.mongoLogId(1_001_000_145),
'Settings',
'Failed to fetch settings',
{ message: (e as Error).message }
@@ -147,7 +144,7 @@ export const changeFieldValue = (
return async (
dispatch,
getState,
- { preferencesSandbox: sandbox }
+ { preferencesSandbox: sandbox, logger }
): Promise => {
const { loadingState } = getState().settings;
if (loadingState === 'loading') {
@@ -163,8 +160,8 @@ export const changeFieldValue = (
// can fail if user input doesn't pass validation.
await sandbox.updateField(field, value);
} catch (err) {
- log.error(
- mongoLogId(1_001_000_223),
+ logger.log.error(
+ logger.mongoLogId(1_001_000_223),
'Settings',
'Failed to change settings value',
{ error: (err as Error).stack }
@@ -194,7 +191,7 @@ export const saveSettings = (): SettingsThunkAction> => {
return async (
dispatch,
getState,
- { preferencesSandbox: sandbox }
+ { preferencesSandbox: sandbox, logger }
): Promise => {
const { loadingState } = getState().settings;
if (loadingState === 'loading') {
@@ -208,8 +205,8 @@ export const saveSettings = (): SettingsThunkAction> => {
type: ActionTypes.SettingsSaved,
});
} catch (e) {
- log.warn(
- mongoLogId(1_001_000_146),
+ logger.log.warn(
+ logger.mongoLogId(1_001_000_146),
'Settings',
'Failed to update settings',
{ message: (e as Error).message }
diff --git a/packages/compass-shell/package.json b/packages/compass-shell/package.json
index d608a546fca..d5b0376c027 100644
--- a/packages/compass-shell/package.json
+++ b/packages/compass-shell/package.json
@@ -86,7 +86,6 @@
"enzyme": "^3.11.0",
"eslint": "^7.25.0",
"hadron-app-registry": "^9.0.13",
- "hadron-ipc": "^3.2.4",
"mocha": "^10.2.0",
"nyc": "^15.1.0",
"prop-types": "^15.7.2",
diff --git a/packages/compass-shell/src/components/shell-info-modal/shell-info-modal.jsx b/packages/compass-shell/src/components/shell-info-modal/shell-info-modal.jsx
index c009ebc32ab..05fd7a2e233 100644
--- a/packages/compass-shell/src/components/shell-info-modal/shell-info-modal.jsx
+++ b/packages/compass-shell/src/components/shell-info-modal/shell-info-modal.jsx
@@ -8,7 +8,7 @@ import {
Subtitle,
spacing,
} from '@mongodb-js/compass-components';
-import { useTrackOnChange } from '@mongodb-js/compass-logging';
+import { useTrackOnChange } from '@mongodb-js/compass-logging/provider';
import { KeyboardShortcutsTable } from './keyboard-shortcuts-table';
diff --git a/packages/compass-shell/src/stores/store.js b/packages/compass-shell/src/stores/store.js
index c724c65aa54..29d7744d6d9 100644
--- a/packages/compass-shell/src/stores/store.js
+++ b/packages/compass-shell/src/stores/store.js
@@ -3,7 +3,7 @@ import reducer from '../modules';
import { changeEnableShell, setupRuntime } from '../modules/runtime';
import { globalAppRegistryActivated } from '@mongodb-js/mongodb-redux-common/app-registry';
import { setupLoggerAndTelemetry } from '@mongosh/logging';
-import createLoggerAndTelemetry from '@mongodb-js/compass-logging';
+import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging';
import preferences from 'compass-preferences-model';
const { log, debug, track } = createLoggerAndTelemetry('COMPASS-SHELL');
diff --git a/packages/compass-sidebar/src/components/csfle-connection-modal.tsx b/packages/compass-sidebar/src/components/csfle-connection-modal.tsx
index 1474c8e50bc..bc5aac62599 100644
--- a/packages/compass-sidebar/src/components/csfle-connection-modal.tsx
+++ b/packages/compass-sidebar/src/components/csfle-connection-modal.tsx
@@ -10,7 +10,7 @@ import {
Toggle,
InfoModal,
} from '@mongodb-js/compass-components';
-import { useTrackOnChange } from '@mongodb-js/compass-logging';
+import { useTrackOnChange } from '@mongodb-js/compass-logging/provider';
const toggleStyles = css({
marginTop: spacing[3],
@@ -57,8 +57,7 @@ export default function CSFLEConnectionModal({
}
},
[open],
- undefined,
- React
+ undefined
);
return (
diff --git a/packages/compass-sidebar/src/components/non-genuine-warning-modal.tsx b/packages/compass-sidebar/src/components/non-genuine-warning-modal.tsx
index ce1afe4c5a2..dbf72ee1034 100644
--- a/packages/compass-sidebar/src/components/non-genuine-warning-modal.tsx
+++ b/packages/compass-sidebar/src/components/non-genuine-warning-modal.tsx
@@ -8,7 +8,7 @@ import {
Body,
BannerVariant,
} from '@mongodb-js/compass-components';
-import { useTrackOnChange } from '@mongodb-js/compass-logging';
+import { useTrackOnChange } from '@mongodb-js/compass-logging/provider';
const modalBodyStyles = css({
marginTop: spacing[3],
@@ -43,8 +43,7 @@ function NonGenuineWarningModal({
}
},
[isVisible],
- undefined,
- React
+ undefined
);
return (
diff --git a/packages/compass/src/app/index.jsx b/packages/compass/src/app/index.jsx
index 84615e460fa..a947eb03bbe 100644
--- a/packages/compass/src/app/index.jsx
+++ b/packages/compass/src/app/index.jsx
@@ -4,12 +4,15 @@ import '../setup-hadron-distribution';
import dns from 'dns';
import { ipcRenderer } from 'hadron-ipc';
import * as remote from '@electron/remote';
-
+import { AppRegistryProvider, globalAppRegistry } from 'hadron-app-registry';
import preferences, { getActiveUser } from 'compass-preferences-model';
+import { CompassHomePlugin } from '@mongodb-js/compass-home';
// https://github.com/nodejs/node/issues/40537
dns.setDefaultResultOrder('ipv4first');
+app.appRegistry = globalAppRegistry;
+
// this is so sub-processes (ie. the shell) will do the same
process.env.NODE_OPTIONS ??= '';
if (!process.env.NODE_OPTIONS.includes('--dns-result-order')) {
@@ -65,6 +68,7 @@ import { setupTheme } from './theme';
import { setupIntercom } from './intercom';
+import { LoggerAndTelemetryProvider } from '@mongodb-js/compass-logging/provider';
import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging';
const { log, mongoLogId, track } = createLoggerAndTelemetry('COMPASS-APP');
@@ -174,19 +178,16 @@ const Application = View.extend({
this.el = document.querySelector('#application');
this.renderWithTemplate(this);
- const HomeComponent = app.appRegistry.getComponent('Home.Home');
-
- if (!HomeComponent) {
- throw new Error("Can't find Home plugin in appRegistry");
- }
-
ReactDOM.render(
-
+
+
+
+
+
,
this.queryByHook('layout-container')
);
diff --git a/packages/compass/src/app/intercom/intercom-script.ts b/packages/compass/src/app/intercom/intercom-script.ts
index 8291c066e1a..8069f691a4a 100644
--- a/packages/compass/src/app/intercom/intercom-script.ts
+++ b/packages/compass/src/app/intercom/intercom-script.ts
@@ -1,4 +1,4 @@
-import createLoggerAndTelemetry from '@mongodb-js/compass-logging';
+import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging';
const { debug, mongoLogId, log } = createLoggerAndTelemetry('COMPASS-INTERCOM');
const INTERCOM_SCRIPT_ELEM_ID = 'intercom-script';
diff --git a/packages/compass/src/app/intercom/setup-intercom.ts b/packages/compass/src/app/intercom/setup-intercom.ts
index 279b517a97e..a67943715e4 100644
--- a/packages/compass/src/app/intercom/setup-intercom.ts
+++ b/packages/compass/src/app/intercom/setup-intercom.ts
@@ -1,4 +1,4 @@
-import createLoggerAndTelemetry from '@mongodb-js/compass-logging';
+import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging';
import type { IntercomMetadata } from './intercom-script';
import { IntercomScript, buildIntercomScriptUrl } from './intercom-script';
diff --git a/packages/compass/src/main/application.ts b/packages/compass/src/main/application.ts
index 8dd1ba31041..a15a1195d6d 100644
--- a/packages/compass/src/main/application.ts
+++ b/packages/compass/src/main/application.ts
@@ -16,7 +16,7 @@ import preferences, {
} from 'compass-preferences-model';
import { AtlasService } from '@mongodb-js/atlas-service/main';
import { defaultsDeep } from 'lodash';
-import createLoggerAndTelemetry from '@mongodb-js/compass-logging';
+import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging';
import { setupTheme } from './theme';
import { setupProtocolHandlers } from './protocol-handling';
import { ConnectionStorage } from '@mongodb-js/connection-storage/main';
diff --git a/packages/compass/src/main/protocol-handling.ts b/packages/compass/src/main/protocol-handling.ts
index fb4486333ad..9e6a3ddb60d 100644
--- a/packages/compass/src/main/protocol-handling.ts
+++ b/packages/compass/src/main/protocol-handling.ts
@@ -2,7 +2,7 @@ import preferencesAccess from 'compass-preferences-model';
import { promisify } from 'util';
import { app as electronApp } from 'electron';
import path from 'path';
-import createLoggerAndTelemetry from '@mongodb-js/compass-logging';
+import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging';
import { mongoLogId } from 'mongodb-log-writer';
import type { RegistryItem } from 'winreg-ts';
import { Registry } from 'winreg-ts';
diff --git a/packages/connection-storage/src/connection-storage.ts b/packages/connection-storage/src/connection-storage.ts
index b6e9be83f4d..af38b1a664e 100644
--- a/packages/connection-storage/src/connection-storage.ts
+++ b/packages/connection-storage/src/connection-storage.ts
@@ -3,7 +3,7 @@ import { ipcMain } from 'hadron-ipc';
import keytar from 'keytar';
import type { ConnectionInfo } from './connection-info';
-import createLoggerAndTelemetry from '@mongodb-js/compass-logging';
+import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging';
import {
type ConnectionSecrets,
mergeSecrets,
diff --git a/packages/connection-storage/src/import-export-connection.ts b/packages/connection-storage/src/import-export-connection.ts
index a841c202d0f..3a4fc8533fd 100644
--- a/packages/connection-storage/src/import-export-connection.ts
+++ b/packages/connection-storage/src/import-export-connection.ts
@@ -4,7 +4,7 @@ import type { ConnectionInfo } from './connection-info';
import type { ConnectionSecrets } from './connection-secrets';
import { extractSecrets, mergeSecrets } from './connection-secrets';
import { Decrypter, Encrypter } from './encrypt';
-import createLoggerAndTelemetry from '@mongodb-js/compass-logging';
+import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging';
const { log, mongoLogId, track } = createLoggerAndTelemetry(
'COMPASS-CONNECTION-IMPORT-EXPORT'
diff --git a/packages/databases-collections/package.json b/packages/databases-collections/package.json
index 0cbd3b6fe95..7c6f22cf760 100644
--- a/packages/databases-collections/package.json
+++ b/packages/databases-collections/package.json
@@ -69,7 +69,6 @@
"eslint": "^7.25.0",
"hadron-app": "^5.15.0",
"hadron-app-registry": "^9.0.13",
- "hadron-ipc": "^3.2.4",
"lodash": "^4.17.21",
"mocha": "^10.2.0",
"mongodb": "^6.0.0",
diff --git a/packages/databases-collections/src/components/drop-collection-modal.tsx b/packages/databases-collections/src/components/drop-collection-modal.tsx
index 904bfe4b4bd..880c8d44a04 100644
--- a/packages/databases-collections/src/components/drop-collection-modal.tsx
+++ b/packages/databases-collections/src/components/drop-collection-modal.tsx
@@ -14,7 +14,7 @@ import {
import { dropCollection } from '../modules/drop-collection/drop-collection';
import { toggleIsVisible } from '../modules/is-visible';
import type { RootState } from '../modules/drop-collection/drop-collection';
-import { useTrackOnChange } from '@mongodb-js/compass-logging';
+import { useTrackOnChange } from '@mongodb-js/compass-logging/provider';
const progressContainerStyles = css({
display: 'flex',
@@ -66,8 +66,7 @@ function DropCollectionModal({
}
},
[isVisible],
- undefined,
- React
+ undefined
);
return (
diff --git a/packages/databases-collections/src/components/drop-database-modal.tsx b/packages/databases-collections/src/components/drop-database-modal.tsx
index af3a880da46..6292cd07961 100644
--- a/packages/databases-collections/src/components/drop-database-modal.tsx
+++ b/packages/databases-collections/src/components/drop-database-modal.tsx
@@ -14,7 +14,7 @@ import {
import { dropDatabase } from '../modules/drop-database/drop-database';
import { toggleIsVisible } from '../modules/is-visible';
import type { RootState } from '../modules/drop-database/drop-database';
-import { useTrackOnChange } from '@mongodb-js/compass-logging';
+import { useTrackOnChange } from '@mongodb-js/compass-logging/provider';
const progressContainerStyles = css({
display: 'flex',
@@ -68,8 +68,7 @@ function DropDatabaseModal({
}
},
[isVisible],
- undefined,
- React
+ undefined
);
return (
diff --git a/packages/hadron-app-registry/.mocharc.js b/packages/hadron-app-registry/.mocharc.js
index 7e473d17b76..30aecfb78c3 100644
--- a/packages/hadron-app-registry/.mocharc.js
+++ b/packages/hadron-app-registry/.mocharc.js
@@ -1 +1 @@
-module.exports = require('@mongodb-js/mocha-config-compass');
+module.exports = require('@mongodb-js/mocha-config-compass/react');
diff --git a/packages/hadron-app-registry/package.json b/packages/hadron-app-registry/package.json
index 0da8b54c032..be710b12b93 100644
--- a/packages/hadron-app-registry/package.json
+++ b/packages/hadron-app-registry/package.json
@@ -45,13 +45,17 @@
"dependencies": {
"debug": "^4.2.0",
"eventemitter3": "^4.0.0",
- "reflux": "^0.4.1"
+ "reflux": "^0.4.1",
+ "redux": "^4.2.1",
+ "react": "^17.0.2",
+ "react-redux": "^8.0.5"
},
"devDependencies": {
"@mongodb-js/eslint-config-compass": "^1.0.11",
"@mongodb-js/mocha-config-compass": "^1.3.2",
"@mongodb-js/prettier-config-compass": "^1.0.1",
"@mongodb-js/tsconfig-compass": "^1.0.3",
+ "@testing-library/react": "^12.1.4",
"@types/chai": "^4.2.21",
"@types/mocha": "^9.0.0",
"@types/reflux": "^6.4.3",
@@ -61,6 +65,7 @@
"eslint-config-mongodb-js": "^5.0.3",
"mocha": "^10.2.0",
"prettier": "^2.7.1",
+ "reflux-state-mixin": "github:mongodb-js/reflux-state-mixin",
"sinon": "^9.0.0",
"typescript": "^5.0.4"
}
diff --git a/packages/hadron-app-registry/src/app-registry.ts b/packages/hadron-app-registry/src/app-registry.ts
index 2b9a49190d8..027456e6caf 100644
--- a/packages/hadron-app-registry/src/app-registry.ts
+++ b/packages/hadron-app-registry/src/app-registry.ts
@@ -1,4 +1,5 @@
import type { Store as RefluxStore } from 'reflux';
+import type { Store as ReduxStore } from 'redux';
import EventEmitter from 'eventemitter3';
import { Actions } from './actions';
@@ -17,14 +18,25 @@ interface Role {
storeName?: string;
configureStore?: (storeSetup: any) => any;
order?: number;
- hasQueryHistory?: boolean;
}
-type Store = Partial<
- RefluxStore & {
- onActivated?: (appRegistry: AppRegistry) => void;
- }
->;
+export type Store = (ReduxStore | Partial) & {
+ onActivated?: (appRegistry: AppRegistry) => void;
+};
+
+export function isReduxStore(store: Store): store is ReduxStore {
+ return (
+ store &&
+ typeof store === 'object' &&
+ Object.prototype.hasOwnProperty.call(store, 'dispatch')
+ );
+}
+
+export interface Plugin {
+ store: Store;
+ actions?: Record;
+ deactivate?: () => void;
+}
/**
* Is a registry for all user interface components, stores, and actions
@@ -36,6 +48,7 @@ export class AppRegistry {
components: Record>;
stores: Record;
roles: Record;
+ plugins: Record;
storeMisses: Record;
/**
@@ -47,6 +60,7 @@ export class AppRegistry {
this.components = {};
this.stores = {};
this.roles = {};
+ this.plugins = {};
this.storeMisses = {};
}
@@ -114,6 +128,11 @@ export class AppRegistry {
return this;
}
+ deregisterPlugin(name: string): this {
+ delete this.plugins[name];
+ return this;
+ }
+
/**
* Get an action for the name.
*
@@ -158,6 +177,10 @@ export class AppRegistry {
return this.stores[name];
}
+ getPlugin(name: string): Plugin | undefined {
+ return this.plugins[name];
+ }
+
/**
* Calls onActivated on all the stores in the registry.
*
@@ -253,6 +276,20 @@ export class AppRegistry {
return this;
}
+ registerPlugin(name: string, plugin: Plugin): this {
+ this.plugins[name] = plugin;
+ return this;
+ }
+
+ deactivate() {
+ for (const plugin of Object.values(this.plugins)) {
+ plugin.deactivate?.();
+ }
+ for (const event of this.eventNames()) {
+ this.removeAllListeners(event);
+ }
+ }
+
/**
* Adds a listener for the event name to the underlying event emitter.
*
diff --git a/packages/hadron-app-registry/src/index.ts b/packages/hadron-app-registry/src/index.ts
index 4022f5359cb..497b41d2e2a 100644
--- a/packages/hadron-app-registry/src/index.ts
+++ b/packages/hadron-app-registry/src/index.ts
@@ -2,4 +2,16 @@ import { AppRegistry, globalAppRegistry } from './app-registry';
import type { Role } from './app-registry';
export { AppRegistry, globalAppRegistry };
export type { Role };
+export {
+ AppRegistryProvider,
+ useGlobalAppRegistry,
+ useLocalAppRegistry,
+ useAppRegistryComponent,
+ useAppRegistryRole,
+} from './react-context';
+export {
+ registerHadronPlugin,
+ HadronPluginComponent,
+ HadronPluginConfig,
+} from './register-plugin';
export default AppRegistry;
diff --git a/packages/hadron-app-registry/src/react-context.tsx b/packages/hadron-app-registry/src/react-context.tsx
new file mode 100644
index 00000000000..6eebdcbc776
--- /dev/null
+++ b/packages/hadron-app-registry/src/react-context.tsx
@@ -0,0 +1,142 @@
+import React, {
+ createContext,
+ useEffect,
+ useRef,
+ useContext,
+ useState,
+} from 'react';
+import { globalAppRegistry, AppRegistry } from './app-registry';
+import createDebug from 'debug';
+const debug = createDebug('hadron-app-registry:react');
+
+const GlobalAppRegistryContext = createContext(globalAppRegistry);
+const LocalAppRegistryContext = createContext(null);
+
+type AppRegistryProviderProps =
+ | {
+ localAppRegistry?: never;
+ deactivateOnUnmount?: never;
+ children: React.ReactNode;
+ }
+ | {
+ /**
+ * localAppRegistry to be set in React context. By default will be created
+ * when this component renders. Can be used to preserve appRegistry state even
+ * if AppRegistryProvider is unmounted
+ *
+ * @example
+ * function CollectionTab({ id }) {
+ * return (
+ *
+ * ...
+ *
+ * )
+ * }
+ */
+ localAppRegistry: AppRegistry;
+
+ /**
+ * Deactivates all active plugins and remove all event listeners from the app
+ * registry when provider unmounts. Default is `true`
+ */
+ deactivateOnUnmount?: boolean;
+ children: React.ReactNode;
+ };
+
+export function AppRegistryProvider({
+ children,
+ ...props
+}: AppRegistryProviderProps) {
+ const initialPropsRef = useRef(props);
+ const {
+ localAppRegistry: initialLocalAppRegistry,
+ deactivateOnUnmount = true,
+ } = initialPropsRef.current;
+
+ const globalAppRegistry = useGlobalAppRegistry();
+ const isTopLevelProvider = useContext(LocalAppRegistryContext) === null;
+ const [localAppRegistry] = useState(() => {
+ return (
+ initialLocalAppRegistry ??
+ (isTopLevelProvider ? globalAppRegistry : new AppRegistry())
+ );
+ });
+
+ useEffect(() => {
+ // For cases where localAppRegistry was provided by the parent, we allow
+ // parent to also take control over the cleanup lifecycle by disabling
+ // deactivate call with the `deactivateOnUnmount` prop. Otherwise if
+ // localAppRegistry was created by the provider, it will always clean up on
+ // unmount
+ const shouldDeactivate = initialLocalAppRegistry
+ ? deactivateOnUnmount
+ : true;
+ return () => {
+ if (shouldDeactivate) {
+ localAppRegistry.deactivate();
+ }
+ };
+ }, [localAppRegistry, initialLocalAppRegistry, deactivateOnUnmount]);
+
+ return (
+
+
+ {children}
+
+
+ );
+}
+
+export function useGlobalAppRegistry(): AppRegistry {
+ return useContext(GlobalAppRegistryContext);
+}
+
+export function useLocalAppRegistry(): AppRegistry {
+ const appRegistry = useContext(LocalAppRegistryContext);
+ if (!appRegistry) {
+ throw new Error(`No local AppRegistry registered within this context`);
+ }
+ return appRegistry;
+}
+
+/** @deprecated prefer using plugins or direct references instead */
+export function useAppRegistryComponent(
+ componentName: string
+): React.JSXElementConstructor | null {
+ const appRegistry = useLocalAppRegistry();
+
+ const [component] = useState(() => {
+ const newComponent = appRegistry.getComponent(componentName);
+ if (!newComponent) {
+ debug(
+ `home plugin loading component, but ${String(componentName)} is NULL`
+ );
+ }
+ return newComponent;
+ });
+
+ return component ? component : null;
+}
+
+/** @deprecated prefer using plugins or direct references instead */
+export function useAppRegistryRole(roleName: string):
+ | {
+ component: React.JSXElementConstructor;
+ name: string;
+ }[]
+ | null {
+ const appRegistry = useLocalAppRegistry();
+
+ const [role] = useState(() => {
+ const newRole = appRegistry.getRole(roleName);
+ if (!newRole) {
+ debug(`home plugin loading role, but ${String(roleName)} is NULL`);
+ }
+ return newRole;
+ });
+
+ return role ? role : null;
+}
diff --git a/packages/hadron-app-registry/src/register-plugin.spec.tsx b/packages/hadron-app-registry/src/register-plugin.spec.tsx
new file mode 100644
index 00000000000..efac8a2406f
--- /dev/null
+++ b/packages/hadron-app-registry/src/register-plugin.spec.tsx
@@ -0,0 +1,134 @@
+import React, { createContext, useContext } from 'react';
+import { cleanup, render } from '@testing-library/react';
+import { expect } from 'chai';
+import sinon from 'sinon';
+import { createStore as createRefluxStore } from 'reflux';
+import StateMixin from 'reflux-state-mixin';
+import { AppRegistryProvider, registerHadronPlugin } from './';
+import { createStore } from 'redux';
+import { connect } from 'react-redux';
+
+describe('registerHadronPlugin', 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({
+ name: 'refluxish',
+ component,
+ activate,
+ });
+ expect(Plugin.displayName).to.equal('refluxish');
+ render(
+
+
+
+ );
+ expect(activate).to.have.been.calledOnce;
+ expect(activate.firstCall.args[0]).to.deep.equal({});
+ expect(activate.firstCall.args[1]).to.have.property('localAppRegistry');
+ expect(activate.firstCall.args[1]).to.have.property('globalAppRegistry');
+ expect(component).to.have.been.calledOnceWith({
+ store: { state: { foo: 'bar' } },
+ actions: undefined,
+ foo: 'bar',
+ });
+ });
+
+ it('allows registering plugins with a proper reflux store', function () {
+ const component = sinon.stub().callsFake(() => <>>);
+ const store = createRefluxStore({
+ mixins: [StateMixin.store],
+ getInitialState() {
+ return { foo: 'bar' };
+ },
+ });
+ const activate = sinon.stub().returns({ store });
+ const Plugin = registerHadronPlugin({
+ name: 'reflux',
+ component,
+ activate,
+ });
+ expect(Plugin.displayName).to.equal('reflux');
+ render(
+
+
+
+ );
+ expect(activate).to.have.been.calledOnce;
+ expect(activate.firstCall.args[0]).to.deep.equal({});
+ expect(activate.firstCall.args[1]).to.have.property('localAppRegistry');
+ expect(activate.firstCall.args[1]).to.have.property('globalAppRegistry');
+ expect(component).to.have.been.calledOnceWith({
+ store,
+ actions: undefined,
+ foo: 'bar',
+ });
+ });
+
+ it('allows registering plugins with a redux store', function () {
+ const connector = connect(({ counter }) => ({ counter }));
+ const component = sinon.stub().callsFake(() => <>>);
+ const store = createStore(
+ (state: { counter: number } | undefined, action: { type: 'inc' }) => {
+ state ??= { counter: 0 };
+ if (action.type === 'inc') return { counter: state.counter + 1 };
+ return state;
+ }
+ );
+ const activate = sinon.stub().returns({ store });
+ const Plugin = registerHadronPlugin({
+ name: 'redux',
+ component: connector(component),
+ activate,
+ });
+ expect(Plugin.displayName).to.equal('redux');
+ render(
+
+
+
+ );
+ expect(activate).to.have.been.calledOnce;
+ expect(activate.firstCall.args[0]).to.deep.equal({});
+ expect(activate.firstCall.args[1]).to.have.property('localAppRegistry');
+ expect(activate.firstCall.args[1]).to.have.property('globalAppRegistry');
+ expect(component).to.have.been.calledWith({
+ counter: 0,
+ dispatch: store.dispatch,
+ });
+ store.dispatch({ type: 'inc' });
+ expect(component).to.have.been.calledWith({
+ counter: 1,
+ dispatch: store.dispatch,
+ });
+ });
+
+ it('allows registering a plugin with external services dependencies', function () {
+ const dummy = { value: 'blah' };
+ const blahContext = createContext(dummy);
+ const useBlah = () => useContext(blahContext);
+
+ const connector = connect();
+ const component = sinon.stub().callsFake(() => <>>);
+ const store = createStore(() => ({}));
+ const activate = sinon.stub().returns({ store });
+ const Plugin = registerHadronPlugin(
+ {
+ name: 'service1',
+ component: connector(component),
+ activate,
+ },
+ {
+ blah: useBlah,
+ }
+ );
+ expect(Plugin.displayName).to.equal('service1');
+ render(
+
+
+
+ );
+ expect(activate.firstCall.args[1]).to.have.property('blah', dummy);
+ });
+});
diff --git a/packages/hadron-app-registry/src/register-plugin.tsx b/packages/hadron-app-registry/src/register-plugin.tsx
new file mode 100644
index 00000000000..d7c0c0946c7
--- /dev/null
+++ b/packages/hadron-app-registry/src/register-plugin.tsx
@@ -0,0 +1,200 @@
+import React, { useRef, useState } from 'react';
+import type { Store as RefluxStore } from 'reflux';
+import { Provider as ReduxStoreProvider } from 'react-redux';
+import type { Actions } from './actions';
+import { type Store, type AppRegistry, isReduxStore } from './app-registry';
+import { useGlobalAppRegistry, useLocalAppRegistry } from './react-context';
+
+function LegacyRefluxProvider({
+ store,
+ actions,
+ children,
+}: {
+ store: Partial;
+ actions?: Partial;
+ children: React.ReactElement;
+}) {
+ const storeRef = useRef(store);
+ const [state, setState] = React.useState(() => {
+ return storeRef.current.state;
+ });
+
+ React.useEffect(() => {
+ const unsubscribe = storeRef.current.listen?.(setState, null);
+ return () => unsubscribe?.();
+ }, []);
+
+ return React.cloneElement(
+ // There is a ton of issues with cloning children like that,
+ // this is a legacy piece of code that we know works in our cases and so we
+ // can ignore ts errors instead of handling all corner-cases
+ children,
+ // There is no single pattern to how reflux is used by plugins, sometime
+ // store is passed directly, sometimes only state, sometimes actions are
+ // passed as a single prop, sometimes spreaded, sometimes all the approaches
+ // are mixed and used like that in the plugins. Reflux is legacy that we
+ // prefer to not spend time cleaning up so we're just trying to cover all
+ // the cases here as much as possible
+ { store, actions, ...actions, ...state }
+ );
+}
+
+type Registries = {
+ globalAppRegistry: AppRegistry;
+ localAppRegistry: AppRegistry;
+};
+
+type Services unknown>> = {
+ [SvcName in keyof S]: ReturnType;
+};
+
+export type HadronPluginConfig unknown>> = {
+ name: string;
+ component: React.ComponentType;
+ /**
+ * Plugin activation method, will receive any props passed to the component,
+ * and global and local app registry instances to subscribe to any relevant
+ * events. Should return plugin store and an optional deactivate method to
+ * clean up subscriptions or any other store-related state
+ */
+ activate: (
+ options: T,
+ services: Registries & Services
+ ) => {
+ /**
+ * Redux or reflux store that will be automatically passed to a
+ * corresponding provider
+ */
+ store: Store;
+ /**
+ * Optional, only relevant for plugins still using reflux
+ */
+ actions?: typeof Actions;
+ /**
+ * Will be called to clean up plugin subscriptions when it is deactivated by
+ * app registry scope
+ */
+ deactivate: () => void;
+ };
+};
+
+export type HadronPluginComponent = React.FunctionComponent & {
+ displayName: string;
+};
+
+/**
+ * Creates a hadron plugin that will be automatically activated on first render
+ * and cleaned up when localAppRegistry unmounts
+ *
+ * @param config Hadron plugin configuration
+ * @param services Map of service locator functions that plugin depends on
+ *
+ * @returns Hadron plugin component
+ *
+ * @example
+ * const CreateCollectionPlugin = registerHadronPlugin({
+ * name: 'CreateCollection',
+ * component: CreateCollectionModal,
+ * activate(opts, { globalAppRegistry }) {
+ * const store = configureStore(...);
+ * const openCreateCollectionModal = (ns) => {
+ * store.dispatch(openModal(ns));
+ * }
+ * globalAppRegistry.on('create-collection', openCreateCollectionModal);
+ * return {
+ * store,
+ * deactivate() {
+ * globalAppRegistry.removeEventListener(
+ * 'create-collection',
+ * openCreateCollectionModal
+ * );
+ * }
+ * }
+ * }
+ * });
+ *
+ * @example
+ * // app.js
+ * import CompassLogging from '@mongodb-js/compass-logging';
+ * import { LoggingProvider } from '@mongodb-js/compass-logging/provider';
+ *
+ * ReactDOM.render(
+ *
+ *
+ *
+ * )
+ *
+ * // plugin.js
+ * import { logging } from '@mongodb-js/compass-logging/provider'
+ *
+ * const PluginWithLogger = registerHadronPlugin({
+ * name: 'LoggingPlugin',
+ * component: () => null,
+ * activate(opts, { logging }) {
+ * loggging.log('Plugin activated!');
+ * }
+ * }, { logging })
+ */
+export function registerHadronPlugin<
+ T,
+ S extends Record unknown>
+>(config: HadronPluginConfig, services?: S): HadronPluginComponent {
+ const Component = config.component;
+ const registryName = `${config.name}.Plugin`;
+
+ return Object.assign(
+ (props: React.PropsWithChildren) => {
+ const propsRef = useRef(props);
+ const globalAppRegistry = useGlobalAppRegistry();
+ const localAppRegistry = useLocalAppRegistry();
+
+ const serviceImpls = Object.fromEntries(
+ Object.entries(services ?? {}).map(([key, service]) => {
+ try {
+ return [key, service()];
+ } catch (err) {
+ if (
+ err &&
+ typeof err === 'object' &&
+ 'message' in err &&
+ typeof err.message === 'string'
+ )
+ err.message += ` [locating service '${key}' for '${registryName}']`;
+ throw err;
+ }
+ })
+ ) as Services;
+
+ const [{ store, actions }] = useState(
+ () =>
+ localAppRegistry.getPlugin(registryName) ??
+ (() => {
+ const plugin = config.activate(propsRef.current, {
+ globalAppRegistry,
+ localAppRegistry,
+ ...serviceImpls,
+ });
+ localAppRegistry.registerPlugin(registryName, plugin);
+ return plugin;
+ })()
+ );
+
+ if (isReduxStore(store)) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+
+
+ );
+ },
+ {
+ displayName: config.name,
+ }
+ );
+}
diff --git a/packages/hadron-app-registry/tsconfig.json b/packages/hadron-app-registry/tsconfig.json
index ecd0a14474a..79bc84584ce 100644
--- a/packages/hadron-app-registry/tsconfig.json
+++ b/packages/hadron-app-registry/tsconfig.json
@@ -1,5 +1,5 @@
{
- "extends": "@mongodb-js/tsconfig-compass/tsconfig.common.json",
+ "extends": "@mongodb-js/tsconfig-compass/tsconfig.react.json",
"compilerOptions": {
"outDir": "dist"
},