Skip to content
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
2 changes: 2 additions & 0 deletions package-lock.json

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

6 changes: 5 additions & 1 deletion packages/atlas-service/src/atlas-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,11 @@ export class AtlasService {
userDataEndpoint(
orgId: string,
groupId: string,
type: 'favoriteQueries' | 'recentQueries' | 'favoriteAggregations',
type:
| 'favoriteQueries'
| 'recentQueries'
| 'favoriteAggregations'
| 'dataModelDescriptions',
id?: string
): string {
const encodedOrgId = encodeURIComponent(orgId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import React, { type InputHTMLAttributes, useRef } from 'react';
import { css } from '@leafygreen-ui/emotion';

const displayNoneStyles = css({
display: 'none',
// make sure actual input is always hidden (mms is doing something weird
// forcing these to be visible)
display: 'none !important',
Copy link
Collaborator

Choose a reason for hiding this comment

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

😭

});

type FileSelectorTriggerProps = {
Expand Down
5 changes: 3 additions & 2 deletions packages/compass-data-modeling/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@
".": "./dist/index.js",
"./provider": "./dist/provider/index.js",
"./renderer": "./dist/services/data-model-storage-electron.js",
"./web": "./dist/services/data-model-storage-in-memory.js"
"./web": "./dist/services/data-model-storage-web.js"
},
"compass:exports": {
".": "./src/index.ts",
"./provider": "./src/provider/index.tsx",
"./renderer": "./src/services/data-model-storage-electron.tsx",
"./web": "./src/services/data-model-storage-in-memory.tsx"
"./web": "./src/services/data-model-storage-web.tsx"
},
"types": "./dist/index.d.ts",
"scripts": {
Expand All @@ -54,6 +54,7 @@
"reformat": "npm run eslint . -- --fix && npm run prettier -- --write ."
},
"dependencies": {
"@mongodb-js/atlas-service": "^0.65.0",
"@mongodb-js/compass-app-registry": "^9.4.27",
"@mongodb-js/compass-app-stores": "^7.67.0",
"@mongodb-js/compass-components": "^1.55.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { AtlasUserData } from '@mongodb-js/compass-user-data';
import type {
DataModelStorage,
MongoDBDataModelDescription,
} from './data-model-storage';
import { MongoDBDataModelDescriptionSchema } from './data-model-storage';
import dataModelStorageInMemory from './data-model-storage-in-memory';
import {
atlasServiceLocator,
type AtlasService,
} from '@mongodb-js/atlas-service/provider';
import { createServiceProvider } from '@mongodb-js/compass-app-registry';
import { DataModelStorageServiceProvider } from '../provider';
import React, { useRef } from 'react';
import { mongoLogId, useLogger } from '@mongodb-js/compass-logging/provider';

class DataModelStorageAtlas implements DataModelStorage {
private readonly userData: AtlasUserData<
typeof MongoDBDataModelDescriptionSchema
>;
constructor(orgId: string, projectId: string, atlasService: AtlasService) {
this.userData = new AtlasUserData(
MongoDBDataModelDescriptionSchema,
'dataModelDescriptions',
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not that important as this will be probably refactored too, just looks like we're repeating a const here.

{
orgId,
projectId,
getResourceUrl(path) {
// TODO(COMPASS-9960): this is copied from compass-web entrypoint for
// brevity, but shouldn't be defined outside of AtlasUserData, this
// logic is literally the same between all user data instances and can
// be encapsulated inside of the AtlasUserData class implementation
Comment on lines +29 to +32
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This clean-up will touch a lot of unrelated code, so I'm splitting it to a separate PR

const [type, pathOrgId, pathProjectId, id] =
path?.split('/').filter(Boolean) || [];

if (
!type ||
!pathOrgId ||
!pathProjectId ||
type !== 'dataModelDescriptions'
Copy link
Collaborator

Choose a reason for hiding this comment

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

Also not that important just highlighting that this last check sounds like it could be misconfiguration rather than "outside of atlas cloud context"

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'm not putting much effort into this because COMPASS-9960 will remove this whole block of code. Strictly speaking none of these can be missing because all these options are required in the user data constructor

) {
throw new Error(
'DataModelStorageAtlas is used outside of Atlas Cloud context'
);
}

return atlasService.userDataEndpoint(
pathOrgId,
pathProjectId,
type,
id
);
},
authenticatedFetch: atlasService.authenticatedFetch.bind(atlasService),
}
);
}
save(description: MongoDBDataModelDescription) {
return this.userData.write(description.id, description);
}
delete(id: MongoDBDataModelDescription['id']) {
return this.userData.delete(id);
}
async loadAll(): Promise<MongoDBDataModelDescription[]> {
try {
const res = await this.userData.readAll();
return res.data;
} catch {
return [];
}
}
async load(id: string): Promise<MongoDBDataModelDescription | null> {
return (
(await this.loadAll()).find((item) => {
return item.id === id;
}) ?? null
);
}
}

export const DataModelStorageServiceProviderWeb = createServiceProvider(
function DataModelStorageServiceProviderWeb({
children,
orgId,
projectId,
}: {
/**
* Atlas organization id. Optional. If provided, data model storage will
* save the user data in Atlas Cloud, otherwise will fall back to in-memory
* storage
*/
orgId?: string;
/**
* Atlas project id. Optional. If provided, data model storage will
* save the user data in Atlas Cloud, otherwise will fall back to in-memory
* storage
*/
projectId?: string;
children?: React.ReactNode;
}) {
const storageRef = useRef<DataModelStorage>();
const atlasService = atlasServiceLocator();
const logger = useLogger('DATA-MODEL-STORAGE');

if (!storageRef.current) {
if (orgId && projectId) {
storageRef.current = new DataModelStorageAtlas(
orgId,
projectId,
atlasService
);
} else {
logger.log.warn(
mongoLogId(1_001_000_379),
'DataModelStorageServiceProviderWeb',
'Falling back to in memory storage because orgId or projectId is missing'
);
// Fallback to in-memory if we're outside of Atlas Cloud
storageRef.current = dataModelStorageInMemory;
Copy link
Collaborator

Choose a reason for hiding this comment

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

If this fallback is for the sandbox, can we check if it's sandbox and otherwise fail visibly?

Copy link
Collaborator Author

@gribnoysup gribnoysup Oct 21, 2025

Choose a reason for hiding this comment

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

This is not a fallback for the sandbox, I don't think it's a good idea to think about it in that way and would suggest to try not to do it 🙂 Compass-web needs to be able to work outside Atlas runtime seamlessly as much as possible. Arbitrary environment checks are going against that, introducing more complexity than needed, causing weird behavioral differences for different envs (similar to the case of preferences not working as expected in sandbox because it was gated by the environment check for the sandbox), and spreading configuration across the whole codebase instead of keeping it all in one place, so instead I strongly recomment just solving the problem in a more generic way: DM storage in web can fallback to in memory if that's how it is configured, it's the work of consumer to pass the options correctly.

I can rename this file to data-model-storage-web to illustrate this better I think

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah I see, so the idea would be this is for other integrations? My concern is that we made a decision to not design around in-memory usage, at present the UI is very strongly suggesting that the diagrams are persisted.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Renaming it is a good idea, thanks!

Copy link
Collaborator

Choose a reason for hiding this comment

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

I just hope it's somehow clear that other consumers should implement their own persistent storage, otherwise the feature doesn't work as expected

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, so the main thing here is that compass-web package should work if it's not used in Atlas without the need to "pretend" to be a sandbox or an e2e test environment through some global configuration like an env var. Maybe there's a better way for us to embed this assumption in the code, FWIW component props are the public interface here controlling this behavior pretty explicitly, but doesn't mean there isn't a better way 🤔

}
}

return (
<DataModelStorageServiceProvider storage={storageRef.current}>
{children}
</DataModelStorageServiceProvider>
);
}
);
2 changes: 1 addition & 1 deletion packages/compass-data-modeling/web.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export * from './dist/services/data-model-storage-in-memory.d';
export * from './dist/services/data-model-storage-web.d';
2 changes: 1 addition & 1 deletion packages/compass-data-modeling/web.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
'use strict';
module.exports = require('./dist/services/data-model-storage-in-memory');
module.exports = require('./dist/services/data-model-storage-web');
3 changes: 2 additions & 1 deletion packages/compass-web/sandbox/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const App = () => {
const [currentTab, updateCurrentTab] = useWorkspaceTabRouter();
const { status, projectParams } = useAtlasProxySignIn();
const {
orgId,
projectId,
csrfToken,
csrfTime,
Expand Down Expand Up @@ -99,7 +100,7 @@ const App = () => {
<SandboxPreferencesUpdateProvider>
<Body as="div" className={sandboxContainerStyles}>
<CompassWeb
orgId={''}
orgId={orgId ?? ''}
projectId={projectId ?? ''}
onActiveWorkspaceTabChange={updateCurrentTab}
initialWorkspace={currentTab ?? undefined}
Expand Down
3 changes: 3 additions & 0 deletions packages/compass-web/sandbox/sandbox-atlas-sign-in.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ console.info(
type SignInStatus = 'checking' | 'signed-in' | 'signed-out';

type ProjectParams = {
orgId: string;
projectId: string;
csrfToken: string;
csrfTime: string;
Expand Down Expand Up @@ -127,10 +128,12 @@ export function useAtlasProxySignIn(): AtlasLoginReturnValue {
currentOrganization: { genAIFeaturesEnabled },
featureFlags: { groupEnabledFeatureFlags },
userRoles,
currentOrganization,
} = params;
const overrideGenAIFeatures =
process.env.COMPASS_OVERRIDE_ENABLE_AI_FEATURES === 'true';
setProjectParams({
orgId: currentOrganization.id,
projectId,
csrfToken,
csrfTime,
Expand Down
9 changes: 6 additions & 3 deletions packages/compass-web/src/entrypoint.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ import { WebWorkspaceTab as WelcomeWorkspaceTab } from '@mongodb-js/compass-welc
import { WorkspaceTab as MyQueriesWorkspace } from '@mongodb-js/compass-saved-aggregations-queries';
import { useCompassWebPreferences } from './preferences';
import { DataModelingWorkspaceTab as DataModelingWorkspace } from '@mongodb-js/compass-data-modeling';
import { DataModelStorageServiceProviderInMemory } from '@mongodb-js/compass-data-modeling/web';
import { DataModelStorageServiceProviderWeb } from '@mongodb-js/compass-data-modeling/web';
import {
createWebRecentQueryStorage,
createWebFavoriteQueryStorage,
Expand Down Expand Up @@ -507,7 +507,10 @@ const CompassWeb = ({
<TelemetryProvider options={telemetryOptions.current}>
<WithAtlasProviders>
<WithStorageProviders orgId={orgId} projectId={projectId}>
<DataModelStorageServiceProviderInMemory>
<DataModelStorageServiceProviderWeb
orgId={orgId}
projectId={projectId}
>
<AtlasCloudConnectionStorageProvider
orgId={orgId}
projectId={projectId}
Expand Down Expand Up @@ -576,7 +579,7 @@ const CompassWeb = ({
</CompassConnections>
</CompassAssistantProvider>
</AtlasCloudConnectionStorageProvider>
</DataModelStorageServiceProviderInMemory>
</DataModelStorageServiceProviderWeb>
</WithStorageProviders>
</WithAtlasProviders>
</TelemetryProvider>
Expand Down
Loading