From 716378bad80f866b360d13b44ea9c1aacda95aaf Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Wed, 8 Nov 2023 13:10:16 +0100 Subject: [PATCH] fix(compass-sidebar): convert to ts, fix save connections bug COMPASS-7418 (#5077) --- package-lock.json | 62 ----- .../src/databases-navigation-tree.tsx | 2 +- packages/compass-sidebar/.mocharc.js | 7 +- packages/compass-sidebar/package.json | 9 +- .../src/components/connection-info-modal.tsx | 54 ++-- .../src/components/db-stats.tsx | 24 +- .../src/components/navigation-items.tsx | 57 ++--- .../sidebar-databases-navigation.tsx | 8 +- .../src/components/sidebar.tsx | 13 +- .../src/{index.js => index.ts} | 5 +- .../compass-sidebar/src/modules/collection.js | 48 ---- .../src/modules/collection.spec.js | 82 ------- ...n-info.spec.js => connection-info.spec.ts} | 28 ++- ...{connection-info.js => connection-info.ts} | 77 ++++-- ...ons.spec.js => connection-options.spec.ts} | 2 +- .../src/modules/connection-options.ts | 19 +- .../compass-sidebar/src/modules/databases.js | 174 ------------- .../{databases.spec.js => databases.spec.ts} | 13 +- .../compass-sidebar/src/modules/databases.ts | 230 ++++++++++++++++++ packages/compass-sidebar/src/modules/index.js | 64 ----- packages/compass-sidebar/src/modules/index.ts | 116 +++++++++ .../compass-sidebar/src/modules/instance.js | 36 --- .../{instance.spec.js => instance.spec.ts} | 6 +- .../compass-sidebar/src/modules/instance.ts | 61 +++++ ...ed.spec.js => is-details-expanded.spec.ts} | 2 +- ...ils-expanded.js => is-details-expanded.ts} | 20 +- .../src/modules/is-expanded.ts | 26 +- .../src/modules/is-genuine-mongodb-visible.js | 41 ---- ....js => is-genuine-mongodb-visible.spec.ts} | 2 +- .../src/modules/is-genuine-mongodb-visible.ts | 55 +++++ .../compass-sidebar/src/modules/location.ts | 18 +- packages/compass-sidebar/src/modules/reset.js | 13 - .../modules/{reset.spec.js => reset.spec.ts} | 0 packages/compass-sidebar/src/modules/reset.ts | 14 ++ .../src/{plugin.jsx => plugin.tsx} | 0 .../src/stores/{index.js => index.ts} | 0 .../stores/{store.spec.js => store.spec.ts} | 8 +- .../src/stores/{store.js => store.ts} | 45 ++-- .../test/{helpers.js => helpers.ts} | 3 +- .../test/mocks/collection-store.js | 63 ----- .../test/mocks/deployment-state-store.js | 42 ---- .../test/mocks/namespace-store.js | 54 ---- packages/compass-sidebar/test/setup.js | 15 -- .../src/connection-title.spec.ts | 7 - .../src/connection-title.ts | 4 +- 45 files changed, 720 insertions(+), 909 deletions(-) rename packages/compass-sidebar/src/{index.js => index.ts} (83%) delete mode 100644 packages/compass-sidebar/src/modules/collection.js delete mode 100644 packages/compass-sidebar/src/modules/collection.spec.js rename packages/compass-sidebar/src/modules/{connection-info.spec.js => connection-info.spec.ts} (80%) rename packages/compass-sidebar/src/modules/{connection-info.js => connection-info.ts} (50%) rename packages/compass-sidebar/src/modules/{connection-options.spec.js => connection-options.spec.ts} (95%) delete mode 100644 packages/compass-sidebar/src/modules/databases.js rename packages/compass-sidebar/src/modules/{databases.spec.js => databases.spec.ts} (91%) create mode 100644 packages/compass-sidebar/src/modules/databases.ts delete mode 100644 packages/compass-sidebar/src/modules/index.js create mode 100644 packages/compass-sidebar/src/modules/index.ts delete mode 100644 packages/compass-sidebar/src/modules/instance.js rename packages/compass-sidebar/src/modules/{instance.spec.js => instance.spec.ts} (82%) create mode 100644 packages/compass-sidebar/src/modules/instance.ts rename packages/compass-sidebar/src/modules/{is-details-expanded.spec.js => is-details-expanded.spec.ts} (92%) rename packages/compass-sidebar/src/modules/{is-details-expanded.js => is-details-expanded.ts} (54%) delete mode 100644 packages/compass-sidebar/src/modules/is-genuine-mongodb-visible.js rename packages/compass-sidebar/src/modules/{is-genuine-mongodb-visible.spec.js => is-genuine-mongodb-visible.spec.ts} (92%) create mode 100644 packages/compass-sidebar/src/modules/is-genuine-mongodb-visible.ts delete mode 100644 packages/compass-sidebar/src/modules/reset.js rename packages/compass-sidebar/src/modules/{reset.spec.js => reset.spec.ts} (100%) create mode 100644 packages/compass-sidebar/src/modules/reset.ts rename packages/compass-sidebar/src/{plugin.jsx => plugin.tsx} (100%) rename packages/compass-sidebar/src/stores/{index.js => index.ts} (100%) rename packages/compass-sidebar/src/stores/{store.spec.js => store.spec.ts} (93%) rename packages/compass-sidebar/src/stores/{store.js => store.ts} (81%) rename packages/compass-sidebar/test/{helpers.js => helpers.ts} (88%) delete mode 100644 packages/compass-sidebar/test/mocks/collection-store.js delete mode 100644 packages/compass-sidebar/test/mocks/deployment-state-store.js delete mode 100644 packages/compass-sidebar/test/mocks/namespace-store.js delete mode 100644 packages/compass-sidebar/test/setup.js diff --git a/package-lock.json b/package-lock.json index 39c877dd9a5..2e3fdbedd7a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32269,31 +32269,6 @@ "lodash": "^4.17.15" } }, - "node_modules/mongodb-reflux-store": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/mongodb-reflux-store/-/mongodb-reflux-store-0.0.1.tgz", - "integrity": "sha1-0r9x+WNAIYkplYl9w2cajMRIRjI=", - "dev": true, - "dependencies": { - "debug": "^2.2.0", - "reflux": "^0.4.0" - } - }, - "node_modules/mongodb-reflux-store/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/mongodb-reflux-store/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, "node_modules/mongodb-runner": { "version": "5.4.4", "resolved": "https://registry.npmjs.org/mongodb-runner/-/mongodb-runner-5.4.4.tgz", @@ -47199,22 +47174,17 @@ "debug": "^4.2.0", "depcheck": "^1.4.1", "eslint": "^7.25.0", - "hadron-app": "^5.15.0", "hadron-app-registry": "^9.0.13", "lodash": "^4.17.21", "mocha": "^10.2.0", "mongodb-ns": "^2.4.0", - "mongodb-reflux-store": "^0.0.1", "nyc": "^15.1.0", "prettier": "^2.7.1", - "prop-types": "^15.7.2", "react": "^17.0.2", "react-dom": "^17.0.2", "react-redux": "^8.0.5", "redux": "^4.2.1", "redux-thunk": "^2.4.1", - "reflux": "^0.4.1", - "reflux-state-mixin": "github:mongodb-js/reflux-state-mixin", "sinon": "^9.2.3", "xvfb-maybe": "^0.2.1" }, @@ -60248,23 +60218,18 @@ "debug": "^4.2.0", "depcheck": "^1.4.1", "eslint": "^7.25.0", - "hadron-app": "^5.15.0", "hadron-app-registry": "^9.0.13", "lodash": "^4.17.21", "mocha": "^10.2.0", "mongodb-instance-model": "^12.15.0", "mongodb-ns": "^2.4.0", - "mongodb-reflux-store": "^0.0.1", "nyc": "^15.1.0", "prettier": "^2.7.1", - "prop-types": "^15.7.2", "react": "^17.0.2", "react-dom": "^17.0.2", "react-redux": "^8.0.5", "redux": "^4.2.1", "redux-thunk": "^2.4.1", - "reflux": "^0.4.1", - "reflux-state-mixin": "github:mongodb-js/reflux-state-mixin", "sinon": "^9.2.3", "xvfb-maybe": "^0.2.1" }, @@ -85347,33 +85312,6 @@ "lodash": "^4.17.15" } }, - "mongodb-reflux-store": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/mongodb-reflux-store/-/mongodb-reflux-store-0.0.1.tgz", - "integrity": "sha1-0r9x+WNAIYkplYl9w2cajMRIRjI=", - "dev": true, - "requires": { - "debug": "^2.2.0", - "reflux": "^0.4.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, "mongodb-runner": { "version": "5.4.4", "resolved": "https://registry.npmjs.org/mongodb-runner/-/mongodb-runner-5.4.4.tgz", diff --git a/packages/compass-databases-navigation/src/databases-navigation-tree.tsx b/packages/compass-databases-navigation/src/databases-navigation-tree.tsx index 5716239f22c..70844a0cd2c 100644 --- a/packages/compass-databases-navigation/src/databases-navigation-tree.tsx +++ b/packages/compass-databases-navigation/src/databases-navigation-tree.tsx @@ -66,7 +66,7 @@ type TreeItem = PlaceholderTreeItem | DatabaseTreeItem | CollectionTreeItem; type ListItemData = { items: TreeItem[]; isReadOnly: boolean; - activeNamespace: string; + activeNamespace?: string; currentTabbable?: string; onDatabaseExpand(this: void, id: string, isExpanded: boolean): void; onNamespaceAction(this: void, namespace: string, action: Actions): void; diff --git a/packages/compass-sidebar/.mocharc.js b/packages/compass-sidebar/.mocharc.js index accc49db7cd..a7e53abc444 100644 --- a/packages/compass-sidebar/.mocharc.js +++ b/packages/compass-sidebar/.mocharc.js @@ -1,6 +1 @@ -const base = require('@mongodb-js/mocha-config-compass/compass-plugin'); - -module.exports = { - ...base, - require: base.require.concat(['test/setup.js']), -}; +module.exports = require('@mongodb-js/mocha-config-compass/compass-plugin'); diff --git a/packages/compass-sidebar/package.json b/packages/compass-sidebar/package.json index a95edcaa181..b39dc9bd258 100644 --- a/packages/compass-sidebar/package.json +++ b/packages/compass-sidebar/package.json @@ -24,14 +24,14 @@ ], "license": "SSPL", "main": "dist/index.js", - "compass:main": "src/index.js", + "compass:main": "src/index.ts", "types": "dist/src/index.d.ts", "exports": { "browser": "./dist/browser.js", "require": "./dist/index.js" }, "compass:exports": { - ".": "./src/index.js" + ".": "./src/index.ts" }, "scripts": { "bootstrap": "npm run postcompile", @@ -97,22 +97,17 @@ "debug": "^4.2.0", "depcheck": "^1.4.1", "eslint": "^7.25.0", - "hadron-app": "^5.15.0", "hadron-app-registry": "^9.0.13", "lodash": "^4.17.21", "mocha": "^10.2.0", "mongodb-ns": "^2.4.0", - "mongodb-reflux-store": "^0.0.1", "nyc": "^15.1.0", "prettier": "^2.7.1", - "prop-types": "^15.7.2", "react": "^17.0.2", "react-dom": "^17.0.2", "react-redux": "^8.0.5", "redux": "^4.2.1", "redux-thunk": "^2.4.1", - "reflux": "^0.4.1", - "reflux-state-mixin": "github:mongodb-js/reflux-state-mixin", "sinon": "^9.2.3", "xvfb-maybe": "^0.2.1" } diff --git a/packages/compass-sidebar/src/components/connection-info-modal.tsx b/packages/compass-sidebar/src/components/connection-info-modal.tsx index 1a81e8bc5e4..53cb62bb652 100644 --- a/packages/compass-sidebar/src/components/connection-info-modal.tsx +++ b/packages/compass-sidebar/src/components/connection-info-modal.tsx @@ -2,22 +2,9 @@ import React from 'react'; import { connect } from 'react-redux'; import { InfoModal, Body, css, spacing } from '@mongodb-js/compass-components'; import { ServerType, TopologyType } from 'mongodb-instance-model'; -import type { MongoDBInstance } from 'mongodb-instance-model'; -import type { ConnectionOptions } from '../modules/connection-options'; - -type Collection = { - id: string; - name: string; - type: string; -}; - -type Database = { - _id: string; - name: string; - collectionsStatus: string; - collectionsLength: number; - collections: Collection[]; -}; +import type { ConnectionInfo as ConnectionStorageConnectionInfo } from '@mongodb-js/connection-storage/renderer'; +import type { RootState } from '../modules'; +import type { Database } from '../modules/databases'; type ConnectionInfo = { term: string; @@ -107,15 +94,13 @@ function getVersionDistro({ return isEnterprise ? 'Enterprise' : 'Community'; } -type InfoParameters = { - instance: MongoDBInstance; +type InfoParameters = Pick & { databases: Database[]; - connectionInfo: ConnectionInfo; - connectionOptions: ConnectionOptions; + connectionInfo: Partial; }; function getStatsInfo({ instance, databases }: InfoParameters): ConnectionInfo { - const isReady = instance.refreshingStatus === 'ready'; + const isReady = instance?.refreshingStatus === 'ready'; const numDbs = isReady ? databases.length : '-'; const numCollections = isReady @@ -135,7 +120,7 @@ function getStatsInfo({ instance, databases }: InfoParameters): ConnectionInfo { } function getHostInfo({ instance }: InfoParameters): ConnectionInfo { - const { type, servers } = instance.topologyDescription; + const { type, servers = [] } = instance?.topologyDescription ?? {}; let heading = servers.length === 1 ? 'Host' : 'Hosts'; if (type === TopologyType.LOAD_BALANCED) { @@ -168,7 +153,7 @@ function makeNodesInfo( } function getClusterInfo({ instance }: InfoParameters): ConnectionInfo { - const { type, setName, servers } = instance.topologyDescription; + const { type, setName, servers = [] } = instance?.topologyDescription ?? {}; let clusterType: string; let nodesInfo; @@ -205,12 +190,12 @@ function getClusterInfo({ instance }: InfoParameters): ConnectionInfo { function getVersionInfo({ instance }: InfoParameters): ConnectionInfo { return { term: 'Edition', - description: instance.dataLake.isDataLake - ? `Atlas Data Federation ${instance.dataLake.version ?? ''}` - : `MongoDB ${instance.build.version} ${getVersionDistro({ - isEnterprise: instance.build.isEnterprise, - isLocalAtlas: instance.isLocalAtlas, - isAtlas: instance.isAtlas, + description: instance?.dataLake.isDataLake + ? `Atlas Data Federation ${instance?.dataLake.version ?? ''}` + : `MongoDB ${instance?.build.version} ${getVersionDistro({ + isEnterprise: instance?.build.isEnterprise, + isLocalAtlas: instance?.isLocalAtlas, + isAtlas: instance?.isAtlas, })}`, }; } @@ -254,16 +239,7 @@ function getInfos(infoParameters: InfoParameters) { return infos; } -const mapStateToProps = (state: { - instance: MongoDBInstance; - databases: { - databases: Database[]; - }; - connectionInfo: { - connectionInfo: ConnectionInfo; - }; - connectionOptions: ConnectionOptions; -}) => { +const mapStateToProps = (state: RootState) => { const { instance, databases, connectionOptions } = state; const { connectionInfo } = state.connectionInfo; diff --git a/packages/compass-sidebar/src/components/db-stats.tsx b/packages/compass-sidebar/src/components/db-stats.tsx index df035d4b6c7..ce639edb6a9 100644 --- a/packages/compass-sidebar/src/components/db-stats.tsx +++ b/packages/compass-sidebar/src/components/db-stats.tsx @@ -10,7 +10,8 @@ import { Subtitle, Overline, } from '@mongodb-js/compass-components'; -import type { MongoDBInstance } from 'mongodb-instance-model'; +import type { RootState } from '../modules'; +import type { Database } from '../modules/databases'; type RefreshingStatus = | 'initial' @@ -19,20 +20,6 @@ type RefreshingStatus = | 'ready' | 'error'; -type Collection = { - id: string; - name: string; - type: string; -}; - -type Database = { - _id: string; - name: string; - collectionsStatus: string; - collectionsLength: number; - collections: Collection[]; -}; - const dbStats = css({ display: 'flex', alignItems: 'center', @@ -93,12 +80,7 @@ export function DBStats({ ); } -const mapStateToProps = (state: { - instance?: MongoDBInstance; - databases: { - databases: Database[]; - }; -}) => ({ +const mapStateToProps = (state: RootState) => ({ refreshingStatus: state.instance ? state.instance.refreshingStatus : 'initial', diff --git a/packages/compass-sidebar/src/components/navigation-items.tsx b/packages/compass-sidebar/src/components/navigation-items.tsx index 31a38ce6364..6e112564cc8 100644 --- a/packages/compass-sidebar/src/components/navigation-items.tsx +++ b/packages/compass-sidebar/src/components/navigation-items.tsx @@ -22,6 +22,7 @@ import DatabaseCollectionFilter from './database-collection-filter'; import SidebarDatabasesNavigation from './sidebar-databases-navigation'; import { changeFilterRegex } from '../modules/databases'; +import type { RootState } from '../modules'; type DatabasesActions = 'open-create-database' | 'refresh-databases'; @@ -272,45 +273,31 @@ export function NavigationItems({ ); } -const mapStateToProps = - (state: // TODO(COMPASS-6914): Properly type stores instead of this - { - location: string | null; - databases: { - databases: any[]; - }; - instance?: { - dataLake: { - isDataLake: boolean; - }; - isWritable: boolean; - databasesStatus: string; - }; - }) => { - const totalCollectionsCount = state.databases.databases.reduce( - (acc: number, db: { collectionsLength: number }) => { - return acc + db.collectionsLength; - }, - 0 - ); +const mapStateToProps = (state: RootState) => { + const totalCollectionsCount = state.databases.databases.reduce( + (acc: number, db: { collectionsLength: number }) => { + return acc + db.collectionsLength; + }, + 0 + ); - const databasesStatus = state.instance?.databasesStatus; - const isReady = - databasesStatus !== undefined && - !['initial', 'fetching'].includes(databasesStatus); + const databasesStatus = state.instance?.databasesStatus; + const isReady = + databasesStatus !== undefined && + !['initial', 'fetching'].includes(databasesStatus); - const numNonSpecialDatabases = state.databases.databases - .map((x: { _id: string }) => toNS(x._id)) - .filter((x) => !x.specialish).length; + const numNonSpecialDatabases = state.databases.databases + .map((x: { _id: string }) => toNS(x._id)) + .filter((x) => !x.specialish).length; - return { - currentLocation: state.location, - isDataLake: state.instance?.dataLake.isDataLake, - isWritable: state.instance?.isWritable, - showTooManyCollectionsInsight: totalCollectionsCount > 10_000, - showCreateDatabaseGuideCue: isReady && numNonSpecialDatabases === 0, - }; + return { + currentLocation: state.location, + isDataLake: state.instance?.dataLake.isDataLake, + isWritable: state.instance?.isWritable, + showTooManyCollectionsInsight: totalCollectionsCount > 10_000, + showCreateDatabaseGuideCue: isReady && numNonSpecialDatabases === 0, }; +}; const MappedNavigationItems = connect(mapStateToProps, { changeFilterRegex, diff --git a/packages/compass-sidebar/src/components/sidebar-databases-navigation.tsx b/packages/compass-sidebar/src/components/sidebar-databases-navigation.tsx index a359fb47a5c..f8c029e1d8f 100644 --- a/packages/compass-sidebar/src/components/sidebar-databases-navigation.tsx +++ b/packages/compass-sidebar/src/components/sidebar-databases-navigation.tsx @@ -7,6 +7,7 @@ import { globalAppRegistryEmit } from '@mongodb-js/mongodb-redux-common/app-regi import toNS from 'mongodb-ns'; import { toggleDatabaseExpanded } from '../modules/databases'; import { withPreferences } from 'compass-preferences-model'; +import type { RootState } from '../modules'; function SidebarDatabasesNavigation( dbNavigationProps: React.ComponentProps & { @@ -24,8 +25,7 @@ function SidebarDatabasesNavigation( ); } -function mapStateToProps(state: any) { - // TODO: type state +function mapStateToProps(state: RootState) { const { databases: { filterRegex, @@ -37,7 +37,7 @@ function mapStateToProps(state: any) { } = state; const status = instance?.databasesStatus; const isReady = - status !== undefined && !['initial', 'fetching'].includes(status as string); + status !== undefined && !['initial', 'fetching'].includes(status); const defaultExpanded = Boolean(filterRegex); const expanded = Object.fromEntries( (filteredDatabases as any[]).map(({ name }) => [ @@ -51,7 +51,7 @@ function mapStateToProps(state: any) { isReady, isDataLake, isWritable, - activeNamespace, + activeNamespace: toNS(activeNamespace).ns, databases: filteredDatabases, expanded, }; diff --git a/packages/compass-sidebar/src/components/sidebar.tsx b/packages/compass-sidebar/src/components/sidebar.tsx index a178c7246b4..3fba4353f70 100644 --- a/packages/compass-sidebar/src/components/sidebar.tsx +++ b/packages/compass-sidebar/src/components/sidebar.tsx @@ -3,7 +3,6 @@ import { cloneDeep } from 'lodash'; import { connect } from 'react-redux'; import { getConnectionTitle } from '@mongodb-js/connection-storage/renderer'; import type { ConnectionInfo } from '@mongodb-js/connection-storage/renderer'; -import type { MongoDBInstance } from 'mongodb-instance-model'; import { css, spacing, @@ -26,6 +25,7 @@ import { updateAndSaveConnectionInfo } from '../modules/connection-info'; import { toggleIsGenuineMongoDBVisible } from '../modules/is-genuine-mongodb-visible'; import { setIsExpanded } from '../modules/is-expanded'; import { maybeProtectConnectionString } from '@mongodb-js/compass-maybe-protect-connection-string'; +import type { RootState } from '../modules'; const TOAST_TIMEOUT_MS = 5000; // 5 seconds. @@ -47,7 +47,7 @@ export function Sidebar({ csfleMode, }: { isExpanded: boolean; - connectionInfo: ConnectionInfo; + connectionInfo: Omit & Partial; globalAppRegistryEmit: any; updateAndSaveConnectionInfo: any; isGenuineMongoDBVisible: boolean; @@ -206,14 +206,7 @@ export function Sidebar({ ); } -const mapStateToProps = (state: { - isExpanded: boolean; - connectionInfo: { - connectionInfo: ConnectionInfo; - }; - isGenuineMongoDBVisible: boolean; - instance?: MongoDBInstance; -}) => ({ +const mapStateToProps = (state: RootState) => ({ isExpanded: state.isExpanded, connectionInfo: state.connectionInfo.connectionInfo, isGenuineMongoDBVisible: state.isGenuineMongoDBVisible, diff --git a/packages/compass-sidebar/src/index.js b/packages/compass-sidebar/src/index.ts similarity index 83% rename from packages/compass-sidebar/src/index.js rename to packages/compass-sidebar/src/index.ts index 872f43f4e30..e7f17c44d59 100644 --- a/packages/compass-sidebar/src/index.js +++ b/packages/compass-sidebar/src/index.ts @@ -1,3 +1,4 @@ +import type { AppRegistry } from 'hadron-app-registry'; import SidebarPlugin from './plugin'; import SidebarStore from './stores'; @@ -5,7 +6,7 @@ import SidebarStore from './stores'; * Activate all the components in the Sidebar package. * @param {Object} appRegistry - The Hadron appRegisrty to activate this plugin with. **/ -function activate(appRegistry) { +function activate(appRegistry: AppRegistry) { appRegistry.registerComponent('Sidebar.Component', SidebarPlugin); appRegistry.registerStore('Sidebar.Store', SidebarStore); } @@ -14,7 +15,7 @@ function activate(appRegistry) { * Deactivate all the components in the Sidebar package. * @param {Object} appRegistry - The Hadron appRegisrty to deactivate this plugin with. **/ -function deactivate(appRegistry) { +function deactivate(appRegistry: AppRegistry) { appRegistry.deregisterComponent('Sidebar.Component'); appRegistry.deregisterStore('Sidebar.Store'); } diff --git a/packages/compass-sidebar/src/modules/collection.js b/packages/compass-sidebar/src/modules/collection.js deleted file mode 100644 index 7715c12eced..00000000000 --- a/packages/compass-sidebar/src/modules/collection.js +++ /dev/null @@ -1,48 +0,0 @@ -import toNS from 'mongodb-ns'; - -/** - * Get the source object. - * - * @param {String} name - The source collection name. - * @param {Array} collections - The collections to search. - * - * @returns {Object} The source object. - */ -export const getSource = (name, collections) => { - return collections.find((coll) => { - return toNS(coll._id).collection === name; - }); -}; - -/** - * Generate a source namespace. - * - * @param {Boolean} isReadonly - If the collection is readonly, i.e. could be a view. - * @param {String} database - The database name. - * @param {String} name - The source collection name. - * - * @returns {String} The full source namespace. - */ -export const getSourceName = (isReadonly, database, name) => { - if (isReadonly && name) { - return `${database}.${name}`; - } - return null; -}; - -/** - * Get the source view name. - * - * @param {String} database - The database. - * @param {Object} source - The source object. - * - * @returns {String} The source view on name. - */ -export const getSourceViewOn = (database, source) => { - if (source && source.view_on) { - return `${database}.${source.view_on}`; - } - return null; -}; - -export const TIME_SERIES_COLLECTION_TYPE = 'timeseries'; diff --git a/packages/compass-sidebar/src/modules/collection.spec.js b/packages/compass-sidebar/src/modules/collection.spec.js deleted file mode 100644 index 9eff48083e9..00000000000 --- a/packages/compass-sidebar/src/modules/collection.spec.js +++ /dev/null @@ -1,82 +0,0 @@ -import { expect } from 'chai'; - -import { getSource, getSourceName, getSourceViewOn } from './collection'; - -const COLL = { - _id: 'db.test', - readonly: false, -}; - -const VIEW = { - _id: 'db.testView', - readonly: true, - view_on: 'test', - pipeline: [], -}; - -const VIEW_ON_VIEW = { - _id: 'db.testViewOnView', - readonly: true, - view_on: 'testView', - pipeline: [], -}; - -const TIME_SERIES = { - _id: 'db.testTimeSeries', - type: 'timeSeries', - readonly: false, -}; - -const COLLECTIONS = [COLL, VIEW, VIEW_ON_VIEW, TIME_SERIES]; - -describe('collection module', function () { - describe('#getSource', function () { - context('when the name matches', function () { - it('returns the source', function () { - expect(getSource('testView', COLLECTIONS)._id).to.equal('db.testView'); - }); - }); - - context('when the name does not match', function () { - it('returns undefined', function () { - expect(getSource('notFound', COLLECTIONS)).to.equal(undefined); - }); - }); - }); - - describe('#getSourceName', function () { - context('when the collection is a view', function () { - it('returns the source name', function () { - expect(getSourceName(VIEW.readonly, 'db', 'testView')).to.equal( - 'db.testView' - ); - }); - }); - - context('when the collection is not a view', function () { - it('returns null', function () { - expect(getSourceName(COLL.readonly)).to.equal(null); - }); - }); - - context('when the collection is readonly but not a view', function () { - it('returns null', function () { - expect(getSourceName(true, 'db', undefined)).to.equal(null); - }); - }); - }); - - describe('#getSourceViewOn', function () { - context('when the source is a view', function () { - it('returns the view namespace', function () { - expect(getSourceViewOn('db', VIEW)).to.equal('db.test'); - }); - }); - - context('when the source is not a view', function () { - it('returns null', function () { - expect(getSourceViewOn('db')).to.equal(null); - }); - }); - }); -}); diff --git a/packages/compass-sidebar/src/modules/connection-info.spec.js b/packages/compass-sidebar/src/modules/connection-info.spec.ts similarity index 80% rename from packages/compass-sidebar/src/modules/connection-info.spec.js rename to packages/compass-sidebar/src/modules/connection-info.spec.ts index b32d7c4051e..7cec7a65946 100644 --- a/packages/compass-sidebar/src/modules/connection-info.spec.js +++ b/packages/compass-sidebar/src/modules/connection-info.spec.ts @@ -31,7 +31,7 @@ describe('connection info module', function () { connectionStorage: { save: saveSpy, }, - }, + } as any, changeConnectionInfo(connectionInfoNotFavorite) ); @@ -53,12 +53,12 @@ describe('connection info module', function () { connectionStorage: { save: function () {}, }, - }, + } as any, newConnection ); - expect(state.connectionInfo.favorite.name).to.equal('My Favorite'); - expect(state.connectionInfo.favorite.color).to.equal('#d4366e'); + expect(state.connectionInfo.favorite?.name).to.equal('My Favorite'); + expect(state.connectionInfo.favorite?.color).to.equal('#d4366e'); }); it('calls to save the connection info in the connection storage', function () { @@ -75,19 +75,21 @@ describe('connection info module', function () { connectionStorage: { save: saveSpy, }, - }, + } as any, newConnection ); expect(saveSpy.callCount).to.equal(1); expect(saveSpy.firstCall.args[0]).to.deep.equal({ - connectionOptions: { - connectionString: 'mongodb://outerspace:27000', - }, - id: '123', - favorite: { - name: 'My Favorite', - color: '#d4366e', + connectionInfo: { + connectionOptions: { + connectionString: 'mongodb://outerspace:27000', + }, + id: '123', + favorite: { + name: 'My Favorite', + color: '#d4366e', + }, }, }); }); @@ -95,7 +97,7 @@ describe('connection info module', function () { context('when an action is not provided', function () { it('returns the default state', function () { - expect(reducer(undefined, {})).to.equal(INITIAL_STATE); + expect(reducer(undefined, {} as any)).to.equal(INITIAL_STATE); }); }); }); diff --git a/packages/compass-sidebar/src/modules/connection-info.js b/packages/compass-sidebar/src/modules/connection-info.ts similarity index 50% rename from packages/compass-sidebar/src/modules/connection-info.js rename to packages/compass-sidebar/src/modules/connection-info.ts index e81c1195a1e..7f48c204ca7 100644 --- a/packages/compass-sidebar/src/modules/connection-info.js +++ b/packages/compass-sidebar/src/modules/connection-info.ts @@ -1,22 +1,37 @@ -import { ConnectionStorage } from '@mongodb-js/connection-storage/renderer'; +import { + ConnectionStorage, + type ConnectionInfo, +} from '@mongodb-js/connection-storage/renderer'; import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging'; +import type { RootAction } from '.'; const { debug } = createLoggerAndTelemetry('COMPASS-SIDEBAR'); /** * Change connection action name. */ -const CHANGE_CONNECTION_INFO = 'sidebar/connection/CHANGE_CONNECTION_INFO'; +const CHANGE_CONNECTION_INFO = + 'sidebar/connection/CHANGE_CONNECTION_INFO' as const; + +interface ChangeConnectionInfoAction { + type: typeof CHANGE_CONNECTION_INFO; + connectionInfo: ConnectionInfo; +} /** * Save favorite connection action name. */ -const SAVE_CONNECTION_INFO = 'sidebar/connection/SAVE_CONNECTION_INFO'; +const SAVE_CONNECTION_INFO = 'sidebar/connection/SAVE_CONNECTION_INFO' as const; + +interface SaveConnectionInfoAction { + type: typeof SAVE_CONNECTION_INFO; + connectionInfo: ConnectionInfo; +} /** * The initial state of the connection. */ -export const INITIAL_STATE = { +export const INITIAL_STATE: ConnectionInfoState = { connectionInfo: { connectionOptions: { connectionString: 'mongodb://localhost:27017', @@ -25,11 +40,23 @@ export const INITIAL_STATE = { connectionStorage: ConnectionStorage, }; -async function saveConnectionInfo(connectionInfo, connectionStorage) { +export interface ConnectionInfoState { + connectionInfo: Omit & Partial; + connectionStorage: typeof ConnectionStorage; +} + +export type ConnectionInfoAction = + | ChangeConnectionInfoAction + | SaveConnectionInfoAction; + +async function saveConnectionInfo( + connectionInfo: ConnectionInfo, + connectionStorage: typeof ConnectionStorage +) { try { - await connectionStorage.save(connectionInfo); + await connectionStorage.save({ connectionInfo }); debug(`saved connection with id ${connectionInfo.id || ''}`); - } catch (err) { + } catch (err: any) { // Currently we silently fail if saving the favorite fails. debug( `error saving connection with id ${connectionInfo.id || ''}: ${ @@ -47,7 +74,10 @@ async function saveConnectionInfo(connectionInfo, connectionStorage) { * * @returns {Object} The new state. */ -const doChangeConnectionInfo = (state, action) => { +const doChangeConnectionInfo = ( + state: ConnectionInfoState, + action: ChangeConnectionInfoAction +) => { return { ...state, connectionInfo: action.connectionInfo }; }; @@ -59,8 +89,11 @@ const doChangeConnectionInfo = (state, action) => { * * @returns {Object} The new state. */ -const doSaveConnectionInfo = (state, action) => { - saveConnectionInfo(action.connectionInfo, state.connectionStorage); +const doSaveConnectionInfo = ( + state: ConnectionInfoState, + action: SaveConnectionInfoAction +) => { + void saveConnectionInfo(action.connectionInfo, state.connectionStorage); return { ...state, connectionInfo: action.connectionInfo }; }; @@ -68,7 +101,12 @@ const doSaveConnectionInfo = (state, action) => { /** * To not have a huge switch statement in the reducer. */ -const MAPPINGS = { +const MAPPINGS: { + [Type in ConnectionInfoAction['type']]: ( + state: ConnectionInfoState, + action: ConnectionInfoAction & { type: Type } + ) => ConnectionInfoState; +} = { [CHANGE_CONNECTION_INFO]: doChangeConnectionInfo, [SAVE_CONNECTION_INFO]: doSaveConnectionInfo, }; @@ -80,10 +118,13 @@ const MAPPINGS = { * @param {Object} action - The action. * */ -export default function reducer(state = INITIAL_STATE, action) { - const fn = MAPPINGS[action.type]; +export default function reducer( + state: ConnectionInfoState = INITIAL_STATE, + action: RootAction +) { + const fn = MAPPINGS[action.type as ConnectionInfoAction['type']]; - return fn ? fn(state, action) : state; + return fn ? fn(state, action as any) : state; } /** @@ -93,7 +134,9 @@ export default function reducer(state = INITIAL_STATE, action) { * * @returns {Object} The action. */ -export const changeConnectionInfo = (connectionInfo) => ({ +export const changeConnectionInfo = ( + connectionInfo: ConnectionInfo +): ChangeConnectionInfoAction => ({ type: CHANGE_CONNECTION_INFO, connectionInfo, }); @@ -105,7 +148,9 @@ export const changeConnectionInfo = (connectionInfo) => ({ * * @returns {Object} The action. */ -export const updateAndSaveConnectionInfo = (connectionInfo) => ({ +export const updateAndSaveConnectionInfo = ( + connectionInfo: ConnectionInfo +): SaveConnectionInfoAction => ({ type: SAVE_CONNECTION_INFO, connectionInfo, }); diff --git a/packages/compass-sidebar/src/modules/connection-options.spec.js b/packages/compass-sidebar/src/modules/connection-options.spec.ts similarity index 95% rename from packages/compass-sidebar/src/modules/connection-options.spec.js rename to packages/compass-sidebar/src/modules/connection-options.spec.ts index 3e2e7a60830..0ea8f372a54 100644 --- a/packages/compass-sidebar/src/modules/connection-options.spec.js +++ b/packages/compass-sidebar/src/modules/connection-options.spec.ts @@ -7,7 +7,7 @@ import reducer, { describe('connection options module', function () { it('correctly sets the initial state', function () { - expect(reducer(undefined, {})).to.deep.equal(INITIAL_STATE); + expect(reducer(undefined, {} as any)).to.deep.equal(INITIAL_STATE); }); it('does not truncate hosts shorter than 25 characters', function () { diff --git a/packages/compass-sidebar/src/modules/connection-options.ts b/packages/compass-sidebar/src/modules/connection-options.ts index 1ad37b5c4e9..9047819802d 100644 --- a/packages/compass-sidebar/src/modules/connection-options.ts +++ b/packages/compass-sidebar/src/modules/connection-options.ts @@ -1,17 +1,22 @@ -import type { AnyAction } from 'redux'; +import type { RootAction } from '.'; const HOST_STRING_LENGTH = 25; export const CHANGE_CONNECTION_OPTIONS = - 'sidebar/connection-options/CHANGE_CONNECTION_OPTIONS'; + 'sidebar/connection-options/CHANGE_CONNECTION_OPTIONS' as const; +interface ChangeConnectionOptionsAction { + type: typeof CHANGE_CONNECTION_OPTIONS; + options: ConnectionOptionsState; +} +export type ConnectionOptionsAction = ChangeConnectionOptionsAction; -export const INITIAL_STATE = { +export const INITIAL_STATE: ConnectionOptionsState = { sshTunnel: false, sshTunnelHostname: '', sshTunnelPort: '', sshTunnelHostPortString: '', }; -export type ConnectionOptions = { +export type ConnectionOptionsState = { sshTunnel: boolean; sshTunnelHostname: string; sshTunnelPort: string | number; @@ -20,8 +25,8 @@ export type ConnectionOptions = { export default function reducer( state = INITIAL_STATE, - action: AnyAction -): ConnectionOptions { + action: RootAction +): ConnectionOptionsState { if (action.type === CHANGE_CONNECTION_OPTIONS) { return action.options; } @@ -38,7 +43,7 @@ function combineHostPort(host: string, port: string | number): string { export function changeConnectionOptions(connectionOptions: { sshTunnel?: { host: string; port: string | number }; -}): { type: typeof CHANGE_CONNECTION_OPTIONS; options: ConnectionOptions } { +}): ConnectionOptionsAction { const sshTunnel = !!connectionOptions.sshTunnel; const sshTunnelHostname = connectionOptions?.sshTunnel?.host ?? ''; const sshTunnelPort = connectionOptions?.sshTunnel?.port ?? ''; diff --git a/packages/compass-sidebar/src/modules/databases.js b/packages/compass-sidebar/src/modules/databases.js deleted file mode 100644 index ccb4ace9a67..00000000000 --- a/packages/compass-sidebar/src/modules/databases.js +++ /dev/null @@ -1,174 +0,0 @@ -import toNS from 'mongodb-ns'; - -/** - * Databases actions. - */ -export const CHANGE_FILTER_REGEX = 'sidebar/databases/CHANGE_FILTER_REGEX'; -export const CHANGE_DATABASES = 'sidebar/databases/CHANGE_DATABASES'; -export const CHANGE_ACTIVE_NAMESPACE = - 'sidebar/databases/CHANGE_ACTIVE_NAMESPACE'; -export const TOGGLE_DATABASE = 'sidebar/databases/TOGGLE_DATABASE'; - -const NO_REGEX = null; - -export const NO_ACTIVE_NAMESPACE = ''; - -/** - * The initial state of the sidebar databases. - */ -export const INITIAL_STATE = { - databases: [], - filteredDatabases: [], - expandedDbList: {}, - activeNamespace: NO_ACTIVE_NAMESPACE, - filterRegex: NO_REGEX, -}; - -/** - * Reducer function for handle state changes to sidebar databases. - * - * @param {String} state - The sidebar databases state. - * @param {Object} action - The action. - * - * @returns {Object} The new state. - */ -export default function reducer(state = INITIAL_STATE, action) { - if (action.type === TOGGLE_DATABASE) { - return { - ...state, - expandedDbList: { - ...state.expandedDbList, - [action.id]: action.expanded, - }, - }; - } - - if (action.type === CHANGE_FILTER_REGEX) { - const filterModeStatusChange = - Boolean(state.filterRegex && !action.filterRegex) || - Boolean(!state.filterRegex && action.filterRegex); - - let expandedDbList = state.expandedDbList; - - // On filter mode status change (when either entering "filter mode" when no - // regex was in the search box before or exiting it) we want to filter out - // all the "collapsed" states so that default collapsed state that is based - // on the "filter mode" can take over. When user then collapses something in - // the navigation we want to preserve their choice until the "filter mode" - // is changed again - if (filterModeStatusChange) { - expandedDbList = Object.fromEntries( - Object.entries(expandedDbList).filter(([, val]) => val !== false) - ); - } - - return { - ...state, - filterRegex: action.filterRegex, - filteredDatabases: filterDatabases(state.databases, action.filterRegex), - expandedDbList, - }; - } - - if (action.type === CHANGE_ACTIVE_NAMESPACE) { - return { - ...state, - activeNamespace: action.activeNamespace, - ...(action.activeDatabase && { - expandedDbList: { - ...state.expandedDbList, - [action.activeDatabase]: true, - }, - }), - }; - } - - if (action.type === CHANGE_DATABASES) { - return { - ...state, - databases: action.databases, - filteredDatabases: filterDatabases(action.databases, state.filterRegex), - }; - } - return state; -} - -/** - * The change databases action creator. - * - * @param {Array} databases - * - * @returns {Object} The action. - */ -export const changeDatabases = (databases) => ({ - type: CHANGE_DATABASES, - databases, -}); - -/** - * The change active namespace action creator. - * - * @param {String} activeNamespace - * - * @returns {Object} The action. - */ -export const changeActiveNamespace = (activeNamespace) => ({ - type: CHANGE_ACTIVE_NAMESPACE, - activeNamespace, - activeDatabase: toNS(activeNamespace).database, -}); - -export const toggleDatabaseExpanded = (id, force) => (dispatch, getState) => { - const { appRegistry, databases } = getState(); - const expanded = force ?? !databases.expandedDbList[id]; - if (appRegistry.globalAppRegistry && expanded) { - // Fetch collections list on expand if we haven't done it yet (this is - // relevant only for the code path that has global overlay disabled) - appRegistry.globalAppRegistry.emit('sidebar-expand-database', id); - } - dispatch({ type: TOGGLE_DATABASE, id, expanded }); -}; - -/** - * The change filterRegex action creator. - * - * @param {RegExp | null} filterRegex - The filterRegex. - * - * @returns {Object} The action. - */ -export const changeFilterRegex = (filterRegex) => (dispatch, getState) => { - const { appRegistry } = getState(); - if (appRegistry.globalAppRegistry && filterRegex) { - // When filtering, emit an event so that we can fetch all collections. This - // is required as a workaround for the syncronous nature of the current - // filtering feature - appRegistry.globalAppRegistry.emit('sidebar-filter-navigation-list'); - } - dispatch({ - type: CHANGE_FILTER_REGEX, - filterRegex: filterRegex, - }); -}; - -const filterDatabases = (databases, re) => { - if (!re) { - return databases; - } - - return databases.reduce((result, db) => { - const id = db._id; - if (re.test(id)) { - result.push(db); - } else { - const collections = db.collections.filter((coll) => re.test(coll.name)); - - if (collections.length > 0) { - result.push({ - ...db, - collections, - }); - } - } - return result; - }, []); -}; diff --git a/packages/compass-sidebar/src/modules/databases.spec.js b/packages/compass-sidebar/src/modules/databases.spec.ts similarity index 91% rename from packages/compass-sidebar/src/modules/databases.spec.js rename to packages/compass-sidebar/src/modules/databases.spec.ts index 0d4aa334165..29f8452686f 100644 --- a/packages/compass-sidebar/src/modules/databases.spec.js +++ b/packages/compass-sidebar/src/modules/databases.spec.ts @@ -1,3 +1,4 @@ +import type { RootAction } from './'; import { expect } from 'chai'; import databasesReducer, { @@ -9,16 +10,16 @@ import databasesReducer, { import { createInstance } from '../../test/helpers'; -function createGetState(dbs) { +function createGetState(dbs: any[] = []) { return function () { return { instance: createInstance(dbs).toJSON(), - appRegistry: {}, + appRegistry: { localAppRegistry: null, globalAppRegistry: null }, }; }; } -function createDatabases(dbs) { +function createDatabases(dbs: any[] = []) { return createInstance(dbs).databases.map((db) => { return { ...db.toJSON(), @@ -33,10 +34,10 @@ function createMockStoreSlice(initialState = {}, reducer = databasesReducer) { get state() { return state; }, - dispatch(action) { + dispatch(action: RootAction) { state = reducer(state, action); }, - }; + } as any; } describe('sidebar databases', function () { @@ -129,7 +130,7 @@ describe('sidebar databases', function () { context('when an action is not provided', function () { it('returns the default state', function () { - expect(databasesReducer(undefined, {})).to.equal(INITIAL_STATE); + expect(databasesReducer(undefined, {} as any)).to.equal(INITIAL_STATE); }); }); }); diff --git a/packages/compass-sidebar/src/modules/databases.ts b/packages/compass-sidebar/src/modules/databases.ts new file mode 100644 index 00000000000..5c5318647d0 --- /dev/null +++ b/packages/compass-sidebar/src/modules/databases.ts @@ -0,0 +1,230 @@ +import type { MongoDBInstance } from 'mongodb-instance-model'; +import toNS from 'mongodb-ns'; +import type { RootAction, RootState } from '.'; +import type { Dispatch } from 'redux'; + +type NS = ReturnType; +/** + * Databases actions. + */ +export const CHANGE_FILTER_REGEX = + 'sidebar/databases/CHANGE_FILTER_REGEX' as const; +interface ChangeFilterRegexAction { + type: typeof CHANGE_FILTER_REGEX; + filterRegex: null | RegExp; +} + +export const CHANGE_DATABASES = 'sidebar/databases/CHANGE_DATABASES' as const; +interface ChangeDatabasesAction { + type: typeof CHANGE_DATABASES; + databases: Database[]; +} + +export const CHANGE_ACTIVE_NAMESPACE = + 'sidebar/databases/CHANGE_ACTIVE_NAMESPACE' as const; +interface ChangeActiveNamespaceAction { + type: typeof CHANGE_ACTIVE_NAMESPACE; + activeNamespace: string | NS; + activeDatabase: string; +} + +export const TOGGLE_DATABASE = 'sidebar/databases/TOGGLE_DATABASE' as const; +interface ToggleDatabaseAction { + type: typeof TOGGLE_DATABASE; + id: string; + expanded: boolean; +} + +export type DatabasesAction = + | ChangeFilterRegexAction + | ChangeDatabasesAction + | ChangeActiveNamespaceAction + | ToggleDatabaseAction; + +const NO_REGEX = null; + +export const NO_ACTIVE_NAMESPACE = ''; + +type DatabaseRaw = MongoDBInstance['databases'][number]; +export type Database = Pick< + DatabaseRaw, + '_id' | 'name' | 'collectionsStatus' | 'collectionsLength' +> & { + collections: Pick< + DatabaseRaw['collections'][number], + '_id' | 'name' | 'type' + >[]; +}; +export interface DatabaseState { + databases: Database[]; + filteredDatabases: Database[]; + expandedDbList: Record; + activeNamespace: string | NS; + filterRegex: null | RegExp; +} + +/** + * The initial state of the sidebar databases. + */ +export const INITIAL_STATE: DatabaseState = { + databases: [], + filteredDatabases: [], + expandedDbList: Object.create(null), + activeNamespace: NO_ACTIVE_NAMESPACE, + filterRegex: NO_REGEX, +}; + +/** + * Reducer function for handle state changes to sidebar databases. + * + * @param {String} state - The sidebar databases state. + * @param {Object} action - The action. + * + * @returns {Object} The new state. + */ +export default function reducer( + state: DatabaseState = INITIAL_STATE, + action: RootAction +): DatabaseState { + if (action.type === TOGGLE_DATABASE) { + return { + ...state, + expandedDbList: { + ...state.expandedDbList, + [action.id]: action.expanded, + }, + }; + } + + if (action.type === CHANGE_FILTER_REGEX) { + const filterModeStatusChange = + Boolean(state.filterRegex && !action.filterRegex) || + Boolean(!state.filterRegex && action.filterRegex); + + let expandedDbList = state.expandedDbList; + + // On filter mode status change (when either entering "filter mode" when no + // regex was in the search box before or exiting it) we want to filter out + // all the "collapsed" states so that default collapsed state that is based + // on the "filter mode" can take over. When user then collapses something in + // the navigation we want to preserve their choice until the "filter mode" + // is changed again + if (filterModeStatusChange) { + expandedDbList = Object.fromEntries( + Object.entries(expandedDbList).filter(([, val]) => val !== false) + ); + } + + return { + ...state, + filterRegex: action.filterRegex, + filteredDatabases: filterDatabases(state.databases, action.filterRegex), + expandedDbList, + }; + } + + if (action.type === CHANGE_ACTIVE_NAMESPACE) { + return { + ...state, + activeNamespace: action.activeNamespace, + ...(action.activeDatabase && { + expandedDbList: { + ...state.expandedDbList, + [action.activeDatabase]: true, + }, + }), + }; + } + + if (action.type === CHANGE_DATABASES) { + return { + ...state, + databases: action.databases, + filteredDatabases: filterDatabases(action.databases, state.filterRegex), + }; + } + return state; +} + +/** + * The change databases action creator. + * + * @param {Array} databases + * + * @returns {Object} The action. + */ +export const changeDatabases = (databases: Database[]) => ({ + type: CHANGE_DATABASES, + databases, +}); + +/** + * The change active namespace action creator. + * + * @param {String} activeNamespace + * + * @returns {Object} The action. + */ +export const changeActiveNamespace = ( + activeNamespace: string | NS +): ChangeActiveNamespaceAction => ({ + type: CHANGE_ACTIVE_NAMESPACE, + activeNamespace, + activeDatabase: toNS(activeNamespace).database, +}); + +export const toggleDatabaseExpanded = + (id: string, forceExpand: boolean) => + (dispatch: Dispatch, getState: () => RootState) => { + const { appRegistry, databases } = getState(); + const expanded = forceExpand ?? !databases.expandedDbList[id]; + if (appRegistry.globalAppRegistry && expanded) { + // Fetch collections list on expand if we haven't done it yet (this is + // relevant only for the code path that has global overlay disabled) + appRegistry.globalAppRegistry.emit('sidebar-expand-database', id); + } + dispatch({ type: TOGGLE_DATABASE, id, expanded }); + }; + +export const changeFilterRegex = + (filterRegex: RegExp | null) => + ( + dispatch: Dispatch, + getState: () => Pick + ) => { + const { appRegistry } = getState(); + if (appRegistry.globalAppRegistry && filterRegex) { + // When filtering, emit an event so that we can fetch all collections. This + // is required as a workaround for the syncronous nature of the current + // filtering feature + appRegistry.globalAppRegistry.emit('sidebar-filter-navigation-list'); + } + dispatch({ + type: CHANGE_FILTER_REGEX, + filterRegex: filterRegex, + }); + }; + +const filterDatabases = (databases: Database[], re: RegExp | null) => { + if (!re) { + return databases; + } + + const result: Database[] = []; + for (const db of databases) { + const id = db._id; + if (re.test(id)) { + result.push(db); + } else { + const collections = db.collections.filter((coll) => re.test(coll.name)); + + if (collections.length > 0) { + result.push({ + ...db, + collections, + }); + } + } + } + return result; +}; diff --git a/packages/compass-sidebar/src/modules/index.js b/packages/compass-sidebar/src/modules/index.js deleted file mode 100644 index 279454bab0e..00000000000 --- a/packages/compass-sidebar/src/modules/index.js +++ /dev/null @@ -1,64 +0,0 @@ -import { combineReducers } from 'redux'; - -import appRegistry from '@mongodb-js/mongodb-redux-common/app-registry'; -import databases, { - INITIAL_STATE as DATABASES_INITIAL_STATE, -} from './databases'; -import instance, { INITIAL_STATE as INSTANCE_INITIAL_STATE } from './instance'; -import isDetailsExpanded, { - INITIAL_STATE as IS_DETAILS_EXPANDED_INITIAL_STATE, -} from './is-details-expanded'; -import { RESET } from './reset'; -import isGenuineMongoDBVisible, { - INITIAL_STATE as IS_VISIBLE_IS, -} from './is-genuine-mongodb-visible'; -import connectionInfo, { - INITIAL_STATE as CONNECTION_INFO_IS, -} from './connection-info'; -import connectionOptions, { - INITIAL_STATE as CONNECTION_OPTIONS_IS, -} from './connection-options'; -import location, { INITIAL_STATE as LOCATION_IS } from './location'; -import isExpanded, { INITIAL_STATE as IS_EXPANDED_IS } from './is-expanded'; - -/** - * The reducer. - */ -const reducer = combineReducers({ - appRegistry, - databases, - connectionInfo, - connectionOptions, - instance, - isDetailsExpanded, - isGenuineMongoDBVisible, - location, - isExpanded, -}); - -/** - * The root reducer. - * - * @param {Object} state - The state. - * @param {Object} action - The action. - * - * @returns {Object} The new state. - */ -const rootReducer = (state, action) => { - if (action.type === RESET) { - return { - ...state, - connectionInfo: CONNECTION_INFO_IS, - connectionOptions: CONNECTION_OPTIONS_IS, - databases: DATABASES_INITIAL_STATE, - instance: INSTANCE_INITIAL_STATE, - isDetailsExpanded: IS_DETAILS_EXPANDED_INITIAL_STATE, - isGenuineMongoDBVisible: IS_VISIBLE_IS, - location: LOCATION_IS, - isExpanded: IS_EXPANDED_IS, - }; - } - return reducer(state, action); -}; - -export default rootReducer; diff --git a/packages/compass-sidebar/src/modules/index.ts b/packages/compass-sidebar/src/modules/index.ts new file mode 100644 index 00000000000..09c9fa8c312 --- /dev/null +++ b/packages/compass-sidebar/src/modules/index.ts @@ -0,0 +1,116 @@ +import { combineReducers } from 'redux'; + +import appRegistry from '@mongodb-js/mongodb-redux-common/app-registry'; +import type { DatabaseState, DatabasesAction } from './databases'; +import databases, { + INITIAL_STATE as DATABASES_INITIAL_STATE, +} from './databases'; +import type { InstanceAction, InstanceState } from './instance'; +import instance, { INITIAL_STATE as INSTANCE_INITIAL_STATE } from './instance'; +import type { + IsDetailsExpandedState, + ToggleIsDetailsExpandedAction, +} from './is-details-expanded'; +import isDetailsExpanded, { + INITIAL_STATE as IS_DETAILS_EXPANDED_INITIAL_STATE, +} from './is-details-expanded'; +import type { ResetAction } from './reset'; +import { RESET } from './reset'; +import type { + IsGenuineMongoDBVisibleAction, + IsGenuineMongoDBVisibleState, +} from './is-genuine-mongodb-visible'; +import isGenuineMongoDBVisible, { + INITIAL_STATE as IS_VISIBLE_IS, +} from './is-genuine-mongodb-visible'; +import type { + ConnectionInfoAction, + ConnectionInfoState, +} from './connection-info'; +import connectionInfo, { + INITIAL_STATE as CONNECTION_INFO_IS, +} from './connection-info'; +import type { + ConnectionOptionsAction, + ConnectionOptionsState, +} from './connection-options'; +import connectionOptions, { + INITIAL_STATE as CONNECTION_OPTIONS_IS, +} from './connection-options'; +import type { LocationAction, LocationState } from './location'; +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'; + +export interface RootState { + appRegistry: { + globalAppRegistry: AppRegistry | null; + localAppRegistry: AppRegistry | null; + }; + connectionInfo: ConnectionInfoState; + connectionOptions: ConnectionOptionsState; + databases: DatabaseState; + instance: InstanceState; + isDetailsExpanded: IsDetailsExpandedState; + isGenuineMongoDBVisible: IsGenuineMongoDBVisibleState; + location: LocationState; + isExpanded: IsExpandedState; +} + +export type RootAction = + | ConnectionInfoAction + | ConnectionOptionsAction + | DatabasesAction + | InstanceAction + | ToggleIsDetailsExpandedAction + | IsGenuineMongoDBVisibleAction + | LocationAction + | IsExpandedAction + | ResetAction; + +/** + * The reducer. + */ +const reducer = combineReducers({ + appRegistry, + databases, + connectionInfo, + connectionOptions, + instance, + isDetailsExpanded, + isGenuineMongoDBVisible, + location, + isExpanded, +}); + +/** + * The root reducer. + * + * @param {Object} state - The state. + * @param {Object} action - The action. + * + * @returns {Object} The new state. + */ +const rootReducer = ( + state: RootState | undefined, + action: RootAction +): RootState => { + if (action.type === RESET || !state) { + return { + appRegistry: { globalAppRegistry: null, localAppRegistry: null }, + ...state, + connectionInfo: CONNECTION_INFO_IS, + connectionOptions: CONNECTION_OPTIONS_IS, + databases: DATABASES_INITIAL_STATE, + instance: INSTANCE_INITIAL_STATE, + isDetailsExpanded: IS_DETAILS_EXPANDED_INITIAL_STATE, + isGenuineMongoDBVisible: IS_VISIBLE_IS, + location: LOCATION_IS, + isExpanded: IS_EXPANDED_IS, + }; + } + return reducer(state, action); +}; + +export default rootReducer; diff --git a/packages/compass-sidebar/src/modules/instance.js b/packages/compass-sidebar/src/modules/instance.js deleted file mode 100644 index c1b79f360a8..00000000000 --- a/packages/compass-sidebar/src/modules/instance.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Instance action. - */ -export const CHANGE_INSTANCE = 'sidebar/instance/CHANGE_INSTANCE'; - -/** - * The initial state of the sidebar instance. - */ -export const INITIAL_STATE = null; - -/** - * Reducer function for handle state changes to sidebar instance. - * - * @param {String} state - The sidebar instance state. - * @param {Object} action - The action. - * - * @returns {String} The new state. - */ -export default function reducer(state = INITIAL_STATE, action) { - if (action.type === CHANGE_INSTANCE) { - return action.instance; - } - return state; -} - -/** - * The change instance action creator. - * - * @param {String} instance - The instance. - * - * @returns {Object} The action. - */ -export const changeInstance = (instance) => ({ - type: CHANGE_INSTANCE, - instance: instance, -}); diff --git a/packages/compass-sidebar/src/modules/instance.spec.js b/packages/compass-sidebar/src/modules/instance.spec.ts similarity index 82% rename from packages/compass-sidebar/src/modules/instance.spec.js rename to packages/compass-sidebar/src/modules/instance.spec.ts index d868b22e6c8..9ca6b38003e 100644 --- a/packages/compass-sidebar/src/modules/instance.spec.js +++ b/packages/compass-sidebar/src/modules/instance.spec.ts @@ -6,7 +6,7 @@ import reducer, { CHANGE_INSTANCE, } from './instance'; -const instance = { +const instance: any = { _id: '123', }; @@ -22,14 +22,14 @@ describe('sidebar instance', function () { context('when an action is not provided', function () { it('returns the default state', function () { - expect(reducer(undefined, {})).to.equal(INITIAL_STATE); + expect(reducer(undefined, {} as any)).to.equal(INITIAL_STATE); }); }); }); describe('#changeInstance', function () { it('returns the action', function () { - expect(changeInstance('new instance w action')).to.deep.equal({ + expect(changeInstance('new instance w action' as any)).to.deep.equal({ type: CHANGE_INSTANCE, instance: 'new instance w action', }); diff --git a/packages/compass-sidebar/src/modules/instance.ts b/packages/compass-sidebar/src/modules/instance.ts new file mode 100644 index 00000000000..ba4df7ec693 --- /dev/null +++ b/packages/compass-sidebar/src/modules/instance.ts @@ -0,0 +1,61 @@ +import type { MongoDBInstance } from 'mongodb-instance-model'; +import type { RootAction } from '.'; + +/** + * Instance action. + */ +export const CHANGE_INSTANCE = 'sidebar/instance/CHANGE_INSTANCE' as const; +export interface ChangeInstanceAction { + type: typeof CHANGE_INSTANCE; + instance: InstanceState; +} + +/** + * The initial state of the sidebar instance. + */ +export const INITIAL_STATE: InstanceState = null; +export type InstanceState = null | Pick< + MongoDBInstance, + | 'refreshingStatus' + | 'databasesStatus' + | 'csfleMode' + | 'build' + | 'dataLake' + | 'genuineMongoDB' + | 'topologyDescription' + | 'isWritable' + | 'env' + | 'isAtlas' + | 'isLocalAtlas' +>; +export type InstanceAction = ChangeInstanceAction; + +/** + * Reducer function for handle state changes to sidebar instance. + * + * @param {String} state - The sidebar instance state. + * @param {Object} action - The action. + * + * @returns {String} The new state. + */ +export default function reducer( + state: InstanceState = INITIAL_STATE, + action: RootAction +): InstanceState { + if (action.type === CHANGE_INSTANCE) { + return action.instance; + } + return state; +} + +/** + * The change instance action creator. + * + * @param {String} instance - The instance. + * + * @returns {Object} The action. + */ +export const changeInstance = (instance: InstanceState) => ({ + type: CHANGE_INSTANCE, + instance: instance, +}); diff --git a/packages/compass-sidebar/src/modules/is-details-expanded.spec.js b/packages/compass-sidebar/src/modules/is-details-expanded.spec.ts similarity index 92% rename from packages/compass-sidebar/src/modules/is-details-expanded.spec.js rename to packages/compass-sidebar/src/modules/is-details-expanded.spec.ts index dba77973854..ffa2daddab9 100644 --- a/packages/compass-sidebar/src/modules/is-details-expanded.spec.js +++ b/packages/compass-sidebar/src/modules/is-details-expanded.spec.ts @@ -18,7 +18,7 @@ describe('sidebar isDetailsExpanded', function () { context('when an action is not provided', function () { it('returns the default state', function () { - expect(reducer(undefined, {})).to.equal(INITIAL_STATE); + expect(reducer(undefined, {} as any)).to.equal(INITIAL_STATE); }); }); }); diff --git a/packages/compass-sidebar/src/modules/is-details-expanded.js b/packages/compass-sidebar/src/modules/is-details-expanded.ts similarity index 54% rename from packages/compass-sidebar/src/modules/is-details-expanded.js rename to packages/compass-sidebar/src/modules/is-details-expanded.ts index feecc04e66d..436057bd6c9 100644 --- a/packages/compass-sidebar/src/modules/is-details-expanded.js +++ b/packages/compass-sidebar/src/modules/is-details-expanded.ts @@ -1,13 +1,20 @@ +import type { RootAction } from '.'; + /** * Change is details expanded */ export const TOGGLE_IS_DETAILS_EXPANDED = - 'sidebar/is-details-expanded/TOGGLE_IS_DETAILS_EXPANDED'; + 'sidebar/is-details-expanded/TOGGLE_IS_DETAILS_EXPANDED' as const; +export interface ToggleIsDetailsExpandedAction { + type: typeof TOGGLE_IS_DETAILS_EXPANDED; + isExpanded: boolean; +} /** * The initial state of the is expanded attribute. */ -export const INITIAL_STATE = true; +export const INITIAL_STATE: IsDetailsExpandedState = true; +export type IsDetailsExpandedState = boolean; /** * Reducer function for handle state changes to is details expanded. @@ -17,7 +24,10 @@ export const INITIAL_STATE = true; * * @returns {Array} The new state. */ -export default function reducer(state = INITIAL_STATE, action) { +export default function reducer( + state: IsDetailsExpandedState = INITIAL_STATE, + action: RootAction +): IsDetailsExpandedState { if (action.type === TOGGLE_IS_DETAILS_EXPANDED) { return action.isExpanded; } @@ -31,7 +41,9 @@ export default function reducer(state = INITIAL_STATE, action) { * * @returns {Object} The action. */ -export const toggleIsDetailsExpanded = (isExpanded) => ({ +export const toggleIsDetailsExpanded = ( + isExpanded: boolean +): ToggleIsDetailsExpandedAction => ({ type: TOGGLE_IS_DETAILS_EXPANDED, isExpanded: isExpanded, }); diff --git a/packages/compass-sidebar/src/modules/is-expanded.ts b/packages/compass-sidebar/src/modules/is-expanded.ts index 1fc4d645b3e..3ad2a4df30a 100644 --- a/packages/compass-sidebar/src/modules/is-expanded.ts +++ b/packages/compass-sidebar/src/modules/is-expanded.ts @@ -1,11 +1,23 @@ -import type { AnyAction } from 'redux'; +import type { RootAction } from '.'; -export const TOGGLE = 'sidebar/TOGGLE'; -export const SET_EXPANDED = 'sidebar/SET_EXPANDED'; +export const TOGGLE = 'sidebar/TOGGLE' as const; +interface ToggleAction { + type: typeof TOGGLE; +} +export const SET_EXPANDED = 'sidebar/SET_EXPANDED' as const; +interface SetExpandedAction { + type: typeof SET_EXPANDED; + isExpanded: boolean; +} +export type IsExpandedAction = ToggleAction | SetExpandedAction; +export type IsExpandedState = boolean; -export const INITIAL_STATE = true; +export const INITIAL_STATE: IsExpandedState = true; -export default function reducer(state = INITIAL_STATE, action: AnyAction) { +export default function reducer( + state: IsExpandedState = INITIAL_STATE, + action: RootAction +): IsExpandedState { if (action.type === TOGGLE) { return !state; } @@ -15,11 +27,11 @@ export default function reducer(state = INITIAL_STATE, action: AnyAction) { return state; } -export const toggleSidebar = () => ({ +export const toggleSidebar = (): ToggleAction => ({ type: TOGGLE, }); -export const setIsExpanded = (isExpanded: boolean) => ({ +export const setIsExpanded = (isExpanded: boolean): SetExpandedAction => ({ type: SET_EXPANDED, isExpanded, }); diff --git a/packages/compass-sidebar/src/modules/is-genuine-mongodb-visible.js b/packages/compass-sidebar/src/modules/is-genuine-mongodb-visible.js deleted file mode 100644 index 2ea68ac320f..00000000000 --- a/packages/compass-sidebar/src/modules/is-genuine-mongodb-visible.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * The prefix. - */ -const PREFIX = 'sidebar/is-genuine-mongodb-visible'; - -/** - * Toggle is visible. - */ -export const TOGGLE_IS_GENUINE_MONGODB_VISIBLE = `${PREFIX}/TOGGLE_IS_GENUINE_MONGODB_VISIBLE`; - -/** - * The initial state of the is visible attribute. - */ -export const INITIAL_STATE = false; - -/** - * Reducer function for handle state changes to is visible. - * - * @param {Boolean} state - The is visible state. - * @param {Object} action - The action. - * - * @returns {Boolean} The new state. - */ -export default function reducer(state = INITIAL_STATE, action) { - if (action.type === TOGGLE_IS_GENUINE_MONGODB_VISIBLE) { - return action.isVisible; - } - return state; -} - -/** - * The toggle is visible action creator. - * - * @param {Boolean} isVisible - Is visible. - * - * @returns {Object} The action. - */ -export const toggleIsGenuineMongoDBVisible = (isVisible) => ({ - type: TOGGLE_IS_GENUINE_MONGODB_VISIBLE, - isVisible: isVisible, -}); diff --git a/packages/compass-sidebar/src/modules/is-genuine-mongodb-visible.spec.js b/packages/compass-sidebar/src/modules/is-genuine-mongodb-visible.spec.ts similarity index 92% rename from packages/compass-sidebar/src/modules/is-genuine-mongodb-visible.spec.js rename to packages/compass-sidebar/src/modules/is-genuine-mongodb-visible.spec.ts index d35fa1542b3..9beade36fbf 100644 --- a/packages/compass-sidebar/src/modules/is-genuine-mongodb-visible.spec.js +++ b/packages/compass-sidebar/src/modules/is-genuine-mongodb-visible.spec.ts @@ -18,7 +18,7 @@ describe('is genuine mongodb visible module', function () { context('when an action is not provided', function () { it('returns the default state', function () { - expect(reducer(undefined, {})).to.equal(INITIAL_STATE); + expect(reducer(undefined, {} as any)).to.equal(INITIAL_STATE); }); }); }); diff --git a/packages/compass-sidebar/src/modules/is-genuine-mongodb-visible.ts b/packages/compass-sidebar/src/modules/is-genuine-mongodb-visible.ts new file mode 100644 index 00000000000..9adef78e42d --- /dev/null +++ b/packages/compass-sidebar/src/modules/is-genuine-mongodb-visible.ts @@ -0,0 +1,55 @@ +import type { RootAction } from '.'; + +/** + * The prefix. + */ +const PREFIX = 'sidebar/is-genuine-mongodb-visible' as const; + +/** + * Toggle is visible. + */ +export const TOGGLE_IS_GENUINE_MONGODB_VISIBLE = + `${PREFIX}/TOGGLE_IS_GENUINE_MONGODB_VISIBLE` as const; +interface ToggleIsGenuineMongoDBVisibleAction { + type: typeof TOGGLE_IS_GENUINE_MONGODB_VISIBLE; + isVisible: boolean; +} +export type IsGenuineMongoDBVisibleAction = ToggleIsGenuineMongoDBVisibleAction; +export type IsGenuineMongoDBVisibleState = boolean; + +/** + * The initial state of the is visible attribute. + */ +export const INITIAL_STATE: IsGenuineMongoDBVisibleState = false; + +/** + * Reducer function for handle state changes to is visible. + * + * @param {Boolean} state - The is visible state. + * @param {Object} action - The action. + * + * @returns {Boolean} The new state. + */ +export default function reducer( + state: IsGenuineMongoDBVisibleState = INITIAL_STATE, + action: RootAction +): IsGenuineMongoDBVisibleState { + if (action.type === TOGGLE_IS_GENUINE_MONGODB_VISIBLE) { + return action.isVisible; + } + return state; +} + +/** + * The toggle is visible action creator. + * + * @param {Boolean} isVisible - Is visible. + * + * @returns {Object} The action. + */ +export const toggleIsGenuineMongoDBVisible = ( + isVisible: boolean +): ToggleIsGenuineMongoDBVisibleAction => ({ + type: TOGGLE_IS_GENUINE_MONGODB_VISIBLE, + isVisible: isVisible, +}); diff --git a/packages/compass-sidebar/src/modules/location.ts b/packages/compass-sidebar/src/modules/location.ts index be99d7a86d1..27bdece9d5a 100644 --- a/packages/compass-sidebar/src/modules/location.ts +++ b/packages/compass-sidebar/src/modules/location.ts @@ -1,4 +1,4 @@ -import type { AnyAction } from 'redux'; +import type { RootAction } from '.'; export const locations = { database: true, @@ -6,19 +6,29 @@ export const locations = { 'My Queries': true, Databases: true, }; +export type Location = keyof typeof locations; -export const CHANGE_LOCATION = 'sidebar/navigation/CHANGE_LOCATION'; +export const CHANGE_LOCATION = 'sidebar/navigation/CHANGE_LOCATION' as const; +interface ChangeLocationAction { + type: typeof CHANGE_LOCATION; + location: Location; +} +export type LocationAction = ChangeLocationAction; +export type LocationState = null | Location; export const INITIAL_STATE = null; -export default function reducer(state = INITIAL_STATE, action: AnyAction) { +export default function reducer( + state: LocationState = INITIAL_STATE, + action: RootAction +): LocationState { if (action.type === CHANGE_LOCATION) { return action.location; } return state; } -export const changeLocation = (location: keyof typeof locations) => ({ +export const changeLocation = (location: Location): ChangeLocationAction => ({ type: CHANGE_LOCATION, location, }); diff --git a/packages/compass-sidebar/src/modules/reset.js b/packages/compass-sidebar/src/modules/reset.js deleted file mode 100644 index b9c69a92b39..00000000000 --- a/packages/compass-sidebar/src/modules/reset.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * The reset action name. - */ -export const RESET = 'sidebar/reset'; - -/** - * Reset the state action. - * - * @return {Object} The action creator. - */ -export const reset = () => ({ - type: RESET, -}); diff --git a/packages/compass-sidebar/src/modules/reset.spec.js b/packages/compass-sidebar/src/modules/reset.spec.ts similarity index 100% rename from packages/compass-sidebar/src/modules/reset.spec.js rename to packages/compass-sidebar/src/modules/reset.spec.ts diff --git a/packages/compass-sidebar/src/modules/reset.ts b/packages/compass-sidebar/src/modules/reset.ts new file mode 100644 index 00000000000..0c713a7894d --- /dev/null +++ b/packages/compass-sidebar/src/modules/reset.ts @@ -0,0 +1,14 @@ +/** + * The reset action name. + */ +export const RESET = 'sidebar/reset' as const; +export type ResetAction = { type: typeof RESET }; + +/** + * Reset the state action. + * + * @return {Object} The action creator. + */ +export const reset = (): ResetAction => ({ + type: RESET, +}); diff --git a/packages/compass-sidebar/src/plugin.jsx b/packages/compass-sidebar/src/plugin.tsx similarity index 100% rename from packages/compass-sidebar/src/plugin.jsx rename to packages/compass-sidebar/src/plugin.tsx diff --git a/packages/compass-sidebar/src/stores/index.js b/packages/compass-sidebar/src/stores/index.ts similarity index 100% rename from packages/compass-sidebar/src/stores/index.js rename to packages/compass-sidebar/src/stores/index.ts diff --git a/packages/compass-sidebar/src/stores/store.spec.js b/packages/compass-sidebar/src/stores/store.spec.ts similarity index 93% rename from packages/compass-sidebar/src/stores/store.spec.js rename to packages/compass-sidebar/src/stores/store.spec.ts index d6ae23de971..324aae53804 100644 --- a/packages/compass-sidebar/src/stores/store.spec.js +++ b/packages/compass-sidebar/src/stores/store.spec.ts @@ -1,13 +1,15 @@ import AppRegistry from 'hadron-app-registry'; import { expect } from 'chai'; -import store from './'; +import store from '.'; import { reset } from '../modules/reset'; import { createInstance } from '../../test/helpers'; +import type { Database } from '../modules/databases'; +import type { MongoDBInstance } from 'mongodb-instance-model'; const instance = createInstance(); -function getDatabases(_instance) { - return _instance.databases.map((db) => { +function getDatabases(_instance: MongoDBInstance) { + return _instance.databases.map((db: Database) => { return { _id: db._id, name: db.name, diff --git a/packages/compass-sidebar/src/stores/store.js b/packages/compass-sidebar/src/stores/store.ts similarity index 81% rename from packages/compass-sidebar/src/stores/store.js rename to packages/compass-sidebar/src/stores/store.ts index dc2a7afa3d3..030e4d288d5 100644 --- a/packages/compass-sidebar/src/stores/store.js +++ b/packages/compass-sidebar/src/stores/store.ts @@ -1,25 +1,34 @@ +import type { Store } from 'redux'; import { createStore, applyMiddleware } from 'redux'; import throttle from 'lodash/throttle'; import thunk from 'redux-thunk'; import { globalAppRegistryActivated } from '@mongodb-js/mongodb-redux-common/app-registry'; +import type { RootAction, RootState } from '../modules'; import reducer from '../modules'; import { changeInstance } from '../modules/instance'; import { changeLocation } from '../modules/location'; +import type { Database } from '../modules/databases'; import { changeActiveNamespace, changeDatabases } from '../modules/databases'; 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 { toggleSidebar } from '../modules/is-expanded'; +import type AppRegistry from 'hadron-app-registry'; +import type { MongoDBInstance } from 'mongodb-instance-model'; // We use these symbols so that nothing from outside can access these values on // the store const kInstance = Symbol('instance'); -const store = createStore(reducer, applyMiddleware(thunk)); +const store: Store & { + [kInstance]: null | MongoDBInstance; +} = Object.assign(createStore(reducer, applyMiddleware(thunk)), { + [kInstance]: null, +}); -store.onActivated = (appRegistry) => { - const onInstanceChangeNow = (instance) => { +const onActivated = (appRegistry: AppRegistry) => { + const onInstanceChangeNow = (instance: any /* MongoDBInstance */) => { store.dispatch( changeInstance({ refreshingStatus: instance.refreshingStatus, @@ -41,7 +50,7 @@ store.onActivated = (appRegistry) => { onInstanceChangeNow(instance); }, 300); - function getDatabaseInfo(db) { + function getDatabaseInfo(db: Database) { return { _id: db._id, name: db.name, @@ -50,7 +59,7 @@ store.onActivated = (appRegistry) => { }; } - function getCollectionInfo(coll) { + function getCollectionInfo(coll: Database['collections'][number]) { return { _id: coll._id, name: coll.name, @@ -58,7 +67,7 @@ store.onActivated = (appRegistry) => { }; } - const onDatabasesChange = throttle((databases) => { + const onDatabasesChange = throttle((databases: Database[]) => { const dbs = databases.map((db) => { return { ...getDatabaseInfo(db), @@ -86,10 +95,13 @@ store.onActivated = (appRegistry) => { }); appRegistry.on('instance-destroyed', () => { - store[kInstance].off(); - store[kInstance].build.off(); - store[kInstance].dataLake.off(); - store[kInstance].genuineMongoDB.off(); + 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(); @@ -152,10 +164,13 @@ store.onActivated = (appRegistry) => { toggleIsGenuineMongoDBVisible(!instance.genuineMongoDB.isGenuine) ); - instance.genuineMongoDB.on('change:isGenuine', (model, isGenuine) => { - onInstanceChange(instance); // isGenuineMongoDB is part of instance state - store.dispatch(toggleIsGenuineMongoDBVisible(!isGenuine)); - }); + instance.genuineMongoDB.on( + 'change:isGenuine', + (model: unknown, isGenuine: boolean) => { + onInstanceChange(instance); // isGenuineMongoDB is part of instance state + store.dispatch(toggleIsGenuineMongoDBVisible(!isGenuine)); + } + ); instance.on('change:topologyDescription', () => { onInstanceChange(instance); @@ -199,4 +214,4 @@ store.onActivated = (appRegistry) => { }); }; -export default store; +export default Object.assign(store, { onActivated }); diff --git a/packages/compass-sidebar/test/helpers.js b/packages/compass-sidebar/test/helpers.ts similarity index 88% rename from packages/compass-sidebar/test/helpers.js rename to packages/compass-sidebar/test/helpers.ts index a1a79e4f554..8ca6c7e6e8f 100644 --- a/packages/compass-sidebar/test/helpers.js +++ b/packages/compass-sidebar/test/helpers.ts @@ -1,3 +1,4 @@ +// @ts-expect-error TopologyDescription is exported as an interface, not a class import { MongoDBInstance, TopologyDescription } from 'mongodb-instance-model'; export function createInstance( @@ -24,5 +25,5 @@ export function createInstance( }; }), topologyDescription: new TopologyDescription(topologyDescription), - }); + } as any); } diff --git a/packages/compass-sidebar/test/mocks/collection-store.js b/packages/compass-sidebar/test/mocks/collection-store.js deleted file mode 100644 index 2a308020a13..00000000000 --- a/packages/compass-sidebar/test/mocks/collection-store.js +++ /dev/null @@ -1,63 +0,0 @@ -const Reflux = require('reflux'); -const { NamespaceStore } = require('mongodb-reflux-store'); - -/** - * Sets global collection information. - */ -const CollectionStore = Reflux.createStore({ - /** - * Initialize the store. - */ - init() { - this.collection = {}; - this.activeTabIndex = 0; - }, - - /** - * Set the collection information. - * - * @param {Object} collection - The collection info. - */ - setCollection(collection) { - this.collection = collection; - if (collection._id) { - NamespaceStore.ns = collection._id; - } - }, - - /** - * Set the active tab idx for the current collection - * @param {number} idx current tab idx - */ - setActiveTab(idx) { - this.activeTabIndex = idx; - }, - - /** - * Get the active tab idx for the current collection - * @returns {number} the current idx - */ - getActiveTab() { - return this.activeTabIndex; - }, - - /** - * Get the collection ns. - * - * @returns {String} The collection ns. - */ - ns() { - return this.collection._id; - }, - - /** - * Is the collection readonly? - * - * @returns {Boolean} If the collection is readonly. - */ - isReadonly() { - return this.collection.readonly; - }, -}); - -module.exports = CollectionStore; diff --git a/packages/compass-sidebar/test/mocks/deployment-state-store.js b/packages/compass-sidebar/test/mocks/deployment-state-store.js deleted file mode 100644 index 8f8d5e31eb1..00000000000 --- a/packages/compass-sidebar/test/mocks/deployment-state-store.js +++ /dev/null @@ -1,42 +0,0 @@ -const Reflux = require('reflux'); -const StateMixin = require('reflux-state-mixin'); - -/** - * The default description. - */ -const DEFAULT_DESCRIPTION = 'Topology type not yet discovered.'; - -/** - * Deployment State store. - */ -const DeploymentStateStore = Reflux.createStore({ - /** - * adds a state to the store, similar to React.Component's state - * @see https://github.com/yonatanmn/Super-Simple-Flux#reflux-state-mixin - * - * If you call `this.setState({...})` this will cause the store to trigger - * and push down its state as props to connected components. - */ - mixins: [StateMixin.store], - - /** - * Initialize the Deployment State store state. The returned object must - * contain all keys that you might want to modify with this.setState(). - * - * @return {Object} initial store state. - */ - getInitialState() { - return { - isWritable: true, - description: DEFAULT_DESCRIPTION, - }; - }, - setToInitial() { - this.setState({ - isWritable: true, - description: 'description set from renderer', - }); - }, -}); - -module.exports = DeploymentStateStore; diff --git a/packages/compass-sidebar/test/mocks/namespace-store.js b/packages/compass-sidebar/test/mocks/namespace-store.js deleted file mode 100644 index 8078245eab4..00000000000 --- a/packages/compass-sidebar/test/mocks/namespace-store.js +++ /dev/null @@ -1,54 +0,0 @@ -const Reflux = require('reflux'); -const toNS = require('mongodb-ns'); - -/** - * The default namespace when the Compass user connects to a MongoDB instance. - */ -const DEFAULT_NAMESPACE = ''; - -/** - * The store holds the source of truth for the namespace being worked on. - */ -const NamespaceStore = Reflux.createStore({ - /** - * Initializing the store should set up the default namespace. - */ - init() { - this._ns = DEFAULT_NAMESPACE; - }, - - /** - * Gets the current namespace being worked with in the application. - */ - get ns() { - return this._ns; - }, - - /** - * Set the current namespace being worked on in the application. - * - * @param {String} ns - The current ns. - */ - set ns(ns) { - const registry = global.hadronApp.appRegistry; - if (registry) { - const oldNs = toNS(this._ns); - const newNs = toNS(ns); - - if (oldNs.database !== newNs.database) { - registry.emit('database-changed', ns); - } - if ( - oldNs.database !== newNs.database || - oldNs.collection !== newNs.collection - ) { - registry.emit('collection-changed', ns); - } - } - // TODO: still trigger if appRegistry is not available? - this._ns = ns; - this.trigger(this._ns); - }, -}); - -module.exports = NamespaceStore; diff --git a/packages/compass-sidebar/test/setup.js b/packages/compass-sidebar/test/setup.js deleted file mode 100644 index 3484bc4d75c..00000000000 --- a/packages/compass-sidebar/test/setup.js +++ /dev/null @@ -1,15 +0,0 @@ -const hadronApp = require('hadron-app'); -const { AppRegistry } = require('hadron-app-registry'); - -const NamespaceStore = require('./mocks/namespace-store'); -const CollectionStore = require('./mocks/collection-store'); - -const appRegistry = new AppRegistry(); - -global.hadronApp = hadronApp; -global.hadronApp.appRegistry = new AppRegistry(); - -appRegistry.registerStore('App.CollectionStore', CollectionStore); -appRegistry.registerStore('App.NamespaceStore', NamespaceStore); - -appRegistry.onActivated(); diff --git a/packages/connection-storage/src/connection-title.spec.ts b/packages/connection-storage/src/connection-title.spec.ts index 5a050a9cc5f..2cbcffa815b 100644 --- a/packages/connection-storage/src/connection-title.spec.ts +++ b/packages/connection-storage/src/connection-title.spec.ts @@ -5,7 +5,6 @@ describe('getConnectionTitle', function () { it('works with default host', function () { expect( getConnectionTitle({ - id: '123', connectionOptions: { connectionString: 'mongodb://localhost:27017', }, @@ -16,7 +15,6 @@ describe('getConnectionTitle', function () { it('returns the hostname if the connection is srv', function () { expect( getConnectionTitle({ - id: '123', connectionOptions: { connectionString: 'mongodb+srv://somehost', }, @@ -27,7 +25,6 @@ describe('getConnectionTitle', function () { it('returns hosts if the connection is not srv', function () { expect( getConnectionTitle({ - id: '123', connectionOptions: { connectionString: 'mongodb://example.com:12345,example123.com:123452/', @@ -39,7 +36,6 @@ describe('getConnectionTitle', function () { it('returns the name of the favorite if connection is favorite', function () { expect( getConnectionTitle({ - id: '123', connectionOptions: { connectionString: 'somethingwrong', }, @@ -53,7 +49,6 @@ describe('getConnectionTitle', function () { it('falls back to hostname:port if nothing else match', function () { expect( getConnectionTitle({ - id: '123', connectionOptions: { connectionString: 'mongodb://somehost:12345', }, @@ -64,7 +59,6 @@ describe('getConnectionTitle', function () { it('falls back to connection string if it is an invalid connection string', function () { expect( getConnectionTitle({ - id: '123', connectionOptions: { connectionString: 'pineapple', }, @@ -75,7 +69,6 @@ describe('getConnectionTitle', function () { it('falls back to the default name Connection if there is no connection string', function () { expect( getConnectionTitle({ - id: '123', connectionOptions: { connectionString: '', }, diff --git a/packages/connection-storage/src/connection-title.ts b/packages/connection-storage/src/connection-title.ts index eb78a45ca3e..e4651af39a8 100644 --- a/packages/connection-storage/src/connection-title.ts +++ b/packages/connection-storage/src/connection-title.ts @@ -1,7 +1,9 @@ import ConnectionString from 'mongodb-connection-string-url'; import type { ConnectionInfo } from './connection-info'; -export function getConnectionTitle(info: ConnectionInfo): string { +export function getConnectionTitle( + info: Pick +): string { if (info.favorite?.name) { return info.favorite.name; }