Skip to content

Commit ad3348d

Browse files
committed
address comments - safely call faker methods
1 parent 81c62ce commit ad3348d

File tree

7 files changed

+125
-87
lines changed

7 files changed

+125
-87
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ const FakerMappingSelector = ({
5858
))}
5959
</Select>
6060
<Select
61-
data-testid="faker-funtion-select"
61+
data-testid="faker-function-select"
6262
label="Faker Function"
6363
value={activeFakerFunction}
6464
onChange={onFakerFunctionSelect}
@@ -111,7 +111,7 @@ const FakerMappingSelector = ({
111111
key={idx}
112112
label={`Faker Function Parameter ${typeof arg}`}
113113
readOnly
114-
value={JSON.stringify(arg.json)}
114+
value={arg.json}
115115
/>
116116
);
117117
})}

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

Lines changed: 73 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { StepButtonLabelMap } from './constants';
1515
import type { CollectionState } from '../../modules/collection-tab';
1616
import { default as collectionTabReducer } from '../../modules/collection-tab';
1717
import type { ConnectionInfo } from '@mongodb-js/connection-info';
18+
import type { MockDataSchemaResponse } from '@mongodb-js/compass-generative-ai';
1819

1920
describe('MockDataGeneratorModal', () => {
2021
async function renderModal({
@@ -73,10 +74,10 @@ describe('MockDataGeneratorModal', () => {
7374
atlasAiService: {
7475
getMockDataSchema: () => {
7576
return Promise.resolve({
76-
contents: {
77+
content: {
7778
fields: [],
7879
},
79-
});
80+
} as MockDataSchemaResponse);
8081
},
8182
},
8283
workspaces: {},
@@ -168,7 +169,7 @@ describe('MockDataGeneratorModal', () => {
168169
userEvent.click(screen.getByText('Confirm'));
169170

170171
await waitFor(() => {
171-
expect(screen.getByTestId('schema-editor-loading')).to.exist;
172+
expect(screen.getByTestId('faker-schema-editor-loader')).to.exist;
172173
});
173174

174175
userEvent.click(screen.getByText('Cancel'));
@@ -184,7 +185,7 @@ describe('MockDataGeneratorModal', () => {
184185
userEvent.click(screen.getByText('Confirm'));
185186

186187
await waitFor(() => {
187-
expect(screen.getByTestId('schema-editor-loading')).to.exist;
188+
expect(screen.getByTestId('faker-schema-editor-loader')).to.exist;
188189
});
189190

190191
userEvent.click(screen.getByText('Back'));
@@ -208,11 +209,11 @@ describe('MockDataGeneratorModal', () => {
208209
await renderModal();
209210

210211
expect(screen.getByTestId('raw-schema-confirmation')).to.exist;
211-
expect(screen.queryByTestId('faker-schema-editor')).to.not.exist;
212+
expect(screen.queryByTestId('faker-schema-editor-loader')).to.not.exist;
212213
userEvent.click(screen.getByText('Confirm'));
213214
await waitFor(() => {
214215
expect(screen.queryByTestId('raw-schema-confirmation')).to.not.exist;
215-
expect(screen.getByTestId('faker-schema-editor')).to.exist;
216+
expect(screen.getByTestId('faker-schema-editor-loader')).to.exist;
216217
});
217218
});
218219

@@ -236,6 +237,72 @@ describe('MockDataGeneratorModal', () => {
236237
// todo: assert that closing then re-opening the modal after an LLM err removes the err message
237238
});
238239

240+
describe('on the schema editor step', () => {
241+
const mockServicesWithAiResponse = createMockServices();
242+
mockServicesWithAiResponse.atlasAiService.getMockDataSchema = () =>
243+
Promise.resolve({
244+
content: {
245+
fields: [
246+
{
247+
fieldPath: 'name',
248+
mongoType: 'string',
249+
fakerMethod: 'person.firstName',
250+
fakerArgs: [],
251+
isArray: false,
252+
probability: 1.0,
253+
},
254+
{
255+
fieldPath: 'age',
256+
mongoType: 'int',
257+
fakerMethod: 'number.int',
258+
fakerArgs: [],
259+
isArray: false,
260+
probability: 1.0,
261+
},
262+
{
263+
fieldPath: 'email',
264+
mongoType: 'string',
265+
fakerMethod: 'internet.emailAddress',
266+
fakerArgs: [],
267+
isArray: false,
268+
probability: 1.0,
269+
},
270+
],
271+
},
272+
});
273+
274+
it('shows a loading spinner when the faker schema generation is in progress', async () => {
275+
await renderModal();
276+
277+
// advance to the schema editor step
278+
userEvent.click(screen.getByText('Confirm'));
279+
expect(screen.getByTestId('faker-schema-editor-loader')).to.exist;
280+
});
281+
282+
it('shows the faker schema editor when the faker schema generation is completed', async () => {
283+
await renderModal({ mockServices: mockServicesWithAiResponse });
284+
285+
// advance to the schema editor step
286+
userEvent.click(screen.getByText('Confirm'));
287+
288+
expect(await screen.findByTestId('faker-schema-editor')).to.exist;
289+
expect(screen.getByText('name')).to.exist;
290+
expect(screen.getByText('age')).to.exist;
291+
});
292+
293+
it('disables the Next button when the faker schema mapping is not confirmed', async () => {
294+
await renderModal({
295+
mockServices: mockServicesWithAiResponse,
296+
});
297+
298+
// advance to the schema editor step
299+
userEvent.click(screen.getByText('Confirm'));
300+
expect(
301+
screen.getByTestId('next-step-button').getAttribute('aria-disabled')
302+
).to.equal('true');
303+
});
304+
});
305+
239306
describe('on the generate data step', () => {
240307
it('enables the Back button', async () => {
241308
await renderModal({ currentStep: MockDataGeneratorStep.GENERATE_DATA });

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

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
SpinLoaderWithLabel,
1414
} from '@mongodb-js/compass-components';
1515

16-
import { type FakerSchemaMapping, MockDataGeneratorStep } from './types';
16+
import { type MockDataGeneratorState, MockDataGeneratorStep } from './types';
1717
import { StepButtonLabelMap } from './constants';
1818
import type { CollectionState } from '../../modules/collection-tab';
1919
import {
@@ -50,7 +50,7 @@ interface Props {
5050
onNextStep: () => void;
5151
onConfirmSchema: () => Promise<void>;
5252
onPreviousStep: () => void;
53-
fakerSchema?: Array<FakerSchemaMapping>;
53+
fakerSchemaGenerationState: MockDataGeneratorState;
5454
}
5555

5656
const MockDataGeneratorModal = ({
@@ -60,40 +60,51 @@ const MockDataGeneratorModal = ({
6060
onNextStep,
6161
onConfirmSchema,
6262
onPreviousStep,
63-
fakerSchema,
63+
fakerSchemaGenerationState,
6464
}: Props) => {
65+
const [isSchemaConfirmed, setIsSchemaConfirmed] =
66+
React.useState<boolean>(false);
67+
6568
const modalBodyContent = useMemo(() => {
6669
switch (currentStep) {
6770
case MockDataGeneratorStep.SCHEMA_CONFIRMATION:
6871
return <SchemaConfirmationScreen />;
6972
case MockDataGeneratorStep.SCHEMA_EDITOR:
70-
return fakerSchema === undefined ? (
71-
<SpinLoaderWithLabel
72-
className={schemaEditorLoaderStyles}
73-
progressText="Processing Documents..."
74-
/>
75-
) : (
76-
<FakerSchemaEditor
77-
isSchemaConfirmed={isSchemaConfirmed}
78-
onSchemaConfirmed={() => setIsSchemaConfirmed(true)}
79-
fakerMappings={fakerSchema}
80-
/>
81-
);
73+
{
74+
if (fakerSchemaGenerationState.status === 'in-progress') {
75+
return (
76+
<div
77+
data-testid="faker-schema-editor-loader"
78+
className={schemaEditorLoaderStyles}
79+
>
80+
<SpinLoaderWithLabel progressText="Processing Documents..." />
81+
</div>
82+
);
83+
}
84+
if (fakerSchemaGenerationState.status === 'completed') {
85+
return (
86+
<FakerSchemaEditor
87+
isSchemaConfirmed={isSchemaConfirmed}
88+
onSchemaConfirmed={() => setIsSchemaConfirmed(true)}
89+
fakerMappings={fakerSchemaGenerationState.fakerSchema}
90+
/>
91+
);
92+
}
93+
}
94+
break;
8295
case MockDataGeneratorStep.DOCUMENT_COUNT:
8396
return <></>; // TODO: CLOUDP-333856
8497
case MockDataGeneratorStep.PREVIEW_DATA:
8598
return <></>; // TODO: CLOUDP-333857
8699
case MockDataGeneratorStep.GENERATE_DATA:
87100
return <ScriptScreen />;
88101
}
89-
}, [currentStep]);
90-
const [isSchemaConfirmed, setIsSchemaConfirmed] =
91-
React.useState<boolean>(false);
102+
}, [currentStep, fakerSchemaGenerationState.status]);
92103

93104
const isNextButtonDisabled =
94105
currentStep === MockDataGeneratorStep.SCHEMA_EDITOR &&
95106
!isSchemaConfirmed &&
96-
fakerSchema === undefined;
107+
fakerSchemaGenerationState.status === 'in-progress';
97108

98109
const handleNextClick = () => {
99110
if (currentStep === MockDataGeneratorStep.GENERATE_DATA) {
@@ -148,10 +159,7 @@ const MockDataGeneratorModal = ({
148159
const mapStateToProps = (state: CollectionState) => ({
149160
isOpen: state.mockDataGenerator.isModalOpen,
150161
currentStep: state.mockDataGenerator.currentStep,
151-
fakerSchema:
152-
state.fakerSchemaGeneration.status === 'completed'
153-
? state.fakerSchemaGeneration.fakerSchema
154-
: undefined,
162+
fakerSchemaGenerationState: state.fakerSchemaGeneration,
155163
});
156164

157165
const ConnectedMockDataGeneratorModal = connect(mapStateToProps, {

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

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ import type {
4040
MockDataGeneratorState,
4141
} from '../components/mock-data-generator-modal/types';
4242

43+
/* eslint-disable @typescript-eslint/no-require-imports */
44+
const faker = require('@faker-js/faker/locale/en');
45+
4346
const DEFAULT_SAMPLE_SIZE = 100;
4447

4548
const NO_DOCUMENTS_ERROR = 'No documents found in the collection to analyze.';
@@ -611,33 +614,34 @@ export const analyzeCollectionSchema = (): CollectionThunkAction<
611614
};
612615
};
613616

614-
const validateFakerSchema = async (
617+
const validateFakerSchema = (
615618
fakerSchema: MockDataSchemaResponse,
616619
logger: Logger
617620
) => {
618-
const { faker } = await import('@faker-js/faker');
619621
return fakerSchema.content.fields.map((field) => {
620622
const { fakerMethod, fakerArgs } = field;
621623

622624
const [first, second] = fakerMethod.split('.');
623625
try {
624626
// Try with arguments first
625-
const fakerMethodWithArgs = eval(
626-
`(faker, ...fakerArgs) => faker["${first}"]["${second}"](...fakerArgs)`
627-
);
628-
fakerMethodWithArgs(faker, ...fakerArgs);
627+
const method = (faker as any)?.[first]?.[second];
628+
if (typeof method !== 'function') {
629+
throw new Error(`Faker method ${fakerMethod} is not a function`);
630+
}
631+
method(...fakerArgs);
629632
return field;
630633
} catch (error) {
631634
// If that fails and there are arguments, try without arguments
632635
if (fakerArgs.length > 0) {
633636
try {
634-
const fakerMethodWithoutArgs = eval(
635-
`(faker) => faker["${first}"]["${second}"]()`
636-
);
637-
fakerMethodWithoutArgs(faker);
637+
const method = (faker as any)?.[first]?.[second];
638+
if (typeof method !== 'function') {
639+
throw new Error(`Faker method ${fakerMethod} is not a function`);
640+
}
641+
method();
638642
return field;
639643
} catch (error) {
640-
logger.log.debug(
644+
logger.debug(
641645
mongoLogId(1_001_000_371),
642646
'Collection',
643647
'Failed to validate faker schema with arguments',
@@ -649,7 +653,7 @@ const validateFakerSchema = async (
649653
);
650654
}
651655
}
652-
logger.log.debug(
656+
logger.debug(
653657
mongoLogId(1_001_000_372),
654658
'Collection',
655659
'Failed to validate faker schema',
@@ -734,7 +738,7 @@ export const generateFakerMappings = (): CollectionThunkAction<
734738
connectionInfoRef.current
735739
);
736740

737-
const validatedFakerSchema = await validateFakerSchema(response, logger);
741+
const validatedFakerSchema = validateFakerSchema(response, logger);
738742

739743
fakerSchemaGenerationAbortControllerRef.current = undefined;
740744
dispatch({

packages/compass-components/src/components/vertical-rule.tsx

Lines changed: 0 additions & 42 deletions
This file was deleted.

packages/compass-components/src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,4 +223,3 @@ export { ParagraphSkeleton } from '@leafygreen-ui/skeleton-loader';
223223
export { InsightsChip } from './components/insights-chip';
224224
export * from './components/drawer-portal';
225225
export { FileSelector } from './components/file-selector';
226-
export { transitionDuration } from '@leafygreen-ui/tokens';

packages/compass-web/webpack.config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,8 @@ module.exports = (env, args) => {
332332
// bson is not that big, but is a shared dependency of compass-web,
333333
// compass-components and bson-transpilers, so splitting it out
334334
'bson',
335+
// dependency of compass-collection
336+
'@faker-js/faker',
335337
]);
336338

337339
return bundles;

0 commit comments

Comments
 (0)