Skip to content

Commit 5905c31

Browse files
committed
feat(compass-collection): Document Count Screen - Mock Data Generator CLOUDP-333856
1 parent 112ee18 commit 5905c31

File tree

8 files changed

+172
-37
lines changed

8 files changed

+172
-37
lines changed

packages/compass-collection/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
"mongodb-collection-model": "^5.34.2",
6868
"mongodb-ns": "^3.0.1",
6969
"mongodb-schema": "^12.6.3",
70+
"numeral": "^2.0.6",
7071
"react": "^17.0.2",
7172
"react-redux": "^8.1.3",
7273
"redux": "^4.2.1",

packages/compass-collection/src/components/mock-data-generator-modal/constants.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,6 @@ export const StepButtonLabelMap = {
77
[MockDataGeneratorStep.PREVIEW_DATA]: 'Generate Script',
88
[MockDataGeneratorStep.GENERATE_DATA]: 'Done',
99
} as const;
10+
11+
export const DEFAULT_DOCUMENT_COUNT = 1000;
12+
export const MAX_DOCUMENT_COUNT = 100000;

packages/compass-collection/src/components/mock-data-generator-modal/document-count-screen.tsx

Lines changed: 43 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import {
77
} from '@mongodb-js/compass-components';
88
import React, { useMemo } from 'react';
99
import { connect } from 'react-redux';
10-
import { CollectionState } from '../../modules/collection-tab';
11-
import { SchemaAnalysisState } from '../../schema-analysis-types';
10+
import type { CollectionState } from '../../modules/collection-tab';
11+
import type { SchemaAnalysisState } from '../../schema-analysis-types';
1212
import numeral from 'numeral';
1313
import { DEFAULT_DOCUMENT_COUNT, MAX_DOCUMENT_COUNT } from './constants';
1414

@@ -42,6 +42,15 @@ const formatBytes = (bytes: number) => {
4242
return numeral(bytes).format(precision + 'b');
4343
};
4444

