Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
3f336ec
feat(save-user-data): create a user data abstract class
myang1220 Jul 10, 2025
ad5e416
Merge branch 'main' into user-data-interface
myang1220 Jul 10, 2025
78026a3
fix: rm defaults and add semaphore to FileUserData
myang1220 Jul 10, 2025
b24d748
merge remote branch back into local
myang1220 Jul 10, 2025
8413d43
Merge branch 'main' into user-data-interface
myang1220 Jul 10, 2025
dc38640
feat(save-user-data): push for help
myang1220 Jul 15, 2025
41abc96
feat(save-user-data): comment out nulls
myang1220 Jul 15, 2025
e90412e
feat(save-user-data): create a user data abstract class
myang1220 Jul 10, 2025
701adaf
fix: rm defaults and add semaphore to FileUserData
myang1220 Jul 10, 2025
daee65a
feat(save-user-data): push for help
myang1220 Jul 15, 2025
90573c7
feat(save-user-data): comment out nulls
myang1220 Jul 15, 2025
0e997ff
rebase main into branch
myang1220 Jul 15, 2025
5d39eb3
Merge branch 'main' of https://github.com/mongodb-js/compass into ext…
myang1220 Jul 15, 2025
5edf5e2
potentital org and group id retrieval
myang1220 Jul 17, 2025
729c6db
minor fixes
myang1220 Jul 17, 2025
055b898
unit testing
myang1220 Jul 18, 2025
fed4e96
fix package-lock.json merge conflict
myang1220 Jul 18, 2025
2058a32
typo
myang1220 Jul 18, 2025
296147a
package-lock.json
myang1220 Jul 18, 2025
d128447
merge origin back into branch
myang1220 Jul 18, 2025
18240da
fix: parse one by one in readAll
myang1220 Jul 21, 2025
31a8643
fix: change updateAttributes return type to boolean
myang1220 Jul 21, 2025
e88856f
stricter typing for type param and fix update tests
myang1220 Jul 22, 2025
3b8fc66
add getResourceUrl and change groupId to projectId
myang1220 Jul 22, 2025
7d42eef
merge main back into branch
myang1220 Jul 22, 2025
3c033fe
change wsBaseUrl to ccsBaseUrl in spec files
myang1220 Jul 22, 2025
ef94ff8
chore(user-data): remove redundant withStats methods
gribnoysup Jul 23, 2025
3f8405a
fix(user-data): remove nonexistent exports
gribnoysup Jul 23, 2025
7f07ddd
fix(my-queries-storage): adjust types to match the method behavior
gribnoysup Jul 23, 2025
e09d426
fix(saved-aggregations-queries): adjust test fixture
gribnoysup Jul 23, 2025
04ba804
put data type in IUserData class
myang1220 Jul 23, 2025
cf510ce
Merge branch 'main' into extend-user-data
myang1220 Jul 23, 2025
73b2bd1
fix(user-data): add datatype to IUserData abstract class & tests
myang1220 Jul 24, 2025
51ab01e
fix: merge stats change to branch
myang1220 Jul 24, 2025
df135f3
fix(user-data): make create and update return type to be boolean, lik…
myang1220 Jul 24, 2025
0bb77b8
pull remote branch back into local
myang1220 Jul 24, 2025
f6efcd6
fix(compass-aggregations): fix method typing
myang1220 Jul 24, 2025
2c0de81
feat: building demo for saving user data project
myang1220 Jul 29, 2025
3eab096
path segment and method binding
myang1220 Jul 30, 2025
61265d6
working demo
myang1220 Jul 31, 2025
59e2a40
merge
nvs119 Aug 15, 2025
8e5d686
restore again
nvs119 Aug 15, 2025
8e94ca9
make it work on dev
nvs119 Sep 2, 2025
905addd
merge main
nvs119 Sep 2, 2025
8b17775
test fixes
nvs119 Sep 2, 2025
ce55bc5
Merge remote-tracking branch 'origin' into use-atlas-user-data
nvs119 Sep 2, 2025
5e554aa
save progress
nvs119 Sep 3, 2025
814b21e
everything working WITH feature flag
nvs119 Sep 4, 2025
8e343f9
Fix compass-sidebar dependency issue by moving useMyQueriesFeature to…
nvs119 Sep 8, 2025
d11f522
some fixes
nvs119 Sep 8, 2025
e795442
put back
nvs119 Sep 8, 2025
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
4,121 changes: 2,337 additions & 1,784 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,5 +132,8 @@
"@leafygreen-ui/text-area": "^10.0.2",
"@leafygreen-ui/card": "^12.0.2",
"@leafygreen-ui/logo": "^10.0.2"
},
"dependencies": {
"@leafygreen-ui/avatar": "^3.1.0"
Comment on lines +136 to +137
Copy link
Collaborator

Choose a reason for hiding this comment

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

What is this for? I think this was added here by mistake compass top-level doesn't have any deps, all deps are tracked by the packages that need them. so this should be moved to a package.json inside packages/XXX/package.json

}
}
21 changes: 16 additions & 5 deletions packages/atlas-service/src/atlas-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,16 @@ export class AtlasService {
// https://github.com/10gen/mms/blob/9f858bb987aac6aa80acfb86492dd74c89cbb862/client/packages/project/common/ajaxPrefilter.ts#L34-L49
return this.cloudEndpoint(path);
}
tempEndpoint(path?: string): string {
return `https://cluster-connection.cloud-dev.mongodb.com${normalizePath(
path
)}`;
}
userDataEndpoint(path?: string): string {
return `https://cluster-connection.cloud-dev.mongodb.com/userData${normalizePath(
path
)}`;
Comment on lines +82 to +89
Copy link
Preview

Copilot AI Sep 2, 2025

Choose a reason for hiding this comment

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

Hard-coded development endpoint URL in production code poses security risks. This should be configurable or removed from production builds.

Suggested change
return `https://cluster-connection.cloud-dev.mongodb.com${normalizePath(
path
)}`;
}
userDataEndpoint(path?: string): string {
return `https://cluster-connection.cloud-dev.mongodb.com/userData${normalizePath(
path
)}`;
if (!this.config.tempBaseUrl) {
throw new Error('tempBaseUrl is not configured');
}
return `${this.config.tempBaseUrl}${normalizePath(path)}`;
}
userDataEndpoint(path?: string): string {
if (!this.config.tempBaseUrl) {
throw new Error('tempBaseUrl is not configured');
}
return `${this.config.tempBaseUrl}/userData${normalizePath(path)}`;

Copilot uses AI. Check for mistakes.

Copy link
Collaborator

Choose a reason for hiding this comment

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

this will be fixed in follow up pr

Comment on lines +82 to +89
Copy link
Preview

Copilot AI Sep 2, 2025

Choose a reason for hiding this comment

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

Hard-coded development endpoint URL in production code poses security risks. This should be configurable or removed from production builds.

Suggested change
return `https://cluster-connection.cloud-dev.mongodb.com${normalizePath(
path
)}`;
}
userDataEndpoint(path?: string): string {
return `https://cluster-connection.cloud-dev.mongodb.com/userData${normalizePath(
path
)}`;
if (!this.config.tempEndpointBaseUrl) {
throw new Error('tempEndpointBaseUrl is not configured');
}
return `${this.config.tempEndpointBaseUrl}${normalizePath(path)}`;
}
userDataEndpoint(path?: string): string {
if (!this.config.userDataEndpointBaseUrl) {
throw new Error('userDataEndpointBaseUrl is not configured');
}
return `${this.config.userDataEndpointBaseUrl}${normalizePath(path)}`;

Copilot uses AI. Check for mistakes.

}
driverProxyEndpoint(path?: string): string {
return `${this.config.ccsBaseUrl}${normalizePath(path)}`;
}
Expand All @@ -91,13 +101,14 @@ export class AtlasService {
{ url }
);
try {
const headers = {
...this.options?.defaultHeaders,
...(shouldAddCSRFHeaders(init?.method) && getCSRFHeaders()),
...init?.headers,
};
const res = await fetch(url, {
...init,
headers: {
...this.options?.defaultHeaders,
...(shouldAddCSRFHeaders(init?.method) && getCSRFHeaders()),
...init?.headers,
},
headers,
});
this.logger.log.info(
this.logger.mongoLogId(1_001_000_309),
Expand Down
2 changes: 1 addition & 1 deletion packages/atlas-service/src/provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export const AtlasServiceProvider: React.FC<{
);
});

