Skip to content

feat(save-user-data): extend user data abstract class for api backend COMPASS-9558 #7114

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

Open
wants to merge 47 commits into
base: main
Choose a base branch
from

Conversation

myang1220
Copy link
Contributor

@myang1220 myang1220 commented Jul 15, 2025

Description

Extend the IUserData abstract class to create an AtlasUserData class that uses the endpoints created in CCS. Contains unit testing.

Checklist

  • New tests and/or benchmarks are included
  • Documentation is changed or added
  • If this change updates the UI, screenshots/videos are added and a design review is requested
  • I have signed the MongoDB Contributor License Agreement (https://www.mongodb.com/legal/contributor-agreement)

Motivation and Context

  • Bugfix
  • New feature
  • Dependency update
  • Misc

Open Questions

See comments

Dependents

Types of changes

  • Backport Needed
  • Patch (non-breaking change which fixes an issue)
  • Minor (non-breaking change which adds functionality)
  • Major (fix or feature that would cause existing functionality to change)

@github-actions github-actions bot added the feat label Jul 15, 2025
@myang1220 myang1220 added the no release notes Fix or feature not for release notes label Jul 15, 2025
}

return true;
} catch (error) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

How does the logging look? Are there any changes that I should make to make this more consistent with Compass behavior?

@myang1220 myang1220 requested review from Anemy and gribnoysup July 18, 2025 21:21
@myang1220 myang1220 marked this pull request as ready for review July 18, 2025 21:21
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@gribnoysup @Anemy
Do you guys have any clue where these may have come from? I haven't personally modified any of them, so I wonder whether it's due to command line commands?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah, those are not really related. Probably accidentally run a compile command across all packages? We haven't updated this in a while, so I guess there might be some slight formatting variations or something, probably better to just revert those from your patch