45+
type ErrorState =
46+
| {
47+
state: 'error';
48+
message: string;
49+
}
50+
| {
51+
state: 'none';
52+
};
53+
4554
interface OwnProps {
4655
documentCount: number;
4756
onDocumentCountChange: (documentCount: number) => void;
@@ -56,23 +65,30 @@ const DocumentCountScreen = ({
5665
onDocumentCountChange,
5766
schemaAnalysisState,
5867
}: Props) => {
59-
const estimatedDiskSize = useMemo(() => {
60-
return schemaAnalysisState.status === 'complete'
61-
? schemaAnalysisState.schemaMetadata.avgDocumentSize * documentCount
62-
: 0;
63-
}, [schemaAnalysisState, documentCount]);
64-
65-
const errorState = useMemo(() => {
66-
return documentCount < 1 || documentCount > MAX_DOCUMENT_COUNT
67-
? 'error'
68-
: 'none';
69-
}, [documentCount]);
70-
71-
const errorMessage = useMemo(() => {
72-
return documentCount < 1 || documentCount > MAX_DOCUMENT_COUNT
73-
? 'Document count must be between 1 and 100000'
74-
: undefined;
75-
}, [documentCount]);
68+
const estimatedDiskSize = useMemo(
69+
() =>
70+
schemaAnalysisState.status === 'complete' &&
71+
schemaAnalysisState.schemaMetadata.avgDocumentSize
72+
? formatBytes(
73+
schemaAnalysisState.schemaMetadata.avgDocumentSize * documentCount
74+
)
75+
: 'Not available',
76+
[schemaAnalysisState, documentCount]
77+
);
78+
79+
const isOutOfRange = documentCount < 1 || documentCount > MAX_DOCUMENT_COUNT;
80+
81+
const errorState: ErrorState = useMemo(() => {
82+
if (isOutOfRange) {
83+
return {
84+
state: 'error',
85+
message: `Document count must be between 1 and ${MAX_DOCUMENT_COUNT}`,
86+
};
87+
}
88+
return {
89+
state: 'none',
90+
};
91+
}, [isOutOfRange]);
7692

7793
return schemaAnalysisState.status === 'complete' ? (
7894
<div>
@@ -92,14 +108,14 @@ const DocumentCountScreen = ({
92108
onChange={(e) => onDocumentCountChange(Number(e.target.value))}
93109
min={1}
94110
max={MAX_DOCUMENT_COUNT}
95-
state={errorState}
96-
errorMessage={errorMessage}
111+
state={errorState.state}
112+
errorMessage={
113+
errorState.state === 'error' ? errorState.message : undefined
114+
}
97115
/>
98116
<div>
99117
<Body className={boldStyles}>Estimated Disk Size</Body>
100-
<Body className={estimatedDiskSizeStyles}>
101-
{formatBytes(estimatedDiskSize)}
102-
</Body>
118+
<Body className={estimatedDiskSizeStyles}>{estimatedDiskSize}</Body>
103119
</div>
104120
</div>
105121
</div>
@@ -109,13 +125,9 @@ const DocumentCountScreen = ({
109125
);
110126
};
111127

112-
const mapStateToProps = (state: CollectionState, _ownProps: OwnProps) => {
113-
const schemaAnalysisState = state.schemaAnalysis;
114-
115-
return {
116-
schemaAnalysisState,
117-
};
118-
};
128+
const mapStateToProps = (state: CollectionState) => ({
129+
schemaAnalysisState: state.schemaAnalysis,
130+
});
119131

120132
const ConnectedDocumentCountScreen = connect(
121133
mapStateToProps,

packages/compass-collection/src/components/mock-data-generator-modal/mock-data-generator-modal.spec.tsx

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,11 @@ const defaultSchemaAnalysisState: SchemaAnalysisState = {
2929
},
3030
},
3131
sampleDocument: { name: 'John' },
32-
schemaMetadata: { maxNestingDepth: 1, validationRules: null },
32+
schemaMetadata: {
33+
maxNestingDepth: 1,
34+
validationRules: null,
35+
avgDocumentSize: undefined,
36+
},
3337
};
3438

3539
describe('MockDataGeneratorModal', () => {
@@ -627,6 +631,85 @@ describe('MockDataGeneratorModal', () => {
627631
});
628632
});
629633

634+
describe('on the document count step', () => {
635+
it('displays the correct step title and description', async () => {
636+
await renderModal({ currentStep: MockDataGeneratorStep.DOCUMENT_COUNT });
637+
638+
expect(screen.getByText('Specify Number of Documents to Generate')).to
639+
.exist;
640+
641+
expect(
642+
screen.getByText(
643+
/Indicate the amount of documents you want to generate below./
644+
)
645+
).to.exist;
646+
expect(screen.getByText(/Note: We have defaulted to 1000./)).to.exist;
647+
});
648+
649+
it('displays the default document count when the user does not enter a document count', async () => {
650+
await renderModal({ currentStep: MockDataGeneratorStep.DOCUMENT_COUNT });
651+
652+
expect(
653+
screen.getByLabelText('Documents to generate in current collection')
654+
).to.have.value('1000');
655+
});
656+
657+
it('disables the Next button and shows an error message when the document count is greater than 100000', async () => {
658+
await renderModal({ currentStep: MockDataGeneratorStep.DOCUMENT_COUNT });
659+
660+
userEvent.type(
661+
screen.getByLabelText('Documents to generate in current collection'),
662+
'100001'
663+
);
664+
665+
expect(screen.getByText('Document count must be between 1 and 100000')).to
666+
.exist;
667+
expect(
668+
screen.getByTestId('next-step-button').getAttribute('aria-disabled')
669+
).to.equal('true');
670+
});
671+
672+
it('displays "Not available" when the avgDocumentSize is undefined', async () => {
673+
await renderModal({
674+
currentStep: MockDataGeneratorStep.DOCUMENT_COUNT,
675+
schemaAnalysis: {
676+
...defaultSchemaAnalysisState,
677+
schemaMetadata: {
678+
...defaultSchemaAnalysisState.schemaMetadata,
679+
avgDocumentSize: undefined,
680+
},
681+
},
682+
});
683+
684+
expect(screen.getByText('Estimated Disk Size')).to.exist;
685+
expect(screen.getByText('Not available')).to.exist;
686+
});
687+
688+
it('displays the correct estimated disk size when a valid document count is entered', async () => {
689+
await renderModal({
690+
currentStep: MockDataGeneratorStep.DOCUMENT_COUNT,
691+
schemaAnalysis: {
692+
...defaultSchemaAnalysisState,
693+
schemaMetadata: {
694+
...defaultSchemaAnalysisState.schemaMetadata,
695+
avgDocumentSize: 100, // 100 bytes
696+
},
697+
},
698+
});
699+
700+
expect(screen.getByText('Estimated Disk Size')).to.exist;
701+
const documentCountInput = screen.getByLabelText(
702+
'Documents to generate in current collection'
703+
);
704+
userEvent.clear(documentCountInput);
705+
userEvent.type(documentCountInput, '1000');
706+
expect(screen.getByText('100.0KB')).to.exist;
707+
userEvent.clear(documentCountInput);
708+
userEvent.type(documentCountInput, '2000');
709+
expect(screen.getByText('200.0KB')).to.exist;
710+
});
711+
});
712+
630713
describe('on the generate data step', () => {
631714
it('enables the Back button', async () => {
632715
await renderModal({ currentStep: MockDataGeneratorStep.GENERATE_DATA });

packages/compass-collection/src/components/mock-data-generator-modal/mock-data-generator-modal.tsx

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ import {
1414
} from '@mongodb-js/compass-components';
1515

1616
import { type MockDataGeneratorState, MockDataGeneratorStep } from './types';
17-
import { StepButtonLabelMap } from './constants';
17+
import {
18+
DEFAULT_DOCUMENT_COUNT,
19+
MAX_DOCUMENT_COUNT,
20+
StepButtonLabelMap,
21+
} from './constants';
1822
import type { CollectionState } from '../../modules/collection-tab';
1923
import {
2024
mockDataGeneratorModalClosed,
@@ -25,6 +29,7 @@ import {
2529
import RawSchemaConfirmationScreen from './raw-schema-confirmation-screen';
2630
import FakerSchemaEditorScreen from './faker-schema-editor-screen';
2731
import ScriptScreen from './script-screen';
32+
import DocumentCountScreen from './document-count-screen';
2833

2934
const footerStyles = css`
3035
flex-direction: row;
@@ -65,6 +70,9 @@ const MockDataGeneratorModal = ({
6570
}: Props) => {
6671
const [isSchemaConfirmed, setIsSchemaConfirmed] =
6772
React.useState<boolean>(false);
73+
const [documentCount, setDocumentCount] = React.useState<number>(
74+
DEFAULT_DOCUMENT_COUNT
75+
);
6876

6977
const modalBodyContent = useMemo(() => {
7078
switch (currentStep) {
@@ -79,16 +87,32 @@ const MockDataGeneratorModal = ({
7987
/>
8088
);
8189
case MockDataGeneratorStep.DOCUMENT_COUNT:
82-
return <></>; // TODO: CLOUDP-333856
90+
return (
91+
<DocumentCountScreen
92+
documentCount={documentCount}
93+
onDocumentCountChange={setDocumentCount}
94+
/>
95+
);
8396
case MockDataGeneratorStep.PREVIEW_DATA:
8497
return <></>; // TODO: CLOUDP-333857
8598
case MockDataGeneratorStep.GENERATE_DATA:
8699
return <ScriptScreen />;
87100
}
88-
}, [currentStep, fakerSchemaGenerationState, isSchemaConfirmed]);
101+
}, [
102+
currentStep,
103+
fakerSchemaGenerationState,
104+
isSchemaConfirmed,
105+
documentCount,
106+
setDocumentCount,
107+
]);
89108

90109
const isNextButtonDisabled =
91-
currentStep === MockDataGeneratorStep.SCHEMA_EDITOR && !isSchemaConfirmed;
110+
(currentStep === MockDataGeneratorStep.SCHEMA_EDITOR &&
111+
!isSchemaConfirmed) ||
112+
(currentStep === MockDataGeneratorStep.DOCUMENT_COUNT &&
113+
documentCount < 1) ||
114+
(currentStep === MockDataGeneratorStep.DOCUMENT_COUNT &&
115+
documentCount > MAX_DOCUMENT_COUNT);
92116

93117
const handleNextClick = () => {
94118
if (currentStep === MockDataGeneratorStep.GENERATE_DATA) {

packages/compass-collection/src/modules/collection-tab.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
processSchema,
3434
ProcessSchemaUnsupportedStateError,
3535
} from '../transform-schema-to-field-info';
36+
import type { Collection } from '@mongodb-js/compass-app-stores/provider';
3637
import type { Document, MongoError } from 'mongodb';
3738
import { MockDataGeneratorStep } from '../components/mock-data-generator-modal/types';
3839
import type {
@@ -94,6 +95,7 @@ type CollectionThunkAction<R, A extends AnyAction = AnyAction> = ThunkAction<
9495
connectionInfoRef: ConnectionInfoRef;
9596
fakerSchemaGenerationAbortControllerRef: { current?: AbortController };
9697
schemaAnalysisAbortControllerRef: { current?: AbortController };
98+
collection: Collection;
9799
},
98100
A
99101
>;
@@ -147,6 +149,7 @@ interface SchemaAnalysisFinishedAction {
147149
schemaMetadata: {
148150
maxNestingDepth: number;
149151
validationRules: Document | null;
152+
avgDocumentSize: number | undefined;
150153
};
151154
}
152155

@@ -561,7 +564,13 @@ export const analyzeCollectionSchema = (): CollectionThunkAction<
561564
return async (
562565
dispatch,
563566
getState,
564-
{ dataService, preferences, logger, schemaAnalysisAbortControllerRef }
567+
{
568+
dataService,
569+
preferences,
570+
logger,
571+
schemaAnalysisAbortControllerRef,
572+
collection: collectionModel,
573+
}
565574
) => {
566575
const { schemaAnalysis, namespace } = getState();
567576
const analysisStatus = schemaAnalysis.status;
@@ -638,6 +647,7 @@ export const analyzeCollectionSchema = (): CollectionThunkAction<
638647
const schemaMetadata = {
639648
maxNestingDepth,
640649
validationRules,
650+
avgDocumentSize: collectionModel.avg_document_size,
641651
};
642652

643653
// Final check before dispatching results

packages/compass-collection/src/schema-analysis-types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export type SchemaAnalysisCompletedState = {
5858
schemaMetadata: {
5959
maxNestingDepth: number;
6060
validationRules: Document | null;
61+
avgDocumentSize: number | undefined;
6162
};
6263
};
6364

packages/compass-collection/src/stores/collection-tab.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ export function activatePlugin(
109109
applyMiddleware(
110110
thunk.withExtraArgument({
111111
dataService,
112+
collection: collectionModel,
112113
atlasAiService,
113114
workspaces,
114115
localAppRegistry,

0 commit comments

Comments
 (0)