function useAtlasServiceContext(): AtlasService {
export function useAtlasServiceContext(): AtlasService {
const service = useContext(AtlasServiceContext);
if (!service) {
throw new Error('No AtlasService available in this context');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import { connect } from 'react-redux';
import { usePreference } from 'compass-preferences-model/provider';
import { Button, Icon, css, spacing } from '@mongodb-js/compass-components';
import { exportToLanguage } from '../../../modules/export-to-language';
import { SaveMenu } from './pipeline-menus';
Expand Down Expand Up @@ -49,7 +50,10 @@ export const PipelineSettings: React.FunctionComponent<
}) => {
// TODO: remove direct check for storage existing, breaks single source of
// truth rule and exposes services to UI, this breaks the rules for locators
const enableSavedAggregationsQueries = !!usePipelineStorage();
const pipelineStorageAvailable = !!usePipelineStorage();
const isMyQueriesEnabled = usePreference('enableMyQueries');
Copy link
Collaborator

Choose a reason for hiding this comment

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

Typescript error, not sure why though

const enableSavedAggregationsQueries =
pipelineStorageAvailable && isMyQueriesEnabled;
const isPipelineNameDisplayed =
!editViewName && !!enableSavedAggregationsQueries;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class DataModelStorageElectron implements DataModelStorage {
private readonly userData: FileUserData<
typeof MongoDBDataModelDescriptionSchema
>;

constructor(basePath?: string) {
this.userData = new FileUserData(
MongoDBDataModelDescriptionSchema,
Expand All @@ -20,12 +21,15 @@ class DataModelStorageElectron implements DataModelStorage {
}
);
}

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();
Expand All @@ -34,6 +38,7 @@ class DataModelStorageElectron implements DataModelStorage {
return [];
}
}

async load(id: string): Promise<MongoDBDataModelDescription | null> {
return (
(await this.loadAll()).find((item) => {
Expand Down
12 changes: 12 additions & 0 deletions packages/compass-preferences-model/src/preferences-schema.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ export type UserConfigurablePreferences = PermanentFeatureFlags &
enableExplainPlan: boolean;
enableAtlasSearchIndexes: boolean;
enableImportExport: boolean;
enableMyQueries: boolean;
enableAggregationBuilderRunPipeline: boolean;
enableAggregationBuilderExtraOptions: boolean;
enableGenAISampleDocumentPassing: boolean;
Expand Down Expand Up @@ -1019,6 +1020,17 @@ export const storedUserPreferencesProps: Required<{
validator: z.boolean().default(true),
type: 'boolean',
},
enableMyQueries: {
ui: true,
cli: true,
global: true,
description: {
short:
'Enable My Queries feature to save and manage favorite queries and aggregations',
},
validator: z.boolean().default(true),
type: 'boolean',
},

inferNamespacesFromPrivileges: {
ui: true,
Expand Down
14 changes: 14 additions & 0 deletions packages/compass-preferences-model/src/react.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,17 @@ export function withPreferences<
return createElement(component, { ...prefs, ...props });
};
}

/**
* Hook to check if the My Queries Data Explorer feature is enabled.
* This controls access to:
* - Saved queries and aggregations
* - Recent queries autocomplete
* - Favorite queries/aggregations
*/
export function useMyQueriesFeature(): boolean {
const enableMyQueries = usePreference('enableMyQueries');

// Default to true to match the preference schema default
return enableMyQueries ?? true;
}
10 changes: 8 additions & 2 deletions packages/compass-query-bar/src/components/query-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ import {
createAIPlaceholderHTMLPlaceholder,
} from '@mongodb-js/compass-generative-ai';
import { connect } from '../stores/context';
import { useIsAIFeatureEnabled } from 'compass-preferences-model/provider';
import {
useIsAIFeatureEnabled,
usePreference,
} from 'compass-preferences-model/provider';
import { useTelemetry } from '@mongodb-js/compass-telemetry/provider';

import {
Expand Down Expand Up @@ -202,8 +205,11 @@ export const QueryBar: React.FunctionComponent<QueryBarProps> = ({

const favoriteQueryStorageAvailable = !!useFavoriteQueryStorageAccess();
const recentQueryStorageAvailable = !!useRecentQueryStorageAccess();
const isMyQueriesEnabled = usePreference('enableMyQueries');
Copy link
Collaborator

Choose a reason for hiding this comment

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

TS

const enableSavedAggregationsQueries =
favoriteQueryStorageAvailable && recentQueryStorageAvailable;
favoriteQueryStorageAvailable &&
recentQueryStorageAvailable &&
isMyQueriesEnabled;

return (
<form
Expand Down
40 changes: 33 additions & 7 deletions packages/compass-query-bar/src/stores/query-bar-reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,9 +268,20 @@ export const fetchRecents = (): QueryBarThunkAction<
return async (
dispatch,
_getState,
{ recentQueryStorage, logger: { debug } }
{ recentQueryStorage, logger: { debug }, preferences }
) => {
try {
// Check if My Queries feature is enabled
const isMyQueriesEnabled = preferences.getPreferences().enableMyQueries;
if (!isMyQueriesEnabled) {
Comment on lines +275 to +276
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
const isMyQueriesEnabled = preferences.getPreferences().enableMyQueries;
if (!isMyQueriesEnabled) {
const { enableMyQueries } = preferences.getPreferences();
if (!enableMyQueries) {

A nothingburger of a code suggestion, its bit easier to read than the dangling property access. FFTR w/o change.

// If feature is disabled, dispatch empty array
dispatch({
type: QueryBarActions.RecentQueriesFetched,
recents: [],
});
return;
}

const {
queryBar: { namespace },
} = _getState();
Expand All @@ -286,7 +297,14 @@ export const fetchRecents = (): QueryBarThunkAction<
};

export const fetchSavedQueries = (): QueryBarThunkAction<void> => {
return (dispatch) => {
return (dispatch, _getState, { preferences }) => {
// Check if My Queries feature is enabled
const isMyQueriesEnabled = preferences.getPreferences().enableMyQueries;
if (!isMyQueriesEnabled) {
// If feature is disabled, don't fetch anything
return;
}

void dispatch(fetchRecents());
void dispatch(fetchFavorites());
};
Expand All @@ -303,9 +321,20 @@ export const fetchFavorites = (): QueryBarThunkAction<
return async (
dispatch,
_getState,
{ favoriteQueryStorage, logger: { debug } }
{ favoriteQueryStorage, logger: { debug }, preferences }
) => {
try {
// Check if My Queries feature is enabled
const isMyQueriesEnabled = preferences.getPreferences().enableMyQueries;
if (!isMyQueriesEnabled) {
// If feature is disabled, dispatch empty array
dispatch({
type: QueryBarActions.FavoriteQueriesFetched,
favorites: [],
});
return;
}

const {
queryBar: { namespace },
} = _getState();
Expand Down Expand Up @@ -356,10 +385,7 @@ export const saveRecentAsFavorite = (
};

// add it in the favorite
await favoriteQueryStorage?.updateAttributes(
favoriteQuery._id,
favoriteQuery
);
await favoriteQueryStorage?.saveQuery(favoriteQuery, favoriteQuery._id);

// update favorites
void dispatch(fetchFavorites());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
useOpenWorkspace,
useWorkspacePlugins,
} from '@mongodb-js/compass-workspaces/provider';
import { usePreference } from 'compass-preferences-model/provider';
import { usePreference, useMyQueriesFeature } from 'compass-preferences-model/provider';
import React from 'react';

const navigationItem = css({
Expand Down Expand Up @@ -102,9 +102,10 @@ export function Navigation({
const { openMyQueriesWorkspace, openDataModelingWorkspace } =
useOpenWorkspace();
const isDataModelingEnabled = usePreference('enableDataModeling');
const isMyQueriesEnabled = useMyQueriesFeature();
return (
<div>
{hasWorkspacePlugin('My Queries') && (
{hasWorkspacePlugin('My Queries') && isMyQueriesEnabled && (
<NavigationItem
onClick={openMyQueriesWorkspace}
glyph="CurlyBraces"
Expand Down
27 changes: 16 additions & 11 deletions packages/compass-user-data/src/user-data.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import fs from 'fs/promises';
import os from 'os';
import path from 'path';
import { expect } from 'chai';
import { z, type ZodError } from 'zod';
import sinon from 'sinon';

import {
FileUserData,
AtlasUserData,
FileUserData,
type FileUserDataOptions,
} from './user-data';
import { z, type ZodError } from 'zod';
import sinon from 'sinon';

type ValidatorOptions = {
allowUnknownProps?: boolean;
Expand Down Expand Up @@ -362,9 +363,9 @@ describe('AtlasUserData', function () {
orgId = 'test-org',
projectId = 'test-proj',
type:
| 'RecentQueries'
| 'FavoriteQueries'
| 'SavedPipelines' = 'FavoriteQueries'
| 'recentQueries'
| 'favoriteQueries'
| 'favoriteAggregations' = 'favoriteQueries'
) => {
return new AtlasUserData(getTestSchema(validatorOpts), type, {
orgId,
Expand Down Expand Up @@ -404,12 +405,13 @@ describe('AtlasUserData', function () {
expect(options.headers['Content-Type']).to.equal('application/json');

const body = JSON.parse(options.body as string);
expect(body.id).to.equal('test-id');
expect(body.projectId).to.equal('test-proj');
expect(body.data).to.be.a('string');
expect(JSON.parse(body.data as string)).to.deep.equal({ name: 'VSCode' });
expect(body.createdAt).to.be.a('string');
expect(new Date(body.createdAt as string)).to.be.instanceOf(Date);
// id and projectId should not be in the body (they're in the URL path)
expect(body.id).to.be.undefined;
expect(body.projectId).to.be.undefined;
});

it('returns false when authenticatedFetch throws an error', async function () {
Expand Down Expand Up @@ -461,6 +463,7 @@ describe('AtlasUserData', function () {
const [, options] = authenticatedFetchStub.firstCall.args;
const body = JSON.parse(options.body as string);
expect(body.data).to.equal('custom:{"name":"Custom"}');
expect(body.createdAt).to.be.a('string');
});
});

Expand Down Expand Up @@ -767,25 +770,27 @@ describe('AtlasUserData', function () {
await userData.updateAttributes('test-id', { name: 'Updated' });

const [, putOptions] = authenticatedFetchStub.secondCall.args;
expect(putOptions.body as string).to.equal(
const body = JSON.parse(putOptions.body as string);
expect(body.data).to.equal(
'custom:{"name":"Updated","hasDarkMode":true,"hasWebSupport":false}'
);
expect(body.createdAt).to.be.a('string');
});
});

context('AtlasUserData urls', function () {
it('constructs URL correctly for write operation', async function () {
authenticatedFetchStub.resolves(mockResponse({}));
getResourceUrlStub.resolves(
'cluster-connection.cloud.mongodb.com/FavoriteQueries/custom-org/custom-proj'
'cluster-connection.cloud.mongodb.com/favoriteQueries/custom-org/custom-proj/test-id'
);

const userData = getAtlasUserData({}, 'custom-org', 'custom-proj');
await userData.write('test-id', { name: 'Test' });

const [url] = authenticatedFetchStub.firstCall.args;
expect(url).to.equal(
'cluster-connection.cloud.mongodb.com/FavoriteQueries/custom-org/custom-proj'
'cluster-connection.cloud.mongodb.com/favoriteQueries/custom-org/custom-proj/test-id'
);
});

Expand Down
Loading