diff --git a/package-lock.json b/package-lock.json index 1a10f197185..dc29161bea3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6732,6 +6732,28 @@ "@leafygreen-ui/leafygreen-provider": "^4.0.2" } }, + "node_modules/@leafygreen-ui/copyable": { + "version": "10.0.14", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/copyable/-/copyable-10.0.14.tgz", + "integrity": "sha512-O4dstObiN04Zjrd4Z10ratWZAi7pnb6gpML/HQnkAxR+0OwzKOvrR6XOQ2/3IzlLfIiY1TUHIbjavpHy/ppqVw==", + "license": "Apache-2.0", + "dependencies": { + "@leafygreen-ui/button": "^23.1.6", + "@leafygreen-ui/emotion": "^4.1.1", + "@leafygreen-ui/hooks": "^8.4.1", + "@leafygreen-ui/icon": "^13.4.0", + "@leafygreen-ui/lib": "^14.2.0", + "@leafygreen-ui/palette": "^4.1.4", + "@leafygreen-ui/tokens": "^2.12.2", + "@leafygreen-ui/tooltip": "^13.0.13", + "@leafygreen-ui/typography": "^20.1.9", + "clipboard": "^2.0.6", + "polished": "^4.2.2" + }, + "peerDependencies": { + "@leafygreen-ui/leafygreen-provider": "^4.0.7" + } + }, "node_modules/@leafygreen-ui/descendants": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@leafygreen-ui/descendants/-/descendants-2.1.5.tgz", @@ -47813,6 +47835,7 @@ "@leafygreen-ui/code": "^16.0.2", "@leafygreen-ui/combobox": "^11.0.2", "@leafygreen-ui/confirmation-modal": "^6.0.2", + "@leafygreen-ui/copyable": "^10.0.14", "@leafygreen-ui/descendants": "^2.1.0", "@leafygreen-ui/emotion": "^4.0.9", "@leafygreen-ui/guide-cue": "^7.0.2", @@ -58479,6 +58502,24 @@ "@leafygreen-ui/typography": "^20.0.2" } }, + "@leafygreen-ui/copyable": { + "version": "10.0.14", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/copyable/-/copyable-10.0.14.tgz", + "integrity": "sha512-O4dstObiN04Zjrd4Z10ratWZAi7pnb6gpML/HQnkAxR+0OwzKOvrR6XOQ2/3IzlLfIiY1TUHIbjavpHy/ppqVw==", + "requires": { + "@leafygreen-ui/button": "^22.0.2", + "@leafygreen-ui/emotion": "^4.0.9", + "@leafygreen-ui/hooks": "^8.3.4", + "@leafygreen-ui/icon": "^13.1.2", + "@leafygreen-ui/lib": "^15.2.0", + "@leafygreen-ui/palette": "^4.1.3", + "@leafygreen-ui/tokens": "^2.11.3", + "@leafygreen-ui/tooltip": "^13.0.13", + "@leafygreen-ui/typography": "^20.0.2", + "clipboard": "^2.0.6", + "polished": "^4.2.2" + } + }, "@leafygreen-ui/descendants": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@leafygreen-ui/descendants/-/descendants-2.1.5.tgz", @@ -61267,6 +61308,7 @@ "@leafygreen-ui/code": "^16.0.2", "@leafygreen-ui/combobox": "^11.0.2", "@leafygreen-ui/confirmation-modal": "^6.0.2", + "@leafygreen-ui/copyable": "^10.0.14", "@leafygreen-ui/descendants": "^2.1.0", "@leafygreen-ui/emotion": "^4.0.9", "@leafygreen-ui/guide-cue": "^7.0.2", diff --git a/packages/compass-collection/src/components/mock-data-generator-modal/mock-data-generator-modal.spec.tsx b/packages/compass-collection/src/components/mock-data-generator-modal/mock-data-generator-modal.spec.tsx index 9d0dd164906..ddd38a9fdd7 100644 --- a/packages/compass-collection/src/components/mock-data-generator-modal/mock-data-generator-modal.spec.tsx +++ b/packages/compass-collection/src/components/mock-data-generator-modal/mock-data-generator-modal.spec.tsx @@ -2,7 +2,7 @@ import { expect } from 'chai'; import React from 'react'; import { screen, - render, + renderWithActiveConnection, waitFor, userEvent, } from '@mongodb-js/testing-library-compass'; @@ -14,12 +14,19 @@ import { MockDataGeneratorStep } from './types'; import { StepButtonLabelMap } from './constants'; import type { CollectionState } from '../../modules/collection-tab'; import { default as collectionTabReducer } from '../../modules/collection-tab'; +import type { ConnectionInfo } from '@mongodb-js/connection-info'; describe('MockDataGeneratorModal', () => { - function renderModal({ + async function renderModal({ isOpen = true, currentStep = MockDataGeneratorStep.SCHEMA_CONFIRMATION, mockServices = createMockServices(), + connectionInfo, + }: { + isOpen?: boolean; + currentStep?: MockDataGeneratorStep; + mockServices?: any; + connectionInfo?: ConnectionInfo; } = {}) { const initialState: CollectionState = { workspaceTabId: 'test-workspace-tab-id', @@ -52,10 +59,11 @@ describe('MockDataGeneratorModal', () => { applyMiddleware(thunk.withExtraArgument(mockServices)) ); - return render( + return await renderWithActiveConnection( - + , + connectionInfo ); } @@ -89,20 +97,20 @@ describe('MockDataGeneratorModal', () => { } describe('generally', () => { - it('renders the modal when isOpen is true', () => { - renderModal(); + it('renders the modal when isOpen is true', async () => { + await renderModal(); expect(screen.getByTestId('generate-mock-data-modal')).to.exist; }); - it('does not render the modal when isOpen is false', () => { - renderModal({ isOpen: false }); + it('does not render the modal when isOpen is false', async () => { + await renderModal({ isOpen: false }); expect(screen.queryByTestId('generate-mock-data-modal')).to.not.exist; }); it('closes the modal when the close button is clicked', async () => { - renderModal(); + await renderModal(); expect(screen.getByTestId('generate-mock-data-modal')).to.exist; userEvent.click(screen.getByLabelText('Close modal')); @@ -113,7 +121,7 @@ describe('MockDataGeneratorModal', () => { }); it('closes the modal when the cancel button is clicked', async () => { - renderModal(); + await renderModal(); expect(screen.getByTestId('generate-mock-data-modal')).to.exist; userEvent.click(screen.getByText('Cancel')); @@ -154,7 +162,7 @@ describe('MockDataGeneratorModal', () => { it('cancels in-flight faker mapping requests when the cancel button is clicked', async () => { const mockServices = createMockServicesWithSlowAiRequest(); - renderModal({ mockServices: mockServices as any }); + await renderModal({ mockServices: mockServices as any }); expect(screen.getByTestId('raw-schema-confirmation')).to.exist; userEvent.click(screen.getByText('Confirm')); @@ -170,7 +178,7 @@ describe('MockDataGeneratorModal', () => { it('cancels in-flight faker mapping requests when the back button is clicked after schema confirmation', async () => { const mockServices = createMockServicesWithSlowAiRequest(); - renderModal({ mockServices: mockServices as any }); + await renderModal({ mockServices: mockServices as any }); expect(screen.getByTestId('raw-schema-confirmation')).to.exist; userEvent.click(screen.getByText('Confirm')); @@ -186,8 +194,8 @@ describe('MockDataGeneratorModal', () => { }); describe('on the schema confirmation step', () => { - it('disables the Back button', () => { - renderModal(); + it('disables the Back button', async () => { + await renderModal(); expect( screen @@ -197,7 +205,7 @@ describe('MockDataGeneratorModal', () => { }); it('renders the faker schema editor when the confirm button is clicked', async () => { - renderModal(); + await renderModal(); expect(screen.getByTestId('raw-schema-confirmation')).to.exist; expect(screen.queryByTestId('faker-schema-editor')).to.not.exist; @@ -212,7 +220,7 @@ describe('MockDataGeneratorModal', () => { const mockServices = createMockServices(); mockServices.atlasAiService.getMockDataSchema = () => Promise.reject('faker schema generation failed'); - renderModal({ mockServices }); + await renderModal({ mockServices }); expect(screen.getByTestId('raw-schema-confirmation')).to.exist; expect(screen.queryByTestId('faker-schema-editor')).to.not.exist; @@ -228,6 +236,92 @@ describe('MockDataGeneratorModal', () => { // todo: assert that closing then re-opening the modal after an LLM err removes the err message }); + describe('on the generate data step', () => { + it('enables the Back button', async () => { + await renderModal({ currentStep: MockDataGeneratorStep.GENERATE_DATA }); + + expect( + screen + .getByRole('button', { name: 'Back' }) + .getAttribute('aria-disabled') + ).to.not.equal('true'); + }); + + it('renders the main sections: Prerequisites, steps, and Resources', async () => { + await renderModal({ currentStep: MockDataGeneratorStep.GENERATE_DATA }); + + expect(screen.getByText('Prerequisites')).to.exist; + expect(screen.getByText('1. Create a .js file with the following script')) + .to.exist; + expect(screen.getByText('2. Run the script with')).to.exist; + expect(screen.getByText('Resources')).to.exist; + }); + + it('closes the modal when the Done button is clicked', async () => { + await renderModal({ currentStep: MockDataGeneratorStep.GENERATE_DATA }); + + expect(screen.getByTestId('generate-mock-data-modal')).to.exist; + userEvent.click(screen.getByText('Done')); + await waitFor( + () => + expect(screen.queryByTestId('generate-mock-data-modal')).to.not.exist + ); + }); + + it('renders the Database Users link with correct URL when projectId is available', async () => { + const atlasConnectionInfo: ConnectionInfo = { + id: 'test-atlas-connection', + connectionOptions: { connectionString: 'mongodb://localhost:27017' }, + atlasMetadata: { + orgId: 'test-org', + projectId: 'test-project-123', + clusterName: 'test-cluster', + clusterUniqueId: 'test-cluster-unique-id', + clusterType: 'REPLICASET' as const, + clusterState: 'IDLE' as const, + metricsId: 'test-metrics-id', + metricsType: 'replicaSet' as const, + regionalBaseUrl: null, + instanceSize: 'M10', + supports: { + globalWrites: false, + rollingIndexes: true, + }, + }, + }; + + await renderModal({ + currentStep: MockDataGeneratorStep.GENERATE_DATA, + connectionInfo: atlasConnectionInfo, + }); + + const databaseUsersLink = screen.getByRole('link', { + name: 'Access your Database Users', + }); + expect(databaseUsersLink.getAttribute('href')).to.equal( + '/v2/test-project-123#/security/database/users' + ); + }); + + it('does not render the Database Users link when projectId is not available', async () => { + const nonAtlasConnectionInfo: ConnectionInfo = { + id: 'test-local-connection', + connectionOptions: { connectionString: 'mongodb://localhost:27017' }, + // No atlasMetadata means no projectId + }; + + await renderModal({ + currentStep: MockDataGeneratorStep.GENERATE_DATA, + connectionInfo: nonAtlasConnectionInfo, + }); + + expect(screen.queryByRole('link', { name: 'Access your Database Users' })) + .to.not.exist; + }); + + // todo: assert that the generated script is displayed in the code block + }); + describe('when rendering the modal in a specific step', () => { const steps = Object.keys( StepButtonLabelMap @@ -235,8 +329,8 @@ describe('MockDataGeneratorModal', () => { // 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 }); + it(`renders the button with the correct label when the user is in step "${currentStep}"`, async () => { + await renderModal({ currentStep }); expect(screen.getByTestId('next-step-button')).to.have.text( StepButtonLabelMap[currentStep] ); diff --git a/packages/compass-collection/src/components/mock-data-generator-modal/mock-data-generator-modal.tsx b/packages/compass-collection/src/components/mock-data-generator-modal/mock-data-generator-modal.tsx index d09861a6b42..eadb8b13e19 100644 --- a/packages/compass-collection/src/components/mock-data-generator-modal/mock-data-generator-modal.tsx +++ b/packages/compass-collection/src/components/mock-data-generator-modal/mock-data-generator-modal.tsx @@ -23,6 +23,15 @@ import { } from '../../modules/collection-tab'; import { default as SchemaConfirmationScreen } from './raw-schema-confirmation'; import FakerSchemaEditor from './faker-schema-editor'; +import ScriptScreen from './script-screen'; + +const STEP_TO_STEP_CONTENT: Record = { + [MockDataGeneratorStep.SCHEMA_CONFIRMATION]: , + [MockDataGeneratorStep.SCHEMA_EDITOR]: , + [MockDataGeneratorStep.DOCUMENT_COUNT]: <>, // TODO: Implement as part of CLOUDP-333856 + [MockDataGeneratorStep.PREVIEW_DATA]: <>, // TODO: Implement as part of CLOUDP-333857 + [MockDataGeneratorStep.GENERATE_DATA]: , +}; const footerStyles = css` flex-direction: row; @@ -62,18 +71,9 @@ const MockDataGeneratorModal = ({ } }; - let stepContent: React.ReactNode; - - if (currentStep === MockDataGeneratorStep.SCHEMA_CONFIRMATION) { - stepContent = ; - } - - if (currentStep === MockDataGeneratorStep.SCHEMA_EDITOR) { - stepContent = ; - } - return ( { if (!open) { @@ -84,8 +84,9 @@ const MockDataGeneratorModal = ({ > - {stepContent} -
+
+ {STEP_TO_STEP_CONTENT[currentStep]} +