Skip to content

Commit d1fd644

Browse files
authored
feat(compass-collection): Mock Data Generator Document Count and Confirmation Screen Items - CLOUDP-352964 (#7495)
* WIP * Remove banner * Raise error banner * Document count input * Comment * Comments * Decimal * Refactor validation * WIP * WIP * Comment * parseInt to Number
1 parent d8882fc commit d1fd644

File tree

8 files changed

+185
-65
lines changed

8 files changed

+185
-65
lines changed

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

Lines changed: 30 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import React, { useMemo } from 'react';
1010
import { connect } from 'react-redux';
1111
import type { CollectionState } from '../../modules/collection-tab';
1212
import type { SchemaAnalysisState } from '../../schema-analysis-types';
13-
import { DEFAULT_DOCUMENT_COUNT, MAX_DOCUMENT_COUNT } from './constants';
13+
import { MAX_DOCUMENT_COUNT } from './constants';
14+
import { validateDocumentCount } from './utils';
1415
import { useTelemetry } from '@mongodb-js/compass-telemetry/provider';
1516

1617
const BYTE_PRECISION_THRESHOLD = 1000;
@@ -45,18 +46,9 @@ const formatBytes = (bytes: number) => {
4546
return compactBytes(bytes, true, decimals);
4647
};
4748

48-
type ErrorState =
49-
| {
50-
state: 'error';
51-
message: string;
52-
}
53-
| {
54-
state: 'none';
55-
};
56-
5749
interface OwnProps {
58-
documentCount: number;
59-
onDocumentCountChange: (documentCount: number) => void;
50+
documentCount: string;
51+
onDocumentCountChange: (documentCount: string) => void;
6052
}
6153

6254
interface Props extends OwnProps {
@@ -69,37 +61,41 @@ const DocumentCountScreen = ({
6961
schemaAnalysisState,
7062
}: Props) => {
7163
const track = useTelemetry();
72-
const estimatedDiskSize = useMemo(
73-
() =>
74-
schemaAnalysisState.status === 'complete' &&
75-
schemaAnalysisState.schemaMetadata.avgDocumentSize
76-
? formatBytes(
77-
schemaAnalysisState.schemaMetadata.avgDocumentSize * documentCount
78-
)
79-
: 'Not available',
80-
[schemaAnalysisState, documentCount]
81-
);
64+
const validationState = validateDocumentCount(documentCount);
65+
const estimatedDiskSize = useMemo(() => {
66+
if (
67+
!validationState.isValid ||
68+
schemaAnalysisState.status !== 'complete' ||
69+
!schemaAnalysisState.schemaMetadata.avgDocumentSize ||
70+
!validationState.parsedValue
71+
) {
72+
return 'Not available';
73+
}
8274

83-
const isOutOfRange = documentCount < 1 || documentCount > MAX_DOCUMENT_COUNT;
75+
return formatBytes(
76+
schemaAnalysisState.schemaMetadata.avgDocumentSize *
77+
validationState.parsedValue
78+
);
79+
}, [validationState, schemaAnalysisState]);
8480

85-
const errorState: ErrorState = useMemo(() => {
86-
if (isOutOfRange) {
87-
return {
88-
state: 'error',
89-
message: `Document count must be between 1 and ${MAX_DOCUMENT_COUNT}`,
90-
};
81+
const errorState = useMemo(() => {
82+
if (validationState.isValid) {
83+
return { state: 'none' as const };
9184
}
9285
return {
93-
state: 'none',
86+
state: 'error' as const,
87+
message: validationState.errorMessage,
9488
};
95-
}, [isOutOfRange]);
89+
}, [validationState.isValid, validationState.errorMessage]);
9690

9791
const handleDocumentCountChange = (
9892
event: React.ChangeEvent<HTMLInputElement>
9993
) => {
100-
const value = parseInt(event.target.value, 10);
94+
onDocumentCountChange(event.target.value);
95+
96+
// Track telemetry for valid numeric values
97+
const value = Number(event.target.value);
10198
if (!isNaN(value)) {
102-
onDocumentCountChange(value);
10399
track('Mock Data Document Count Changed', {
104100
document_count: value,
105101
});
@@ -113,14 +109,12 @@ const DocumentCountScreen = ({
113109
</Body>
114110
<Body className={descriptionStyles}>
115111
Indicate the amount of documents you want to generate below.
116-
<br />
117-
Note: We have defaulted to {DEFAULT_DOCUMENT_COUNT}.
118112
</Body>
119113
<div className={inputContainerStyles}>
120114
<TextInput
121115
label="Documents to generate in current collection"
122116
type="number"
123-
value={documentCount.toString()}
117+
value={documentCount}
124118
onChange={handleDocumentCountChange}
125119
min={1}
126120
max={MAX_DOCUMENT_COUNT}

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

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
DEFAULT_CONNECTION_STRING_FALLBACK,
1818
MOCK_DATA_GENERATOR_STEP_TO_NEXT_STEP_MAP,
1919
StepButtonLabelMap,
20+
DEFAULT_DOCUMENT_COUNT,
2021
} from './constants';
2122
import type { CollectionState } from '../../modules/collection-tab';
2223
import { default as collectionTabReducer } from '../../modules/collection-tab';
@@ -53,6 +54,7 @@ describe('MockDataGeneratorModal', () => {
5354
schemaAnalysis = defaultSchemaAnalysisState,
5455
fakerSchemaGeneration = { status: 'idle' },
5556
connectionInfo,
57+
documentCount = DEFAULT_DOCUMENT_COUNT.toString(),
5658
}: {
5759
isOpen?: boolean;
5860
enableGenAISampleDocumentPassing?: boolean;
@@ -61,6 +63,7 @@ describe('MockDataGeneratorModal', () => {
6163
connectionInfo?: ConnectionInfo;
6264
schemaAnalysis?: SchemaAnalysisState;
6365
fakerSchemaGeneration?: CollectionState['fakerSchemaGeneration'];
66+
documentCount?: string;
6467
} = {}) {
6568
const initialState: CollectionState = {
6669
workspaceTabId: 'test-workspace-tab-id',
@@ -71,6 +74,7 @@ describe('MockDataGeneratorModal', () => {
7174
mockDataGenerator: {
7275
isModalOpen: isOpen,
7376
currentStep: currentStep,
77+
documentCount: documentCount,
7478
},
7579
};
7680

@@ -828,7 +832,6 @@ describe('MockDataGeneratorModal', () => {
828832
/Indicate the amount of documents you want to generate below./
829833
)
830834
).to.exist;
831-
expect(screen.getByText(/Note: We have defaulted to 1000./)).to.exist;
832835
});
833836

834837
it('displays the default document count when the user does not enter a document count', async () => {
@@ -894,6 +897,53 @@ describe('MockDataGeneratorModal', () => {
894897
expect(screen.getByText('200.0 kB')).to.exist;
895898
});
896899

900+
it('allows the input to be cleared and shows appropriate error message', async () => {
901+
await renderModal({ currentStep: MockDataGeneratorStep.DOCUMENT_COUNT });
902+
903+
const documentCountInput = screen.getByLabelText(
904+
'Documents to generate in current collection'
905+
);
906+
907+
// Clear the input
908+
userEvent.clear(documentCountInput);
909+
910+
// Input should be empty
911+
expect(documentCountInput).to.have.value('');
912+
913+
// Should show error message for empty input
914+
expect(screen.getByText('Document count is required')).to.exist;
915+
916+
// Next button should be disabled for empty input
917+
expect(
918+
screen.getByTestId('next-step-button').getAttribute('aria-disabled')
919+
).to.equal('true');
920+
});
921+
922+
it('handles typing and clearing naturally without reverting', async () => {
923+
await renderModal({ currentStep: MockDataGeneratorStep.DOCUMENT_COUNT });
924+
925+
const documentCountInput = screen.getByLabelText(
926+
'Documents to generate in current collection'
927+
);
928+
929+
// Start with default value
930+
expect(documentCountInput).to.have.value('1000');
931+
932+
// Clear and type a new value
933+
userEvent.clear(documentCountInput);
934+
expect(documentCountInput).to.have.value(''); // Should stay empty
935+
936+
userEvent.type(documentCountInput, '5');
937+
expect(documentCountInput).to.have.value('5'); // Should show what we typed
938+
939+
userEvent.type(documentCountInput, '00');
940+
expect(documentCountInput).to.have.value('500'); // Should accumulate
941+
942+
// Clear again
943+
userEvent.clear(documentCountInput);
944+
expect(documentCountInput).to.have.value(''); // Should stay empty, not revert
945+
});
946+
897947
it('fires a track event when the document count is changed', async () => {
898948
const result = await renderModal({
899949
currentStep: MockDataGeneratorStep.DOCUMENT_COUNT,
@@ -1188,6 +1238,7 @@ describe('MockDataGeneratorModal', () => {
11881238
it('fires a track event when the script is generated', async () => {
11891239
const result = await renderModal({
11901240
currentStep: MockDataGeneratorStep.GENERATE_DATA,
1241+
documentCount: '100',
11911242
fakerSchemaGeneration: {
11921243
status: 'completed',
11931244
originalLlmResponse: {

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

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,17 @@ import {
1515

1616
import { type MockDataGeneratorState, MockDataGeneratorStep } from './types';
1717
import {
18-
DEFAULT_DOCUMENT_COUNT,
19-
MAX_DOCUMENT_COUNT,
2018
MOCK_DATA_GENERATOR_STEP_TO_NEXT_STEP_MAP,
2119
StepButtonLabelMap,
2220
} from './constants';
21+
import { validateDocumentCount } from './utils';
2322
import type { CollectionState } from '../../modules/collection-tab';
2423
import {
2524
mockDataGeneratorModalClosed,
2625
mockDataGeneratorNextButtonClicked,
2726
generateFakerMappings,
2827
mockDataGeneratorPreviousButtonClicked,
28+
mockDataGeneratorDocumentCountChanged,
2929
} from '../../modules/collection-tab';
3030

3131
import RawSchemaConfirmationScreen from './raw-schema-confirmation-screen';
@@ -64,6 +64,8 @@ interface Props {
6464
onPreviousStep: () => void;
6565
namespace: string;
6666
fakerSchemaGenerationState: MockDataGeneratorState;
67+
documentCount: string;
68+
onDocumentCountChange: (documentCount: string) => void;
6769
}
6870

6971
const MockDataGeneratorModal = ({
@@ -75,10 +77,9 @@ const MockDataGeneratorModal = ({
7577
onPreviousStep,
7678
namespace,
7779
fakerSchemaGenerationState,
80+
documentCount,
81+
onDocumentCountChange,
7882
}: Props) => {
79-
const [documentCount, setDocumentCount] = React.useState<number>(
80-
DEFAULT_DOCUMENT_COUNT
81-
);
8283
const track = useTelemetry();
8384
const isAIFeatureEnabled = useIsAIFeatureEnabled();
8485
const isSampleDocumentPassingEnabled = usePreference(
@@ -100,7 +101,7 @@ const MockDataGeneratorModal = ({
100101
return (
101102
<DocumentCountScreen
102103
documentCount={documentCount}
103-
onDocumentCountChange={setDocumentCount}
104+
onDocumentCountChange={onDocumentCountChange}
104105
/>
105106
);
106107
case MockDataGeneratorStep.PREVIEW_DATA:
@@ -120,7 +121,7 @@ const MockDataGeneratorModal = ({
120121
currentStep,
121122
fakerSchemaGenerationState,
122123
documentCount,
123-
setDocumentCount,
124+
onDocumentCountChange,
124125
onNextStep,
125126
]);
126127

@@ -130,13 +131,13 @@ const MockDataGeneratorModal = ({
130131
});
131132
}, [currentStep, track]);
132133

134+
const isDocumentCountInvalid = !validateDocumentCount(documentCount).isValid;
135+
133136
const isNextButtonDisabled =
134137
(currentStep === MockDataGeneratorStep.SCHEMA_EDITOR &&
135138
fakerSchemaGenerationState.status !== 'completed') ||
136139
(currentStep === MockDataGeneratorStep.DOCUMENT_COUNT &&
137-
documentCount < 1) ||
138-
(currentStep === MockDataGeneratorStep.DOCUMENT_COUNT &&
139-
documentCount > MAX_DOCUMENT_COUNT);
140+
isDocumentCountInvalid);
140141

141142
const handleNextClick = useCallback(() => {
142143
const nextStep = MOCK_DATA_GENERATOR_STEP_TO_NEXT_STEP_MAP[currentStep];
@@ -220,13 +221,15 @@ const mapStateToProps = (state: CollectionState) => ({
220221
currentStep: state.mockDataGenerator.currentStep,
221222
namespace: state.namespace,
222223
fakerSchemaGenerationState: state.fakerSchemaGeneration,
224+
documentCount: state.mockDataGenerator.documentCount,
223225
});
224226

225227
const ConnectedMockDataGeneratorModal = connect(mapStateToProps, {
226228
onClose: mockDataGeneratorModalClosed,
227229
onNextStep: mockDataGeneratorNextButtonClicked,
228230
onConfirmSchema: generateFakerMappings,
229231
onPreviousStep: mockDataGeneratorPreviousButtonClicked,
232+
onDocumentCountChange: mockDataGeneratorDocumentCountChanged,
230233
})(MockDataGeneratorModal);
231234

232235
export default ConnectedMockDataGeneratorModal;

packages/compass-collection/src/components/mock-data-generator-modal/raw-schema-confirmation-screen.tsx

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ const descriptionStyles = css({
4646

4747
const errorBannerStyles = css({
4848
marginTop: spacing[400],
49+
marginBottom: spacing[400],
4950
});
5051

5152
const errorBannerTextStyles = css({
@@ -77,6 +78,16 @@ const RawSchemaConfirmationScreen = ({
7778
{subtitleText}
7879
</Body>
7980
<Body className={descriptionStyles}>{descriptionText}</Body>
81+
{fakerSchemaGenerationStatus === 'error' && (
82+
<Banner
83+
variant={BannerVariant.Danger}
84+
className={errorBannerStyles}
85+
>
86+
<Body className={errorBannerTextStyles}>
87+
LLM Request failed. Please confirm again.
88+
</Body>
89+
</Banner>
90+
)}
8091
<div
8192
className={cx(
8293
documentContainerStyles,
@@ -95,16 +106,6 @@ const RawSchemaConfirmationScreen = ({
95106
}
96107
/>
97108
</div>
98-
{fakerSchemaGenerationStatus === 'error' && (
99-
<Banner
100-
variant={BannerVariant.Danger}
101-
className={errorBannerStyles}
102-
>
103-
<Body className={errorBannerTextStyles}>
104-
LLM Request failed. Please confirm again.
105-
</Body>
106-
</Banner>
107-
)}
108109
</>
109110
) : (
110111
// Not reachable since schema analysis must be finished before the modal can be opened

0 commit comments

Comments
 (0)