Skip to content
Merged
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
910c1af
feat(generative-ai): add method to get mock data schema CLOUDP-333849
kpamaran Aug 22, 2025
5034086
Merge branch 'main' into atlas-ai-service-get-mock-data-schema
kpamaran Aug 22, 2025
8a2a096
nit
kpamaran Aug 22, 2025
b7c5461
add exports
kpamaran Aug 22, 2025
cc950ca
Merge branch 'main' into atlas-ai-service-get-mock-data-schema
kpamaran Aug 22, 2025
0f56ee3
address linter err
kpamaran Aug 22, 2025
99c2881
draft redux integration for requesting fakerjs mappings
kpamaran Aug 22, 2025
f7d352d
test thunk behavior
kpamaran Aug 23, 2025
77af388
pass collection name, db name, and validation rules
kpamaran Aug 23, 2025
6e1bb35
nit
kpamaran Aug 23, 2025
051f90e
supply err attrs
kpamaran Aug 26, 2025
1e05219
Merge branch 'main' into atlas-ai-service-get-mock-data-schema
kpamaran Aug 26, 2025
e5fc781
Revert compass-collection diffs
kpamaran Aug 26, 2025
a3d3f98
clean up diffs for review
kpamaran Aug 26, 2025
aebff92
Merge branch 'main' into atlas-ai-service-get-mock-data-schema
kpamaran Aug 27, 2025
0de12bc
type state machine of mock data generator request
kpamaran Aug 27, 2025
203ee82
manage request state in redux store
kpamaran Aug 27, 2025
47c1f3b
unit test thunk
kpamaran Aug 27, 2025
549d838
rename request status enum
kpamaran Aug 27, 2025
b65be4b
update lock file
kpamaran Aug 27, 2025
95d94b8
remove pnpm-lock.yaml
kpamaran Aug 27, 2025
b91a1b6
Merge branch 'atlas-ai-service-get-mock-data-schema' into compass-col…
kpamaran Aug 27, 2025
5d39dee
unit test reducer transitions for fakerSchemaGeneration state
kpamaran Aug 27, 2025
ca06674
Merge branch 'main' into compass-collection-redux-integrates-mock-dat…
kpamaran Aug 28, 2025
d630729
simplify typing
kpamaran Aug 28, 2025
e88a7f7
nit
kpamaran Aug 28, 2025
a444035
handle modal closed action on fakerSchemaGeneration state
kpamaran Aug 28, 2025
8f9aaf9
Pass requestId for tracking
kpamaran Aug 28, 2025
44fa4c0
respect user config for using sample docs on gen ai
kpamaran Aug 28, 2025
3e43c3f
fix
kpamaran Aug 28, 2025
4a5dc46
adapt unit test
kpamaran Aug 28, 2025
5d0f7c5
preserve stack trace on err
kpamaran Aug 28, 2025
e539d11
rename status val
kpamaran Aug 28, 2025
d840ffc
add @mongodb-js/compass-generative-ai as a dep to compass-collection
kpamaran Aug 29, 2025
6d022af
Merge branch 'main' into compass-collection-redux-integrates-mock-dat…
kpamaran Aug 29, 2025
01372f5
remove thunk isolation test
kpamaran Sep 2, 2025
2e1ca5c
address feedback
kpamaran Sep 2, 2025
7206585
Merge branch 'main' into compass-collection-redux-integrates-mock-dat…
kpamaran Sep 2, 2025
d2182b0
Merge branch 'main' into compass-collection-redux-integrates-mock-dat…
kpamaran Sep 2, 2025
21ef538
resolve
kpamaran Sep 2, 2025
5716285
control modal steps with redux
kpamaran Sep 3, 2025
7a56c6e
revert nit
kpamaran Sep 3, 2025
b17d1f2
Merge branch 'main' into compass-collection-redux-integrates-mock-dat…
kpamaran Sep 3, 2025
ef2652f
adapt atlas-ai-service tests
kpamaran Sep 3, 2025
c0a306e
add compass-utils as dep to compass-collection
kpamaran Sep 3, 2025
025d2fe
nit
kpamaran Sep 3, 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
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.

