diff --git a/package-lock.json b/package-lock.json index 5c416678b65..90a2395a0c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -60481,8 +60481,8 @@ "@xyflow/react": "^12.5.1", "d3-path": "^3.1.0", "elkjs": "^0.10.0", - "react": "^17.0.2", - "react-dom": "^17.0.2" + "react": "17.0.2", + "react-dom": "17.0.2" }, "dependencies": { "@leafygreen-ui/emotion": { @@ -60623,14 +60623,14 @@ "@babel/eslint-parser": "^7.22.7", "@babel/preset-env": "^7.22.7", "@babel/preset-react": "^7.22.5", - "@typescript-eslint/eslint-plugin": "^8.38.0", - "@typescript-eslint/parser": "^8.38.0", + "@typescript-eslint/eslint-plugin": "^5.59.0", + "@typescript-eslint/parser": "^5.59.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-filename-rules": "^1.2.0", - "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-jsx-a11y": "^6.4.1", "eslint-plugin-mocha": "^8.0.0", - "eslint-plugin-react": "^7.37.5", - "eslint-plugin-react-hooks": "^5.2.0" + "eslint-plugin-react": "^7.24.0", + "eslint-plugin-react-hooks": "^4.2.0" } }, "@mongodb-js/eslint-plugin-compass": { @@ -65075,7 +65075,7 @@ "requires": { "@babel/runtime": "^7.12.5", "@testing-library/dom": "^8.0.0", - "@types/react-dom": "^17.0.25" + "@types/react-dom": "<18.0.0" } }, "@testing-library/react-hooks": { @@ -65084,8 +65084,8 @@ "integrity": "sha512-dYxpz8u9m4q1TuzfcUApqi8iFfR6R0FaMbr2hjZJy1uC8z+bO/K4v8Gs9eogGKYQop7QsrBTFkv/BCF7MzD2Cg==", "requires": { "@babel/runtime": "^7.12.5", - "@types/react": "^17.0.83", - "@types/react-dom": "^17.0.25", + "@types/react": ">=16.9.0", + "@types/react-dom": ">=16.9.0", "@types/react-test-renderer": ">=16.9.0", "react-error-boundary": "^3.1.0" } @@ -65367,7 +65367,7 @@ "dev": true, "requires": { "@types/cheerio": "*", - "@types/react": "^17.0.83" + "@types/react": "^16" } }, "@types/estree": { @@ -65448,7 +65448,7 @@ "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", "requires": { - "@types/react": "^17.0.83", + "@types/react": "*", "hoist-non-react-statics": "^3.3.0" } }, @@ -65650,7 +65650,7 @@ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.25.tgz", "integrity": "sha512-urx7A7UxkZQmThYA4So0NelOVjx3V4rNFVJwp0WZlbIK5eM4rNJDiN3R/E9ix0MBh6kAEojk/9YL+Te6D9zHNA==", "requires": { - "@types/react": "^17.0.83" + "@types/react": "^17" } }, "@types/react-is": { @@ -65658,7 +65658,7 @@ "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-zts4lhQn5ia0cF/y2+3V6Riu0MAfez9/LJYavdM8TvcVl+S91A/7VWxyBT8hbRuWspmuCaiGI0F41OJYGrKhRA==", "requires": { - "@types/react": "^17.0.83" + "@types/react": "^18" } }, "@types/react-test-renderer": { @@ -65666,7 +65666,7 @@ "resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-17.0.1.tgz", "integrity": "sha512-3Fi2O6Zzq/f3QR9dRnlnHso9bMl7weKCviFmfF6B4LS1Uat6Hkm15k0ZAQuDz+UBq6B3+g+NM6IT2nr5QgPzCw==", "requires": { - "@types/react": "^17.0.83" + "@types/react": "*" } }, "@types/react-transition-group": { @@ -65681,7 +65681,7 @@ "integrity": "sha512-GH8sAnBEM5GV9LTeiz56r4ZhMOUSrP43tAQNSRVxNexDjcNKLCEtnxusAItg1owFUFE6k0NslV26gqVClVvong==", "dev": true, "requires": { - "@types/react": "^17.0.83" + "@types/react": "*" } }, "@types/react-window": { @@ -65690,7 +65690,7 @@ "integrity": "sha512-V9q3CvhC9Jk9bWBOysPGaWy/Z0lxYcTXLtLipkt2cnRj1JOSFNF7wqGpkScSXMgBwC+fnVRg/7shwgddBG5ICw==", "dev": true, "requires": { - "@types/react": "^17.0.83" + "@types/react": "*" } }, "@types/reflux": { @@ -65699,7 +65699,7 @@ "integrity": "sha512-nRTTsQmy0prliP0I0GvpAbE27k7+I+MqD15gs4YuQGkuZjRHK65QHPLkykgHnPTdjZYNaY0sOvMQ7OtbcoDkKA==", "dev": true, "requires": { - "@types/react": "^17.0.83" + "@types/react": "*" } }, "@types/relateurl": { @@ -72088,7 +72088,7 @@ "integrity": "sha512-Dw8/Gs4vRjxY6/6i9wU0V+utmQO9kvh9XLnz3LIudviOnVYDEe2ec+0k+NQoMamn1VrjKgCUOWj5jG/5M5M0Qw==", "requires": { "array.prototype.flat": "^1.2.3", - "cheerio": "1.0.0-rc.10", + "cheerio": "^1.0.0-rc.3", "enzyme-shallow-equal": "^1.0.1", "function.prototype.name": "^1.1.2", "has": "^1.0.3", diff --git a/packages/collection-model/index.d.ts b/packages/collection-model/index.d.ts index f5e2b866268..6b5c7ffe43f 100644 --- a/packages/collection-model/index.d.ts +++ b/packages/collection-model/index.d.ts @@ -83,7 +83,7 @@ interface CollectionProps { sourceName: string | null; source: Collection; properties: { id: string; options?: Record }[]; - is_non_existent: boolean; + inferred_from_privileges: boolean; } type CollectionDataService = Pick< diff --git a/packages/collection-model/lib/model.js b/packages/collection-model/lib/model.js index 262102ca2a5..703ef095247 100644 --- a/packages/collection-model/lib/model.js +++ b/packages/collection-model/lib/model.js @@ -102,7 +102,7 @@ function pickCollectionInfo({ validation, clustered, fle2, - is_non_existent, + inferred_from_privileges, }) { return { type, @@ -113,7 +113,7 @@ function pickCollectionInfo({ validation, clustered, fle2, - is_non_existent, + inferred_from_privileges, }; } @@ -134,8 +134,8 @@ const CollectionModel = AmpersandModel.extend(debounceActions(['fetch']), { status: { type: 'string', default: 'initial' }, statusError: { type: 'string', default: null }, - // Normalized values from collectionInfo command - is_non_existent: 'boolean', + // Normalized values from collectionInfo method + inferred_from_privileges: 'boolean', readonly: 'boolean', clustered: 'boolean', fle2: 'boolean', @@ -269,7 +269,7 @@ const CollectionModel = AmpersandModel.extend(debounceActions(['fetch']), { const shouldFetchDbAndCollStats = getParentByType( this, 'Instance' - ).shouldFetchDbAndCollStats; + ).shouldFetchDbAndCollStats(); try { const newStatus = this.status === 'initial' ? 'fetching' : 'refreshing'; @@ -286,14 +286,14 @@ const CollectionModel = AmpersandModel.extend(debounceActions(['fetch']), { ...collStats, ...(collectionInfo && pickCollectionInfo(collectionInfo)), }); - // If the collection is not unprovisioned `is_non_existent` anymore, - // let's update the parent database model to reflect the change. - // This happens when a user tries to insert first document into a - // collection that doesn't exist yet or creates a new collection - // for an unprovisioned database. - if (!this.is_non_existent) { + // If the collection is not `inferred_from_privileges` anymore, let's + // update the parent database model to reflect the change. This happens + // when a user tries to insert first document into a collection that + // doesn't exist yet or creates a new collection for an unprovisioned + // database. + if (!this.inferred_from_privileges) { getParentByType(this, 'Database').set({ - is_non_existent: false, + inferred_from_privileges: false, }); } } catch (err) { @@ -385,6 +385,11 @@ const CollectionCollection = AmpersandCollection.extend( async fetch({ dataService }) { const databaseName = getParentByType(this, 'Database')?.getId(); + const shouldFetchNamespacesFromPrivileges = getParentByType( + this, + 'Instance' + ).shouldFetchNamespacesFromPrivileges(); + if (!databaseName) { throw new Error( `Trying to fetch ${this.modelType} that doesn't have the Database parent model` @@ -405,6 +410,7 @@ const CollectionCollection = AmpersandCollection.extend( { // Always fetch collections with info nameOnly: false, + fetchNamespacesFromPrivileges: shouldFetchNamespacesFromPrivileges, privileges: instanceModel.auth.privileges, } ); diff --git a/packages/compass-app-stores/src/stores/instance-store.ts b/packages/compass-app-stores/src/stores/instance-store.ts index e89aa50ad42..a2ff56123d6 100644 --- a/packages/compass-app-stores/src/stores/instance-store.ts +++ b/packages/compass-app-stores/src/stores/instance-store.ts @@ -286,6 +286,15 @@ export function createInstancesStore( } ); + preferences.onPreferenceValueChanged('inferNamespacesFromPrivileges', () => { + const connectedConnectionIds = Array.from( + instancesManager.listMongoDBInstances().keys() + ); + for (const connectionId of connectedConnectionIds) { + void refreshDatabases({ connectionId }); + } + }); + on(connections, 'disconnected', function (connectionInfoId: string) { try { const instance = diff --git a/packages/compass-collection/src/plugin-tab-title.tsx b/packages/compass-collection/src/plugin-tab-title.tsx index f12bf03406a..a625a2862b3 100644 --- a/packages/compass-collection/src/plugin-tab-title.tsx +++ b/packages/compass-collection/src/plugin-tab-title.tsx @@ -24,7 +24,7 @@ type PluginTitleProps = { function PluginTitle({ editViewName, - isNonExistent, + inferredFromPrivileges, isReadonly, isTimeSeries, sourceName, @@ -68,12 +68,12 @@ function PluginTitle({ ? 'Visibility' : collectionType === 'timeseries' ? 'TimeSeries' - : isNonExistent + : inferredFromPrivileges ? 'EmptyFolder' : 'Folder' } data-namespace={ns} - isNonExistent={isNonExistent} + inferredFromPrivileges={inferredFromPrivileges} /> ); } diff --git a/packages/compass-components/src/components/workspace-tabs/tab.tsx b/packages/compass-components/src/components/workspace-tabs/tab.tsx index 29519be412b..a24ed4c6f7b 100644 --- a/packages/compass-components/src/components/workspace-tabs/tab.tsx +++ b/packages/compass-components/src/components/workspace-tabs/tab.tsx @@ -138,7 +138,7 @@ const draggingTabStyles = css({ cursor: 'grabbing !important', }); -const nonExistentStyles = css({ +const inferredFromPrivilegesStyles = css({ color: palette.gray.base, }); @@ -184,7 +184,7 @@ export type WorkspaceTabPluginProps = { connectionName?: string; type: string; title: React.ReactNode; - isNonExistent?: boolean; + inferredFromPrivileges?: boolean; iconGlyph: GlyphName | 'Logo' | 'Server'; tooltip?: [string, string][]; }; @@ -206,7 +206,7 @@ function Tab({ type, title, tooltip, - isNonExistent, + inferredFromPrivileges, isSelected, isDragging, onSelect, @@ -271,7 +271,7 @@ function Tab({ className={cx( tabStyles, themeClass, - isNonExistent && nonExistentStyles, + inferredFromPrivileges && inferredFromPrivilegesStyles, isSelected && selectedTabStyles, isSelected && tabTheme && selectedThemedTabStyles, isDragging && draggingTabStyles, diff --git a/packages/compass-connections-navigation/src/connections-navigation-tree.spec.tsx b/packages/compass-connections-navigation/src/connections-navigation-tree.spec.tsx index 7cb214b3d7a..4a53f839043 100644 --- a/packages/compass-connections-navigation/src/connections-navigation-tree.spec.tsx +++ b/packages/compass-connections-navigation/src/connections-navigation-tree.spec.tsx @@ -43,7 +43,7 @@ const connections: Connection[] = [ collectionsStatus: 'initial', collectionsLength: 5, collections: [], - isNonExistent: false, + inferredFromPrivileges: false, }, { _id: 'db_ready', @@ -57,7 +57,7 @@ const connections: Connection[] = [ type: 'collection', sourceName: '', pipeline: [], - isNonExistent: false, + inferredFromPrivileges: false, }, { _id: 'db_ready.woof', @@ -65,7 +65,7 @@ const connections: Connection[] = [ type: 'timeseries', sourceName: '', pipeline: [], - isNonExistent: false, + inferredFromPrivileges: false, }, { _id: 'db_ready.bwok', @@ -73,10 +73,10 @@ const connections: Connection[] = [ type: 'view', sourceName: '', pipeline: [], - isNonExistent: false, + inferredFromPrivileges: false, }, ], - isNonExistent: false, + inferredFromPrivileges: false, }, ], isReady: true, diff --git a/packages/compass-connections-navigation/src/navigation-item-icon.tsx b/packages/compass-connections-navigation/src/navigation-item-icon.tsx index 1dd55b8b34b..4080b3ecc92 100644 --- a/packages/compass-connections-navigation/src/navigation-item-icon.tsx +++ b/packages/compass-connections-navigation/src/navigation-item-icon.tsx @@ -5,8 +5,8 @@ import type { GlyphName } from '@mongodb-js/compass-components'; import { WithStatusMarker } from './with-status-marker'; import { isLocalhost } from 'mongodb-build-info'; -const NON_EXISTANT_NAMESPACE_TEXT = - 'Your privileges grant you access to this namespace, but it does not currently exist'; +const INFERRED_FROM_PRIVILEGES_TEXT = + 'Your privileges grant you access to this namespace, but it might not currently exist'; const tooltipTriggerStyles = css({ display: 'flex', @@ -35,10 +35,10 @@ const IconWithTooltip = ({ export const NavigationItemIcon = ({ item }: { item: SidebarTreeItem }) => { if (item.type === 'database') { - if (item.isNonExistent) { + if (item.inferredFromPrivileges) { return ( ); @@ -46,10 +46,10 @@ export const NavigationItemIcon = ({ item }: { item: SidebarTreeItem }) => { return ; } if (item.type === 'collection') { - if (item.isNonExistent) { + if (item.inferredFromPrivileges) { return ( ); diff --git a/packages/compass-connections-navigation/src/styled-navigation-item.tsx b/packages/compass-connections-navigation/src/styled-navigation-item.tsx index 0fdc7912110..8c9043c50e0 100644 --- a/packages/compass-connections-navigation/src/styled-navigation-item.tsx +++ b/packages/compass-connections-navigation/src/styled-navigation-item.tsx @@ -41,9 +41,9 @@ export default function StyledNavigationItem({ !showDisabledConnections || getConnectable(connectionId); const isDisconnectedConnection = item.type === 'connection' && item.connectionStatus !== 'connected'; - const isNonExistentNamespace = + const inferredFromPrivilegesNamespace = (item.type === 'database' || item.type === 'collection') && - item.isNonExistent; + item.inferredFromPrivileges; if (colorCode && colorCode !== DefaultColorCode) { style['--item-bg-color'] = connectionColorToHex(colorCode); @@ -51,12 +51,16 @@ export default function StyledNavigationItem({ style['--item-bg-color-active'] = connectionColorToHexActive(colorCode); } - if (isDisconnectedConnection || isNonExistentNamespace || !isConnectable) { + if ( + isDisconnectedConnection || + inferredFromPrivilegesNamespace || + !isConnectable + ) { style['--item-color'] = inactiveColor; } // We always show these as inactive - if (isNonExistentNamespace || !isConnectable) { + if (inferredFromPrivilegesNamespace || !isConnectable) { style['--item-color-active'] = inactiveColor; } return style; diff --git a/packages/compass-connections-navigation/src/tree-data.ts b/packages/compass-connections-navigation/src/tree-data.ts index 21575a219db..f8a2a65cc5a 100644 --- a/packages/compass-connections-navigation/src/tree-data.ts +++ b/packages/compass-connections-navigation/src/tree-data.ts @@ -54,7 +54,7 @@ export type Database = { collectionsStatus: DatabaseOrCollectionStatus; collectionsLength: number; collections: Collection[]; - isNonExistent: boolean; + inferredFromPrivileges: boolean; }; type PlaceholderTreeItem = VirtualPlaceholderItem & { @@ -68,7 +68,7 @@ export type Collection = { type: 'view' | 'collection' | 'timeseries'; sourceName: string | null; pipeline: unknown[]; - isNonExistent: boolean; + inferredFromPrivileges: boolean; }; export type NotConnectedConnectionTreeItem = VirtualTreeItem & { @@ -103,7 +103,7 @@ export type DatabaseTreeItem = VirtualTreeItem & { connectionItem: ConnectedConnectionTreeItem; dbName: string; hasWriteActionsDisabled: boolean; - isNonExistent: boolean; + inferredFromPrivileges: boolean; }; export type CollectionTreeItem = VirtualTreeItem & { @@ -115,7 +115,7 @@ export type CollectionTreeItem = VirtualTreeItem & { databaseItem: DatabaseTreeItem; namespace: string; hasWriteActionsDisabled: boolean; - isNonExistent: boolean; + inferredFromPrivileges: boolean; }; export type SidebarActionableItem = @@ -262,7 +262,7 @@ const databaseToItems = ({ collections, collectionsLength, collectionsStatus, - isNonExistent, + inferredFromPrivileges, }, connectionId, connectionItem, @@ -298,7 +298,7 @@ const databaseToItems = ({ dbName: id, isExpandable: true, hasWriteActionsDisabled, - isNonExistent, + inferredFromPrivileges, }; const sidebarData: SidebarTreeItem[] = [databaseTI]; @@ -327,7 +327,7 @@ const databaseToItems = ({ return sidebarData.concat( collections.map( - ({ _id: id, name, type, isNonExistent }, collectionIndex) => ({ + ({ _id: id, name, type, inferredFromPrivileges }, collectionIndex) => ({ id: `${connectionId}.${id}`, // id is the namespace of the collection, so includes db as well level: level + 1, name, @@ -340,7 +340,7 @@ const databaseToItems = ({ namespace: id, hasWriteActionsDisabled, isExpandable: false, - isNonExistent, + inferredFromPrivileges, }) ) ); diff --git a/packages/compass-preferences-model/src/preferences-schema.tsx b/packages/compass-preferences-model/src/preferences-schema.tsx index 93b5a6540d0..99c6d135a0b 100644 --- a/packages/compass-preferences-model/src/preferences-schema.tsx +++ b/packages/compass-preferences-model/src/preferences-schema.tsx @@ -101,6 +101,7 @@ export type UserConfigurablePreferences = PermanentFeatureFlags & enableCreatingNewConnections: boolean; enableProxySupport: boolean; proxy: string; + inferNamespacesFromPrivileges?: boolean; }; /** @@ -1012,6 +1013,18 @@ export const storedUserPreferencesProps: Required<{ type: 'boolean', }, + inferNamespacesFromPrivileges: { + ui: true, + cli: true, + global: true, + description: { + short: 'Infer additional namespaces from privileges', + long: "Show databases and collections implied by your roles and privileges, in addition to those returned by listDatabases and listCollections. This may include namespaces that don't exist yet.", + }, + validator: z.boolean().default(true), + type: 'boolean', + }, + ...allFeatureFlagsProps, }; diff --git a/packages/compass-settings/src/components/settings/general.tsx b/packages/compass-settings/src/components/settings/general.tsx index 92516f7e073..5b9375c1c35 100644 --- a/packages/compass-settings/src/components/settings/general.tsx +++ b/packages/compass-settings/src/components/settings/general.tsx @@ -14,6 +14,7 @@ const generalFields = [ : []), 'enableShowDialogOnQuit', 'enableDbAndCollStats', + 'inferNamespacesFromPrivileges', ] as const; export const GeneralSettings: React.FunctionComponent = () => { diff --git a/packages/compass-sidebar/src/components/use-filtered-connections.spec.ts b/packages/compass-sidebar/src/components/use-filtered-connections.spec.ts index 0c171690dd5..854f5229066 100644 --- a/packages/compass-sidebar/src/components/use-filtered-connections.spec.ts +++ b/packages/compass-sidebar/src/components/use-filtered-connections.spec.ts @@ -51,7 +51,7 @@ const sidebarConnections: SidebarConnection[] = [ type: 'collection', sourceName: '', pipeline: [], - isNonExistent: false, + inferredFromPrivileges: false, }, { _id: 'coll_ready_1_1_2', @@ -59,12 +59,12 @@ const sidebarConnections: SidebarConnection[] = [ type: 'collection', sourceName: '', pipeline: [], - isNonExistent: false, + inferredFromPrivileges: false, }, ], collectionsLength: 1, collectionsStatus: 'ready', - isNonExistent: false, + inferredFromPrivileges: false, }, { _id: 'db_ready_1_2', @@ -76,7 +76,7 @@ const sidebarConnections: SidebarConnection[] = [ type: 'collection', sourceName: '', pipeline: [], - isNonExistent: false, + inferredFromPrivileges: false, }, { _id: 'coll_ready_1_2_2', @@ -84,12 +84,12 @@ const sidebarConnections: SidebarConnection[] = [ type: 'collection', sourceName: '', pipeline: [], - isNonExistent: false, + inferredFromPrivileges: false, }, ], collectionsLength: 1, collectionsStatus: 'ready', - isNonExistent: false, + inferredFromPrivileges: false, }, ], databasesStatus: 'ready', @@ -116,7 +116,7 @@ const sidebarConnections: SidebarConnection[] = [ type: 'collection', sourceName: '', pipeline: [], - isNonExistent: false, + inferredFromPrivileges: false, }, { _id: 'coll_ready_2_2', @@ -124,12 +124,12 @@ const sidebarConnections: SidebarConnection[] = [ type: 'collection', sourceName: '', pipeline: [], - isNonExistent: false, + inferredFromPrivileges: false, }, ], collectionsLength: 1, collectionsStatus: 'ready', - isNonExistent: false, + inferredFromPrivileges: false, }, ], databasesStatus: 'ready', diff --git a/packages/compass-sidebar/src/modules/databases.spec.ts b/packages/compass-sidebar/src/modules/databases.spec.ts index 7f351ce6325..67e521bb09a 100644 --- a/packages/compass-sidebar/src/modules/databases.spec.ts +++ b/packages/compass-sidebar/src/modules/databases.spec.ts @@ -17,12 +17,12 @@ async function createDatabases(dbs: any[] = []) { }; } ); - return data.map(({ is_non_existent, collections, ...rest }) => ({ + return data.map(({ inferred_from_privileges, collections, ...rest }) => ({ ...rest, - isNonExistent: is_non_existent, - collections: collections.map(({ is_non_existent, ...coll }) => ({ + inferredFromPrivileges: inferred_from_privileges, + collections: collections.map(({ inferred_from_privileges, ...coll }) => ({ ...coll, - isNonExistent: is_non_existent, + inferredFromPrivileges: inferred_from_privileges, })), })); } diff --git a/packages/compass-sidebar/src/modules/databases.ts b/packages/compass-sidebar/src/modules/databases.ts index e879bec1859..aaa4d08fdd5 100644 --- a/packages/compass-sidebar/src/modules/databases.ts +++ b/packages/compass-sidebar/src/modules/databases.ts @@ -45,13 +45,13 @@ export type Database = Pick< InstanceDatabase, '_id' | 'name' | 'collectionsStatus' | 'collectionsLength' > & { - isNonExistent: boolean; + inferredFromPrivileges: boolean; collections: Array< Pick< InstanceDatabase['collections'][number], '_id' | 'name' | 'type' | 'sourceName' | 'pipeline' > & { - isNonExistent: boolean; + inferredFromPrivileges: boolean; } >; }; diff --git a/packages/compass-sidebar/src/modules/instance.ts b/packages/compass-sidebar/src/modules/instance.ts index b9c81eafbdc..d714c50ae57 100644 --- a/packages/compass-sidebar/src/modules/instance.ts +++ b/packages/compass-sidebar/src/modules/instance.ts @@ -132,7 +132,7 @@ export const setupInstance = name: db.name, collectionsStatus: db.collectionsStatus, collectionsLength: db.collectionsLength, - isNonExistent: db.is_non_existent, + inferredFromPrivileges: db.inferred_from_privileges, }; } @@ -143,7 +143,7 @@ export const setupInstance = type: coll.type, sourceName: coll.sourceName, pipeline: coll.pipeline, - isNonExistent: coll.is_non_existent, + inferredFromPrivileges: coll.inferred_from_privileges, }; } diff --git a/packages/compass-sidebar/test/helpers.ts b/packages/compass-sidebar/test/helpers.ts index 7219d48af7d..67d2749cf8c 100644 --- a/packages/compass-sidebar/test/helpers.ts +++ b/packages/compass-sidebar/test/helpers.ts @@ -18,11 +18,11 @@ export function createInstance( databases: dbs.map((db) => { return { _id: db._id, - is_non_existent: false, + inferred_from_privileges: false, collections: (db.collections || []).map((coll) => { return { _id: `${db._id}.${coll}`, - is_non_existent: false, + inferred_from_privileges: false, }; }), }; diff --git a/packages/compass-workspaces/src/components/workspaces.tsx b/packages/compass-workspaces/src/components/workspaces.tsx index 16d5c794c3d..3ec7dcc56b7 100644 --- a/packages/compass-workspaces/src/components/workspaces.tsx +++ b/packages/compass-workspaces/src/components/workspaces.tsx @@ -122,21 +122,21 @@ const CompassWorkspaces: React.FunctionComponent = ({ const { content: WorkspaceTabContent, header: WorkspaceTabTitle } = plugin; - let isNonExistent: boolean | undefined; + let inferredFromPrivileges: boolean | undefined; if (tab.type === 'Collections') { - // TODO(COMPASS-9456): Move this logic and `isNonExistent` setting to the plugin. + // TODO(COMPASS-9456): Move this logic and `inferredFromPrivileges` setting to the plugin. const database = tab.namespace; const namespaceId = `${tab.connectionId}.${database}`; - const { isNonExistent: databaseDoesNotExist } = + const { inferredFromPrivileges: databaseDoesNotExist } = databaseInfo[namespaceId] ?? {}; - isNonExistent = databaseDoesNotExist; + inferredFromPrivileges = databaseDoesNotExist; } else if (tab.type === 'Collection') { - // TODO(COMPASS-9456): Move this logic and `isNonExistent` setting to the plugin. + // TODO(COMPASS-9456): Move this logic and `inferredFromPrivileges` setting to the plugin. const { ns } = toNS(tab.namespace); const namespaceId = `${tab.connectionId}.${ns}`; - const { isNonExistent: collectionDoesNotExist } = + const { inferredFromPrivileges: collectionDoesNotExist } = collectionInfo[namespaceId] ?? {}; - isNonExistent = collectionDoesNotExist; + inferredFromPrivileges = collectionDoesNotExist; } return { @@ -156,7 +156,7 @@ const CompassWorkspaces: React.FunctionComponent = ({ diff --git a/packages/compass-workspaces/src/index.spec.tsx b/packages/compass-workspaces/src/index.spec.tsx index 7bc542d8fc7..7ae34918358 100644 --- a/packages/compass-workspaces/src/index.spec.tsx +++ b/packages/compass-workspaces/src/index.spec.tsx @@ -98,7 +98,7 @@ describe('WorkspacesPlugin', function () { { _id: 'db', name: 'db', - is_non_existent: false, + inferred_from_privileges: false, collection_count: 0, document_count: 0, index_count: 0, diff --git a/packages/compass-workspaces/src/index.ts b/packages/compass-workspaces/src/index.ts index e87b816e759..b0126401b9e 100644 --- a/packages/compass-workspaces/src/index.ts +++ b/packages/compass-workspaces/src/index.ts @@ -104,24 +104,28 @@ export function activateWorkspacePlugin( store.dispatch(collectionRenamed(from, collection.ns)); }); - on(instance, 'change:databases.is_non_existent', (database: Database) => { - const namespaceId = `${connectionId}.${database._id}`; - const databaseInfo = { - isNonExistent: database.is_non_existent, - }; - store.dispatch(updateDatabaseInfo(namespaceId, databaseInfo)); - }); + on( + instance, + 'change:databases.inferred_from_privileges', + (database: Database) => { + const namespaceId = `${connectionId}.${database._id}`; + const databaseInfo = { + inferredFromPrivileges: database.inferred_from_privileges, + }; + store.dispatch(updateDatabaseInfo(namespaceId, databaseInfo)); + } + ); on( instance, - 'change:collections.is_non_existent', + 'change:collections.inferred_from_privileges', (collection: Collection) => { const namespaceId = `${connectionId}.${collection._id}`; const collectionInfo = { isTimeSeries: collection.isTimeSeries, isReadonly: collection.readonly ?? collection.isView, sourceName: collection.sourceName, - isNonExistent: collection.is_non_existent, + inferredFromPrivileges: collection.inferred_from_privileges, }; store.dispatch(updateCollectionInfo(namespaceId, collectionInfo)); } diff --git a/packages/compass-workspaces/src/stores/workspaces.ts b/packages/compass-workspaces/src/stores/workspaces.ts index 06444ee81fe..3219cb8ed43 100644 --- a/packages/compass-workspaces/src/stores/workspaces.ts +++ b/packages/compass-workspaces/src/stores/workspaces.ts @@ -77,11 +77,11 @@ export type CollectionTabInfo = { isTimeSeries: boolean; isReadonly: boolean; sourceName?: string | null; - isNonExistent: boolean; + inferredFromPrivileges: boolean; }; export type DatabaseTabInfo = { - isNonExistent: boolean; + inferredFromPrivileges: boolean; }; export type WorkspacesState = { @@ -725,7 +725,7 @@ const fetchCollectionInfo = ( isTimeSeries: coll.isTimeSeries, isReadonly: coll.readonly ?? coll.isView, sourceName: coll.sourceName, - isNonExistent: coll.is_non_existent, + inferredFromPrivileges: coll.inferred_from_privileges, }; dispatch(updateCollectionInfo(namespaceId, info)); } @@ -777,7 +777,7 @@ const fetchDatabaseInfo = ( if (db) { await db.fetch({ dataService }); const info = { - isNonExistent: db.is_non_existent, + inferredFromPrivileges: db.inferred_from_privileges, }; dispatch(updateDatabaseInfo(namespaceId, info)); } diff --git a/packages/compass-workspaces/src/types.ts b/packages/compass-workspaces/src/types.ts index 2cbbc9b6458..4c9c5ebaf62 100644 --- a/packages/compass-workspaces/src/types.ts +++ b/packages/compass-workspaces/src/types.ts @@ -42,8 +42,8 @@ export type CollectionsWorkspace = { type: 'Collections'; connectionId: string; namespace: string; - // TODO(COMPASS-9456): Remove the `isNonExistent` field here. - isNonExistent?: boolean; + // TODO(COMPASS-9456): Remove the `inferredFromPrivileges` field here. + inferredFromPrivileges?: boolean; }; export type CollectionWorkspace = { @@ -61,8 +61,8 @@ export type CollectionWorkspace = { initialPipelineText?: string; initialAggregation?: unknown; editViewName?: string; - // TODO(COMPASS-9456): Remove the `isNonExistent` field here. - isNonExistent?: boolean; + // TODO(COMPASS-9456): Remove the `inferredFromPrivileges` field here. + inferredFromPrivileges?: boolean; }; export type WorkspaceTabProps = diff --git a/packages/data-service/src/data-service.spec.ts b/packages/data-service/src/data-service.spec.ts index c18b9903795..581c0ff7a89 100644 --- a/packages/data-service/src/data-service.spec.ts +++ b/packages/data-service/src/data-service.spec.ts @@ -704,13 +704,29 @@ describe('DataService', function () { it('returns collections from user privileges', async function () { const collections = await dataService.listCollections('imdb'); const mappedCollections = collections.map( - ({ _id, name, is_non_existent }) => ({ _id, name, is_non_existent }) + ({ _id, name, inferred_from_privileges }) => ({ + _id, + name, + inferred_from_privileges, + }) ); const expectedCollections = [ - { _id: 'imdb.movies', name: 'movies', is_non_existent: true }, - { _id: 'imdb.reviews', name: 'reviews', is_non_existent: true }, - { _id: 'imdb.users', name: 'users', is_non_existent: true }, + { + _id: 'imdb.movies', + name: 'movies', + inferred_from_privileges: true, + }, + { + _id: 'imdb.reviews', + name: 'reviews', + inferred_from_privileges: true, + }, + { + _id: 'imdb.users', + name: 'users', + inferred_from_privileges: true, + }, ]; expect(mappedCollections).to.deep.include.members( expectedCollections @@ -721,13 +737,29 @@ describe('DataService', function () { await dataService.createCollection('imdb.movies', {}); const collections = await dataService.listCollections('imdb'); const mappedCollections = collections.map( - ({ _id, name, is_non_existent }) => ({ _id, name, is_non_existent }) + ({ _id, name, inferred_from_privileges }) => ({ + _id, + name, + inferred_from_privileges, + }) ); const expectedCollections = [ - { _id: 'imdb.movies', name: 'movies', is_non_existent: false }, - { _id: 'imdb.reviews', name: 'reviews', is_non_existent: true }, - { _id: 'imdb.users', name: 'users', is_non_existent: true }, + { + _id: 'imdb.movies', + name: 'movies', + inferred_from_privileges: false, + }, + { + _id: 'imdb.reviews', + name: 'reviews', + inferred_from_privileges: true, + }, + { + _id: 'imdb.users', + name: 'users', + inferred_from_privileges: true, + }, ]; expect(mappedCollections).to.deep.include.members( expectedCollections @@ -942,7 +974,11 @@ describe('DataService', function () { it('returns databases from user roles and privileges', async function () { const databases = await dataService.listDatabases(); const mappedDatabases = databases.map( - ({ _id, name, is_non_existent }) => ({ _id, name, is_non_existent }) + ({ _id, name, inferred_from_privileges }) => ({ + _id, + name, + inferred_from_privileges, + }) ); const expectedDatabases = [ @@ -950,11 +986,15 @@ describe('DataService', function () { { _id: 'sample_airbnb', name: 'sample_airbnb', - is_non_existent: true, + inferred_from_privileges: true, + }, + { + _id: 'sample_wiki', + name: 'sample_wiki', + inferred_from_privileges: true, }, - { _id: 'sample_wiki', name: 'sample_wiki', is_non_existent: true }, // Based on privileges - { _id: 'imdb', name: 'imdb', is_non_existent: true }, + { _id: 'imdb', name: 'imdb', inferred_from_privileges: true }, ]; expect(mappedDatabases).to.deep.include.members(expectedDatabases); }); @@ -964,17 +1004,25 @@ describe('DataService', function () { await dataService.createCollection('sample_airbnb.whatever', {}); const databases = await dataService.listDatabases(); const mappedDatabases = databases.map( - ({ _id, name, is_non_existent }) => ({ _id, name, is_non_existent }) + ({ _id, name, inferred_from_privileges }) => ({ + _id, + name, + inferred_from_privileges, + }) ); const expectedDatabases = [ { _id: 'sample_airbnb', name: 'sample_airbnb', - is_non_existent: false, + inferred_from_privileges: false, + }, + { + _id: 'sample_wiki', + name: 'sample_wiki', + inferred_from_privileges: true, }, - { _id: 'sample_wiki', name: 'sample_wiki', is_non_existent: true }, - { _id: 'imdb', name: 'imdb', is_non_existent: false }, + { _id: 'imdb', name: 'imdb', inferred_from_privileges: false }, ]; expect(mappedDatabases).to.deep.include.members(expectedDatabases); }); @@ -2102,13 +2150,16 @@ describe('DataService', function () { }, }); const dbs = (await dataService.listDatabases()).map( - ({ name, is_non_existent }) => ({ name, is_non_existent }) + ({ name, inferred_from_privileges }) => ({ + name, + inferred_from_privileges, + }) ); expect(dbs).to.deep.eq([ - { name: 'pineapple', is_non_existent: true }, - { name: 'foo', is_non_existent: false }, - { name: 'buz', is_non_existent: true }, - { name: 'bar', is_non_existent: false }, + { name: 'pineapple', inferred_from_privileges: true }, + { name: 'foo', inferred_from_privileges: false }, + { name: 'buz', inferred_from_privileges: true }, + { name: 'bar', inferred_from_privileges: false }, ]); }); @@ -2129,9 +2180,14 @@ describe('DataService', function () { }, }); const dbs = (await dataService.listDatabases()).map( - ({ name, is_non_existent }) => ({ name, is_non_existent }) + ({ name, inferred_from_privileges }) => ({ + name, + inferred_from_privileges, + }) ); - expect(dbs).to.deep.eq([{ name: 'foo', is_non_existent: true }]); + expect(dbs).to.deep.eq([ + { name: 'foo', inferred_from_privileges: true }, + ]); }); }); @@ -2224,13 +2280,16 @@ describe('DataService', function () { }, }); const colls = (await dataService.listCollections('foo')).map( - ({ name, is_non_existent }) => ({ name, is_non_existent }) + ({ name, inferred_from_privileges }) => ({ + name, + inferred_from_privileges, + }) ); expect(colls).to.deep.eq([ - { name: 'bar', is_non_existent: true }, - { name: 'buz', is_non_existent: false }, - { name: 'bla', is_non_existent: false }, - { name: 'meow', is_non_existent: false }, + { name: 'bar', inferred_from_privileges: true }, + { name: 'buz', inferred_from_privileges: false }, + { name: 'bla', inferred_from_privileges: false }, + { name: 'meow', inferred_from_privileges: false }, ]); }); @@ -2253,9 +2312,14 @@ describe('DataService', function () { }, }); const colls = (await dataService.listCollections('foo')).map( - ({ name, is_non_existent }) => ({ name, is_non_existent }) + ({ name, inferred_from_privileges }) => ({ + name, + inferred_from_privileges, + }) ); - expect(colls).to.deep.eq([{ name: 'bar', is_non_existent: true }]); + expect(colls).to.deep.eq([ + { name: 'bar', inferred_from_privileges: true }, + ]); }); }); diff --git a/packages/data-service/src/data-service.ts b/packages/data-service/src/data-service.ts index 98505e7440f..b0d9e87ad73 100644 --- a/packages/data-service/src/data-service.ts +++ b/packages/data-service/src/data-service.ts @@ -351,6 +351,7 @@ export interface DataService { filter?: Document, options?: { nameOnly?: true; + fetchNamespacesFromPrivileges?: boolean; privileges?: | ConnectionStatusWithPrivileges['authInfo']['authenticatedUserPrivileges'] | null; @@ -459,6 +460,7 @@ export interface DataService { */ listDatabases(options?: { nameOnly?: true; + fetchNamespacesFromPrivileges?: boolean; privileges?: | ConnectionStatusWithPrivileges['authInfo']['authenticatedUserPrivileges'] | null; @@ -1368,9 +1370,11 @@ class DataServiceImpl extends WithLogContext implements DataService { filter: Document = {}, { nameOnly, + fetchNamespacesFromPrivileges = true, privileges = null, }: { nameOnly?: true; + fetchNamespacesFromPrivileges?: boolean; privileges?: | ConnectionStatusWithPrivileges['authInfo']['authenticatedUserPrivileges'] | null; @@ -1381,11 +1385,14 @@ class DataServiceImpl extends WithLogContext implements DataService { nameOnly, }); return colls.map((coll) => ({ - is_non_existent: false, + inferred_from_privileges: false, ...coll, })); }; const getCollectionsFromPrivileges = async () => { + if (!fetchNamespacesFromPrivileges) { + return []; + } const databases = getPrivilegesByDatabaseAndCollection( await this._getPrivilegesOrFallback(privileges), ['find'] @@ -1400,7 +1407,7 @@ class DataServiceImpl extends WithLogContext implements DataService { // those registered as "real" collection names Boolean ) - .map((name) => ({ name, is_non_existent: true })); + .map((name) => ({ name, inferred_from_privileges: true })); }; const [listedCollections, collectionsFromPrivileges] = await Promise.all([ @@ -1418,8 +1425,8 @@ class DataServiceImpl extends WithLogContext implements DataService { // if they were fetched successfully [...collectionsFromPrivileges, ...listedCollections], 'name' - ).map(({ is_non_existent, ...coll }) => ({ - is_non_existent, + ).map(({ inferred_from_privileges, ...coll }) => ({ + inferred_from_privileges, ...adaptCollectionInfo({ db: databaseName, ...coll }), })); @@ -1434,10 +1441,12 @@ class DataServiceImpl extends WithLogContext implements DataService { }) async listDatabases({ nameOnly, + fetchNamespacesFromPrivileges = true, privileges = null, roles = null, }: { nameOnly?: true; + fetchNamespacesFromPrivileges?: boolean; privileges?: | ConnectionStatusWithPrivileges['authInfo']['authenticatedUserPrivileges'] | null; @@ -1467,7 +1476,7 @@ class DataServiceImpl extends WithLogContext implements DataService { ); return databases.map((x) => ({ ...x, - is_non_existent: false, + inferred_from_privileges: false, })); } catch (err) { // Currently Compass should not fail if listDatabase failed for any @@ -1488,6 +1497,10 @@ class DataServiceImpl extends WithLogContext implements DataService { }; const getDatabasesFromPrivileges = async () => { + if (!fetchNamespacesFromPrivileges) { + return []; + } + const databases = getPrivilegesByDatabaseAndCollection( await this._getPrivilegesOrFallback(privileges), ['find'] @@ -1500,7 +1513,7 @@ class DataServiceImpl extends WithLogContext implements DataService { // out Boolean ) - .map((name) => ({ name, is_non_existent: true })); + .map((name) => ({ name, inferred_from_privileges: true })); }; const getDatabasesFromRoles = async () => { @@ -1515,7 +1528,10 @@ class DataServiceImpl extends WithLogContext implements DataService { // have custom privileges that we can't currently fetch. ['read', 'readWrite', 'dbAdmin', 'dbOwner'] ); - return databases.map((name) => ({ name, is_non_existent: true })); + return databases.map((name) => ({ + name, + inferred_from_privileges: true, + })); }; const [listedDatabases, databasesFromPrivileges, databasesFromRoles] = @@ -1530,11 +1546,11 @@ class DataServiceImpl extends WithLogContext implements DataService { // if they were fetched successfully [...databasesFromRoles, ...databasesFromPrivileges, ...listedDatabases], 'name' - ).map(({ name, is_non_existent, ...db }) => { + ).map(({ name, inferred_from_privileges, ...db }) => { return { _id: name, name, - is_non_existent, + inferred_from_privileges, ...adaptDatabaseInfo(db), }; }); diff --git a/packages/data-service/src/instance-detail-helper.ts b/packages/data-service/src/instance-detail-helper.ts index ce84cf7396f..e93188d6592 100644 --- a/packages/data-service/src/instance-detail-helper.ts +++ b/packages/data-service/src/instance-detail-helper.ts @@ -76,7 +76,7 @@ export type CollectionDetails = { validationAction: string; validationLevel: string; } | null; - is_non_existent: boolean; + inferred_from_privileges: boolean; }; export type DatabaseDetails = { @@ -89,7 +89,7 @@ export type DatabaseDetails = { index_count: number; index_size: number; collections: CollectionDetails[]; - is_non_existent: boolean; + inferred_from_privileges: boolean; }; export type InstanceDetails = { @@ -362,7 +362,10 @@ export function adaptBuildInfo( export function adaptDatabaseInfo( databaseStats: Partial & Partial -): Omit { +): Omit< + DatabaseDetails, + '_id' | 'collections' | 'name' | 'inferred_from_privileges' +> { return { collection_count: databaseStats.collections ?? 0, document_count: databaseStats.objects ?? 0, @@ -382,7 +385,7 @@ export function adaptCollectionInfo({ }: CollectionInfoNameOnly & Partial & { db: string; - }): Omit { + }): Omit { const ns = toNS(`${db}.${name}`); const { collection, diff --git a/packages/database-model/index.d.ts b/packages/database-model/index.d.ts index ac5bdf4250b..b244cc8a9ca 100644 --- a/packages/database-model/index.d.ts +++ b/packages/database-model/index.d.ts @@ -16,7 +16,7 @@ interface DatabaseProps { index_size: number | undefined; collectionsLength: number; collections: CollectionCollection; - is_non_existent: boolean; + inferred_from_privileges: boolean; } interface Database extends DatabaseProps { diff --git a/packages/database-model/lib/model.js b/packages/database-model/lib/model.js index cbe596434a1..4c3a1cfd0e8 100644 --- a/packages/database-model/lib/model.js +++ b/packages/database-model/lib/model.js @@ -105,7 +105,7 @@ const DatabaseModel = AmpersandModel.extend( statusError: { type: 'string', default: null }, collectionsStatus: { type: 'string', default: 'initial' }, collectionsStatusError: { type: 'string', default: null }, - is_non_existent: 'boolean', + inferred_from_privileges: 'boolean', collection_count: 'number', document_count: 'number', storage_size: 'number', @@ -142,7 +142,7 @@ const DatabaseModel = AmpersandModel.extend( const shouldFetchDbAndCollStats = getParentByType( this, 'Instance' - ).shouldFetchDbAndCollStats; + ).shouldFetchDbAndCollStats(); if (!shouldFetch(this.status, force)) { return; @@ -251,17 +251,21 @@ const DatabaseCollection = AmpersandCollection.extend( ); } + const shouldFetchNamespacesFromPrivileges = + instanceModel.shouldFetchNamespacesFromPrivileges(); + const dbs = await dataService.listDatabases({ nameOnly: true, + fetchNamespacesFromPrivileges: shouldFetchNamespacesFromPrivileges, privileges: instanceModel.auth.privileges, roles: instanceModel.auth.roles, }); this.set( - dbs.map(({ _id, name, is_non_existent }) => ({ + dbs.map(({ _id, name, inferred_from_privileges }) => ({ _id, name, - is_non_existent, + inferred_from_privileges, })) ); }, diff --git a/packages/databases-collections-list/src/collections.tsx b/packages/databases-collections-list/src/collections.tsx index 50ea1befcbe..019dc76b822 100644 --- a/packages/databases-collections-list/src/collections.tsx +++ b/packages/databases-collections-list/src/collections.tsx @@ -192,7 +192,7 @@ const CollectionsList: React.FunctionComponent<{ name={coll.name} type="collection" status={coll.status} - isNonExistent={coll.is_non_existent} + inferredFromPrivileges={coll.inferred_from_privileges} data={data} badges={badges} onItemClick={onItemClick} diff --git a/packages/databases-collections-list/src/databases.tsx b/packages/databases-collections-list/src/databases.tsx index 40c1b897c74..c7e780727e8 100644 --- a/packages/databases-collections-list/src/databases.tsx +++ b/packages/databases-collections-list/src/databases.tsx @@ -71,7 +71,7 @@ const DatabasesList: React.FunctionComponent<{ type="database" viewType={viewType} status={db.status} - isNonExistent={db.is_non_existent} + inferredFromPrivileges={db.inferred_from_privileges} data={[ { label: 'Storage size', diff --git a/packages/databases-collections-list/src/index.spec.tsx b/packages/databases-collections-list/src/index.spec.tsx index 19bb3f0d898..9ec322bc4c2 100644 --- a/packages/databases-collections-list/src/index.spec.tsx +++ b/packages/databases-collections-list/src/index.spec.tsx @@ -25,7 +25,7 @@ function createDatabase(name) { collectionsStatusError: null, collection_count: 1, collections: [] as any, - is_non_existent: false, + inferred_from_privileges: false, // dbStats document_count: 10, storage_size: 1500, @@ -59,7 +59,7 @@ function createCollection(name, props: any = {}) { is_capped: false, isTimeSeries: false, isView: false, - is_non_existent: false, + inferred_from_privileges: false, /** Only relevant for a view and identifies collection/view from which this view was created. */ sourceName: null, source: {} as any, diff --git a/packages/databases-collections-list/src/namespace-card.tsx b/packages/databases-collections-list/src/namespace-card.tsx index 90263412ada..0dbd16c7f8f 100644 --- a/packages/databases-collections-list/src/namespace-card.tsx +++ b/packages/databases-collections-list/src/namespace-card.tsx @@ -38,11 +38,11 @@ const CardTitleGroup: React.FunctionComponent = ({ children }) => { return
{children}
; }; -const nonExistantLightStyles = css({ +const inferredFromPrivilegesLightStyles = css({ color: palette.gray.dark1, }); -const nonExistantDarkStyles = css({ +const inferredFromPrivilegesDarkStyles = css({ color: palette.gray.base, }); @@ -86,8 +86,8 @@ const cardName = css({ const CardName: React.FunctionComponent<{ children: string; - isNonExistent: boolean; -}> = ({ children, isNonExistent }) => { + inferredFromPrivileges: boolean; +}> = ({ children, inferredFromPrivileges }) => { const darkMode = useDarkMode(); return (
@@ -96,8 +96,10 @@ const CardName: React.FunctionComponent<{ className={cx( cardName, darkMode ? cardNameDark : cardNameLight, - isNonExistent && !darkMode && nonExistantLightStyles, - isNonExistent && darkMode && nonExistantDarkStyles + inferredFromPrivileges && + !darkMode && + inferredFromPrivilegesLightStyles, + inferredFromPrivileges && darkMode && inferredFromPrivilegesDarkStyles )} > {children} @@ -189,7 +191,7 @@ export type NamespaceItemCardProps = { status: 'initial' | 'fetching' | 'refreshing' | 'ready' | 'error'; data: DataProp[]; badges?: BadgeProp[] | null; - isNonExistent: boolean; + inferredFromPrivileges: boolean; onItemClick(id: string): void; onItemDeleteClick?: (id: string) => void; }; @@ -222,7 +224,7 @@ export const NamespaceItemCard: React.FunctionComponent< onItemDeleteClick, badges = null, viewType, - isNonExistent, + inferredFromPrivileges, ...props }) => { const { readOnly, enableDbAndCollStats } = usePreferences([ @@ -239,7 +241,7 @@ export const NamespaceItemCard: React.FunctionComponent< const hasDeleteHandler = !!onItemDeleteClick; const cardActions: ItemAction[] = useMemo(() => { - return readOnly || !hasDeleteHandler || isNonExistent + return readOnly || !hasDeleteHandler || inferredFromPrivileges ? [] : [ { @@ -248,7 +250,7 @@ export const NamespaceItemCard: React.FunctionComponent< icon: 'Trash', }, ]; - }, [type, readOnly, isNonExistent, hasDeleteHandler]); + }, [type, readOnly, inferredFromPrivileges, hasDeleteHandler]); const defaultActionProps = useDefaultAction(onDefaultAction); @@ -273,9 +275,9 @@ export const NamespaceItemCard: React.FunctionComponent< { className: cx( card, - isNonExistent && [ - !darkMode && nonExistantLightStyles, - darkMode && nonExistantDarkStyles, + inferredFromPrivileges && [ + !darkMode && inferredFromPrivilegesLightStyles, + darkMode && inferredFromPrivilegesDarkStyles, inactiveCardStyles, ] ), @@ -303,9 +305,11 @@ export const NamespaceItemCard: React.FunctionComponent< {...cardProps} > - {name} + + {name} + - {isNonExistent && ( + {inferredFromPrivileges && ( } > - Your privileges grant you access to this namespace, but it does not + Your privileges grant you access to this namespace, but it might not currently exist )} @@ -340,7 +344,7 @@ export const NamespaceItemCard: React.FunctionComponent< ); } diff --git a/packages/instance-model/lib/model.js b/packages/instance-model/lib/model.js index 7142aa5a940..7cfb2cd638c 100644 --- a/packages/instance-model/lib/model.js +++ b/packages/instance-model/lib/model.js @@ -135,22 +135,9 @@ const InstanceModel = AmpersandModel.extend( isSearchIndexesSupported: 'boolean', atlasVersion: { type: 'string', default: '' }, csfleMode: { type: 'string', default: 'unavailable' }, - shouldFetchDbAndCollStats: { type: 'boolean', default: false }, }, initialize: function ({ preferences, ...props }) { - // Initialize the property directly from preferences - this.set({ - shouldFetchDbAndCollStats: - preferences.getPreferences().enableDbAndCollStats, - }); - - // Listen to preference changes using the preferences API - this._preferenceUnsubscribe = preferences.onPreferenceValueChanged( - 'enableDbAndCollStats', - (value) => { - this.set({ shouldFetchDbAndCollStats: value }); - } - ); + this.preferences = preferences; AmpersandModel.prototype.initialize.call(this, props); }, @@ -409,11 +396,15 @@ const InstanceModel = AmpersandModel.extend( return coll; }, + shouldFetchDbAndCollStats() { + return this.preferences.getPreferences().enableDbAndCollStats; + }, + + shouldFetchNamespacesFromPrivileges() { + return this.preferences.getPreferences().inferNamespacesFromPrivileges; + }, + removeAllListeners() { - // Clean up preference listeners - if (this._preferenceUnsubscribe) { - this._preferenceUnsubscribe(); - } InstanceModel.removeAllListeners(this); }, diff --git a/packages/instance-model/test/index.test.js b/packages/instance-model/test/index.test.js index 9dd8f78cf86..b89bf08ccf1 100644 --- a/packages/instance-model/test/index.test.js +++ b/packages/instance-model/test/index.test.js @@ -23,10 +23,10 @@ describe('mongodb-instance-model', function () { const instance = new MongoDBInstance({ _id: 'abc', preferences }); await preferences.savePreferences({ enableDbAndCollStats: true }); - expect(instance.shouldFetchDbAndCollStats).to.equal(true); + expect(instance.shouldFetchDbAndCollStats()).to.equal(true); await preferences.savePreferences({ enableDbAndCollStats: false }); - expect(instance.shouldFetchDbAndCollStats).to.equal(false); + expect(instance.shouldFetchDbAndCollStats()).to.equal(false); }); context('with mocked dataService', function () {