Skip to content

feat: add inferNamespacesFromPrivileges, default to true COMPASS-9572 #7153

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jul 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 19 additions & 19 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/collection-model/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ interface CollectionProps {
sourceName: string | null;
source: Collection;
properties: { id: string; options?: Record<string, unknown> }[];
is_non_existent: boolean;
inferred_from_privileges: boolean;
}

type CollectionDataService = Pick<
Expand Down
30 changes: 18 additions & 12 deletions packages/collection-model/lib/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ function pickCollectionInfo({
validation,
clustered,
fle2,
is_non_existent,
inferred_from_privileges,
}) {
return {
type,
Expand All @@ -113,7 +113,7 @@ function pickCollectionInfo({
validation,
clustered,
fle2,
is_non_existent,
inferred_from_privileges,
};
}

Expand All @@ -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',
Expand Down Expand Up @@ -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';
Expand All @@ -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) {
Expand Down Expand Up @@ -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`
Expand All @@ -405,6 +410,7 @@ const CollectionCollection = AmpersandCollection.extend(
{
// Always fetch collections with info
nameOnly: false,
fetchNamespacesFromPrivileges: shouldFetchNamespacesFromPrivileges,
privileges: instanceModel.auth.privileges,
}
);
Expand Down
9 changes: 9 additions & 0 deletions packages/compass-app-stores/src/stores/instance-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
6 changes: 3 additions & 3 deletions packages/compass-collection/src/plugin-tab-title.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type PluginTitleProps = {

function PluginTitle({
editViewName,
isNonExistent,
inferredFromPrivileges,
isReadonly,
isTimeSeries,
sourceName,
Expand Down Expand Up @@ -68,12 +68,12 @@ function PluginTitle({
? 'Visibility'
: collectionType === 'timeseries'
? 'TimeSeries'
: isNonExistent
: inferredFromPrivileges
? 'EmptyFolder'
: 'Folder'
}
data-namespace={ns}
isNonExistent={isNonExistent}
inferredFromPrivileges={inferredFromPrivileges}
/>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ const draggingTabStyles = css({
cursor: 'grabbing !important',
});

const nonExistentStyles = css({
const inferredFromPrivilegesStyles = css({
color: palette.gray.base,
});

Expand Down Expand Up @@ -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][];
};
Expand All @@ -206,7 +206,7 @@ function Tab({
type,
title,
tooltip,
isNonExistent,
inferredFromPrivileges,
isSelected,
isDragging,
onSelect,
Expand Down Expand Up @@ -271,7 +271,7 @@ function Tab({
className={cx(
tabStyles,
themeClass,
isNonExistent && nonExistentStyles,
inferredFromPrivileges && inferredFromPrivilegesStyles,
isSelected && selectedTabStyles,
isSelected && tabTheme && selectedThemedTabStyles,
isDragging && draggingTabStyles,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const connections: Connection[] = [
collectionsStatus: 'initial',
collectionsLength: 5,
collections: [],
isNonExistent: false,
inferredFromPrivileges: false,
},
{
_id: 'db_ready',
Expand All @@ -57,26 +57,26 @@ const connections: Connection[] = [
type: 'collection',
sourceName: '',
pipeline: [],
isNonExistent: false,
inferredFromPrivileges: false,
},
{
_id: 'db_ready.woof',
name: 'woof',
type: 'timeseries',
sourceName: '',
pipeline: [],
isNonExistent: false,
inferredFromPrivileges: false,
},
{
_id: 'db_ready.bwok',
name: 'bwok',
type: 'view',
sourceName: '',
pipeline: [],
isNonExistent: false,
inferredFromPrivileges: false,
},
],
isNonExistent: false,
inferredFromPrivileges: false,
},
],
isReady: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't know if these collections exist if the user didn't have access to listDatabases or listCollections. We don't record whether they had access or not and therefore we can't know at this point. (If they DID have access to those commands then we can probably safely say that these don't exist. But right now we don't know that.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Arguably we could also change the code in dataService to only infer namespaces from privileges if the corresponding listDatabases() or listCollections() call failed due to not having access. That would simplify matters a bit.)

Copy link
Contributor Author

@lerouxb lerouxb Jul 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess inside dataService's listCollections() or listDatabases() calls we can immediately tell if the corresponding call worked or not and then have different booleans for dbs that definitely don't exist and that may or may not exist 🤔

Copy link
Contributor Author

@lerouxb lerouxb Jul 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a follow-up ticket COMPASS-9641.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also need to update this bit in packages/databases-collections-list/src/namespace-card.tsx:318

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Updated.


const tooltipTriggerStyles = css({
display: 'flex',
Expand Down Expand Up @@ -35,21 +35,21 @@ const IconWithTooltip = ({

export const NavigationItemIcon = ({ item }: { item: SidebarTreeItem }) => {
if (item.type === 'database') {
if (item.isNonExistent) {
if (item.inferredFromPrivileges) {
return (
<IconWithTooltip
text={NON_EXISTANT_NAMESPACE_TEXT}
text={INFERRED_FROM_PRIVILEGES_TEXT}
glyph="EmptyDatabase"
/>
);
}
return <Icon glyph="Database" />;
}
if (item.type === 'collection') {
if (item.isNonExistent) {
if (item.inferredFromPrivileges) {
return (
<IconWithTooltip
text={NON_EXISTANT_NAMESPACE_TEXT}
text={INFERRED_FROM_PRIVILEGES_TEXT}
glyph="EmptyFolder"
/>
);
Expand Down
Loading
Loading