5 changes: 3 additions & 2 deletions packages/compass-collection/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,11 @@
"@mongodb-js/compass-generative-ai": "^0.51.0",
"@mongodb-js/compass-logging": "^1.7.12",
"@mongodb-js/compass-telemetry": "^1.14.0",
"@mongodb-js/compass-utils": "^0.9.11",
"@mongodb-js/compass-workspaces": "^0.52.0",
"@mongodb-js/connection-info": "^0.17.2",
"@mongodb-js/mongodb-constants": "^0.14.0",
"bson": "^6.10.1",
"compass-preferences-model": "^2.51.0",
"hadron-document": "^8.9.6",
"mongodb": "^6.19.0",
Expand All @@ -67,8 +69,7 @@
"react": "^17.0.2",
"react-redux": "^8.1.3",
"redux": "^4.2.1",
"redux-thunk": "^2.4.2",
"bson": "^6.10.1"
"redux-thunk": "^2.4.2"
},
"devDependencies": {
"@mongodb-js/eslint-config-compass": "^1.4.7",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';

// TODO: More to come from CLOUDP-333853, CLOUDP-333854
const FakerSchemaEditor = () => {
return (
<div data-testid="faker-schema-editor">
Schema Editor Content Placeholder
</div>
);
};

export default FakerSchemaEditor;
Original file line number Diff line number Diff line change
@@ -1,82 +1,239 @@
import { expect } from 'chai';
import React from 'react';
import { render, screen } from '@mongodb-js/testing-library-compass';
import Sinon from 'sinon';
import { UnconnectedMockDataGeneratorModal as MockDataGeneratorModal } from './mock-data-generator-modal';
import {
screen,
render,
waitFor,
userEvent,
} from '@mongodb-js/testing-library-compass';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import MockDataGeneratorModal from './mock-data-generator-modal';
import { MockDataGeneratorStep } from './types';
import { StepButtonLabelMap } from './constants';
import type { CollectionState } from '../../modules/collection-tab';
import { default as collectionTabReducer } from '../../modules/collection-tab';

describe('MockDataGeneratorModal', () => {
const sandbox = Sinon.createSandbox();
let onClose: Sinon.SinonSpy;

beforeEach(() => {
onClose = sandbox.spy();
});

afterEach(() => {
sandbox.restore();
});

const onNextStep = Sinon.stub();
const onPreviousStep = Sinon.stub();

function renderModal({
isOpen = true,
currentStep = MockDataGeneratorStep.SCHEMA_CONFIRMATION,
mockServices = createMockServices(),
Comment on lines 19 to +22
Copy link
Collaborator

Choose a reason for hiding this comment

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

Just FYI, we call out testing-library-compass in development guide for a reason. A lot of manual setup you do here and in createMockServices we already do via testing helpers exposed from testing-library-compass (specifically https://github.com/mongodb-js/compass/tree/main/configs/testing-library-compass#createplugintesthelpers for testing parts of the plugin)

Copy link
Collaborator Author

@kpamaran kpamaran Sep 4, 2025

Choose a reason for hiding this comment

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

I attempted createPluginTestHelpers but had some trouble, I'll try again in my upcoming PR and look at the existing usage more closely

} = {}) {
const initialState: CollectionState = {
workspaceTabId: 'test-workspace-tab-id',
namespace: 'test.collection',
metadata: null,
schemaAnalysis: {
status: 'complete',
processedSchema: {
name: {
type: 'String',
probability: 1.0,
sample_values: ['John', 'Jane'],
},
},
sampleDocument: { name: 'John' },
schemaMetadata: { maxNestingDepth: 1, validationRules: null },
},
fakerSchemaGeneration: {
status: 'idle',
},
mockDataGenerator: {
isModalOpen: isOpen,
currentStep: currentStep,
},
};

const store = createStore(
collectionTabReducer,
initialState,
applyMiddleware(thunk.withExtraArgument(mockServices))
);

return render(
<MockDataGeneratorModal
isOpen={isOpen}
onClose={onClose}
currentStep={currentStep}
onNextStep={onNextStep}
onPreviousStep={onPreviousStep}
/>
<Provider store={store}>
<MockDataGeneratorModal />
</Provider>
);
}

it('renders the modal when isOpen is true', () => {
renderModal();
function createMockServices() {
return {
dataService: {},
atlasAiService: {
getMockDataSchema: () => {
return Promise.resolve({
contents: {
fields: [],
},
});
},
},
workspaces: {},
localAppRegistry: {},
experimentationServices: {},
connectionInfoRef: { current: {} },
logger: {
log: {
warn: () => {},
error: () => {},
},
debug: () => {},
mongoLogId: () => 'mock-id',
},
preferences: { getPreferences: () => ({}) },
fakerSchemaGenerationAbortControllerRef: { current: undefined },
};
}

expect(screen.getByTestId('generate-mock-data-modal')).to.exist;
});
describe('generally', () => {
it('renders the modal when isOpen is true', () => {
renderModal();

it('does not render the modal when isOpen is false', () => {
renderModal({ isOpen: false });
expect(screen.getByTestId('generate-mock-data-modal')).to.exist;
});

expect(screen.queryByTestId('generate-mock-data-modal')).to.not.exist;
});
it('does not render the modal when isOpen is false', () => {
renderModal({ isOpen: false });

it('calls onClose when the modal is closed', () => {
renderModal();
expect(screen.queryByTestId('generate-mock-data-modal')).to.not.exist;
});

screen.getByLabelText('Close modal').click();
it('closes the modal when the close button is clicked', async () => {
renderModal();

expect(onClose.calledOnce).to.be.true;
});
expect(screen.getByTestId('generate-mock-data-modal')).to.exist;
userEvent.click(screen.getByLabelText('Close modal'));
await waitFor(
() =>
expect(screen.queryByTestId('generate-mock-data-modal')).to.not.exist
);
});

it('calls onClose when the cancel button is clicked', () => {
renderModal();
it('closes the modal when the cancel button is clicked', async () => {
renderModal();

screen.getByText('Cancel').click();
expect(screen.getByTestId('generate-mock-data-modal')).to.exist;
userEvent.click(screen.getByText('Cancel'));
await waitFor(
() =>
expect(screen.queryByTestId('generate-mock-data-modal')).to.not.exist
);
});

function createMockServicesWithSlowAiRequest() {
let abortSignalReceived = false;
let rejectPromise: (reason?: any) => void;
const rejectedPromise = new Promise((_resolve, reject) => {
rejectPromise = reject;
});

const baseMockServices = createMockServices();

const mockAiService = {
...baseMockServices.atlasAiService,
getMockDataSchema: (request: any) => {
if (request?.signal) {
request.signal.addEventListener('abort', () => {
abortSignalReceived = true;
rejectPromise(new Error('Request aborted'));
});
}
return rejectedPromise;
},
getAbortSignalReceived: () => abortSignalReceived,
};

return {
...baseMockServices,
atlasAiService: mockAiService,
};
}

it('cancels in-flight faker mapping requests when the cancel button is clicked', async () => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nice coverage!

const mockServices = createMockServicesWithSlowAiRequest();
renderModal({ mockServices: mockServices as any });

expect(screen.getByTestId('raw-schema-confirmation')).to.exist;
userEvent.click(screen.getByText('Confirm'));

await waitFor(() => {
expect(screen.getByTestId('faker-schema-editor')).to.exist;
});

expect(onClose.calledOnce).to.be.true;
userEvent.click(screen.getByText('Cancel'));

expect(mockServices.atlasAiService.getAbortSignalReceived()).to.be.true;
});

it('cancels in-flight faker mapping requests when the back button is clicked', async () => {
const mockServices = createMockServicesWithSlowAiRequest();
renderModal({ mockServices: mockServices as any });

expect(screen.getByTestId('raw-schema-confirmation')).to.exist;
userEvent.click(screen.getByText('Confirm'));

await waitFor(() => {
expect(screen.getByTestId('faker-schema-editor')).to.exist;
});

userEvent.click(screen.getByText('Back'));

expect(mockServices.atlasAiService.getAbortSignalReceived()).to.be.true;
});
});

it('disables the Back button on the first step', () => {
renderModal();
describe('on the schema confirmation step', () => {
it('disables the Back button', () => {
renderModal();

expect(
screen
.getByRole('button', { name: 'Back' })
.getAttribute('aria-disabled')
).to.equal('true');
});

it('renders the faker schema editor when the confirm button is clicked', async () => {
renderModal();

expect(screen.getByTestId('raw-schema-confirmation')).to.exist;
expect(screen.queryByTestId('faker-schema-editor')).to.not.exist;
userEvent.click(screen.getByText('Confirm'));
await waitFor(() => {
expect(screen.queryByTestId('raw-schema-confirmation')).to.not.exist;
expect(screen.getByTestId('faker-schema-editor')).to.exist;
});
});

it('stays on the current step when an error is encountered during faker schema generation', async () => {
const mockServices = createMockServices();
mockServices.atlasAiService.getMockDataSchema = () =>
Promise.reject('faker schema generation failed');
renderModal({ mockServices });

expect(screen.getByTestId('raw-schema-confirmation')).to.exist;
expect(screen.queryByTestId('faker-schema-editor')).to.not.exist;
userEvent.click(screen.getByText('Confirm'));
await waitFor(() => {
expect(screen.getByTestId('raw-schema-confirmation')).to.exist;
expect(screen.queryByTestId('faker-schema-editor')).to.not.exist;
});

// todo: assert a user-friendly error is displayed (CLOUDP-333852)
});

expect(
screen.getByRole('button', { name: 'Back' }).getAttribute('aria-disabled')
).to.equal('true');
// todo: assert that closing then re-opening the modal after an LLM err removes the err message
});

describe('when rendering the modal in a specific step', () => {
const steps = Object.keys(
StepButtonLabelMap
) as unknown as MockDataGeneratorStep[];

// note: these tests can be removed after every modal step is implemented
steps.forEach((currentStep) => {
it(`renders the button with the correct label when the user is in step "${currentStep}"`, () => {
renderModal({ currentStep });
Expand Down
Loading
Loading