@@ -6,9 +6,8 @@ export class HistoryStorage {
userData;

constructor(basePath?: string) {
this.userData = new FileUserData(z.string().array(), {
this.userData = new FileUserData(z.string().array(), getAppName() ?? '', {
// Todo: https://jira.mongodb.org/browse/COMPASS-7080
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: this todo should probably still point at the getAppName() part

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is this just referring to the TODO comment?

nvs119 added 6 commits August 12, 2025 16:20
Remove unwanted bson-transpilers changes that were accidentally included in the rebase.
This resets the bson-transpilers package back to its master state while preserving
the user-data related changes that are needed for this PR.
Rebuild the generated symbol table files to ensure they are properly
formatted and up-to-date with the current source templates.
@nvs119 nvs119 requested review from Anemy and gribnoysup August 12, 2025 22:23
@nvs119
Copy link
Contributor

nvs119 commented Aug 12, 2025

Trying to figure out if there are more outstanding comments here that I'll need to resolve as I'm finishing up this PR for Moses! This PR can be merged without fear (I believe) since the actual storage providers don't get added until the next PR. Let me know if that understanding is incorrect!

Copy link
Member

@Anemy Anemy left a comment

Choose a reason for hiding this comment

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

Nice, left a couple comments. Mostly thinking about how we can make the error handling surface easily to the consumer.

}
}

// TODO: change this depending on whether or not updateAttributes can provide all current data
Copy link
Member

Choose a reason for hiding this comment

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

Is this something we need to do in this pr?

Copy link
Contributor

Choose a reason for hiding this comment

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

I believe Moses had a follow up PR for this 1a0fcc3

});
return true;
} catch {
return false;
Copy link
Member

Choose a reason for hiding this comment

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

Here and in other places, would it make sense to instead be throwing these errors? That way the places that use these storage methods can surface errors to users when they happen. Otherwise folks wouldn't know when something failed to write/load/update without checking the logs.

Copy link
Contributor

Choose a reason for hiding this comment

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

I may be wrong, but when I follow this up to the UI calls, the UI ignores return values. if we let the errors bubble up, the UI would crash (to my understanding) with unhandled promise rejections, right?

do you want me to add proper error handling in the UI layer as well? Just not clear if that change is within the scope of this PR or not, happy to do so that if that's what you're suggesting

orgId: string,
projectId: string,
getResourceUrl: GetResourceUrl,
authenticatedFetch: AuthenticatedFetch,
Copy link
Member

Choose a reason for hiding this comment

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

When we start getting this many arguments it reads nicer if we include them in an object type. How does it sound if we have all of these except the validator in AtlasUserDataOptions?

Comment on lines 312 to 316
if (!response.ok) {
throw new Error(
`Failed to post data: ${response.status} ${response.statusText}`
);
}
Copy link
Collaborator

@gribnoysup gribnoysup Aug 14, 2025

Choose a reason for hiding this comment

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

This is already handled by authenticatedFetch method, so no need to check ok here and it all the other places where authenticatedFetch is used

Comment on lines 325 to 327
url: await this.getResourceUrl(
`${this.dataType}/${this.orgId}/${this.projectId}`
),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Super nit, maybe let's just get it once at the top of the function?

Copy link
Contributor

Choose a reason for hiding this comment

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

Changing this for write(), delete(), and readOne()

Left updateAttributes unchanged due to the dependency on the readOne() call order

@nvs119 nvs119 requested review from Anemy, gribnoysup and Copilot August 14, 2025 20:38
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR extends the user data storage abstraction to support Atlas backend APIs by creating a new AtlasUserData class alongside the existing FileUserData class. The primary purpose is to enable saving user data to Atlas cloud services through HTTP endpoints while maintaining the same interface as the file-based storage.

Key changes include:

  • Refactoring the FileUserData constructor to accept dataType as a separate parameter instead of within options
  • Creating a new AtlasUserData class that implements HTTP-based storage operations
  • Modifying interface return types from domain objects to boolean success indicators

Reviewed Changes

Copilot reviewed 18 out of 19 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
packages/compass-user-data/src/user-data.ts Adds new AtlasUserData class and refactors constructor parameter structure
packages/compass-user-data/src/user-data.spec.ts Comprehensive test suite for the new AtlasUserData functionality
packages/compass-user-data/src/index.ts Exports the new AtlasUserData class
packages/my-queries-storage/src/*.ts Updates constructors and return types to align with new interface
packages/connection-storage/src/compass-main-connection-storage.ts Updates constructor calls for refactored parameter structure
packages/compass-shell/src/modules/history-storage.ts Updates constructor calls for refactored parameter structure
packages/compass-preferences-model/src/*.ts Updates constructor calls for refactored parameter structure
packages/compass-data-modeling/src/services/data-model-storage-electron.tsx Updates constructor calls for refactored parameter structure
packages/atlas-service/src/*.ts Renames wsBaseUrl to ccsBaseUrl for consistency

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

private readonly authenticatedFetch;
private readonly getResourceUrl;
private orgId: string = '';
private projectId: string = '';
Copy link
Preview

Copilot AI Aug 14, 2025

Choose a reason for hiding this comment

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

The orgId and projectId are initialized as empty strings but are required constructor parameters. This initialization is redundant and potentially confusing since these values are immediately overwritten in the constructor.

Suggested change
private projectId: string = '';
private orgId: string;
private projectId: string;

Copilot uses AI. Check for mistakes.

private readonly authenticatedFetch;
private readonly getResourceUrl;
private orgId: string = '';
private projectId: string = '';
Copy link
Preview

Copilot AI Aug 14, 2025

Choose a reason for hiding this comment

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

The projectId is initialized as an empty string but is a required constructor parameter. This initialization is redundant and potentially confusing since these values are immediately overwritten in the constructor.

Suggested change
private projectId: string = '';
private projectId: string;

Copilot uses AI. Check for mistakes.

error: (error as Error).message,
}
);
return null;
Copy link
Preview

Copilot AI Aug 14, 2025

Choose a reason for hiding this comment

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

The readOne method returns null on error, but the return type is Promise<z.output>. This will cause type errors since null is not assignable to z.output. Consider throwing the error or returning a default value that matches the schema.

Suggested change
return null;
throw error;

Copilot uses AI. Check for mistakes.

data: Partial<z.input<T>>
): Promise<boolean> {
try {
const prevData = await this.readOne(id);
Copy link
Preview

Copilot AI Aug 14, 2025

Choose a reason for hiding this comment

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

The updateAttributes method calls readOne which can return null on error, but the code assumes it will always return valid data. This could cause runtime errors when spreading null into the newData object.

Suggested change
const prevData = await this.readOne(id);
const prevData = await this.readOne(id);
if (prevData == null) {
log.error(
mongoLogId(1_001_000_370),
'Atlas Backend',
'Error updating data: could not read previous data',
{
url: await this.getResourceUrl(
`${this.dataType}/${this.orgId}/${this.projectId}/${id}`
),
id,
}
);
return false;
}

Copilot uses AI. Check for mistakes.


const [getUrl, getOptions] = authenticatedFetchStub.firstCall.args;
expect(getUrl).to.equal(
'cluster-connection.cloud.mongodb.com/FavoriteQueries/test-org/test-proj'
Copy link
Preview

Copilot AI Aug 14, 2025

Choose a reason for hiding this comment

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

The test expects the first call to be a GET request, but the updateAttributes implementation calls readOne first which makes a GET request to a different URL pattern than expected. The URL construction should match the actual implementation.

Suggested change
'cluster-connection.cloud.mongodb.com/FavoriteQueries/test-org/test-proj'
'cluster-connection.cloud.mongodb.com/FavoriteQueries/test-org/test-proj/test-id'

Copilot uses AI. Check for mistakes.

});
this.userData = new FileUserData(
MongoDBDataModelDescriptionSchema,
'DataModelDescription',
Copy link
Preview

Copilot AI Aug 14, 2025

Choose a reason for hiding this comment

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

The dataType parameter was changed from 'DataModelDescriptions' (plural) to 'DataModelDescription' (singular), which could break existing file storage paths and cause data migration issues.

Suggested change
'DataModelDescription',
'DataModelDescriptions',

Copilot uses AI. Check for mistakes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feat no release notes Fix or feature not for release notes
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants