Skip to content

Commit f8d3723

Browse files
committed
address comments - safely call faker methods
1 parent 24b1b1b commit f8d3723

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({
@@ -80,10 +81,10 @@ describe('MockDataGeneratorModal', () => {
8081
atlasAiService: {
8182
getMockDataSchema: () => {
8283
return Promise.resolve({
83-
contents: {
84+
content: {
8485
fields: [],
8586
},
86-
});
87+
} as MockDataSchemaResponse);
8788
},
8889
},
8990
workspaces: {},
@@ -175,7 +176,7 @@ describe('MockDataGeneratorModal', () => {
175176
userEvent.click(screen.getByText('Confirm'));
176177

177178
await waitFor(() => {
178-
expect(screen.getByTestId('schema-editor-loading')).to.exist;
179+
expect(screen.getByTestId('faker-schema-editor-loader')).to.exist;
179180
});
180181

181182
userEvent.click(screen.getByText('Cancel'));
@@ -191,7 +192,7 @@ describe('MockDataGeneratorModal', () => {
191192
userEvent.click(screen.getByText('Confirm'));
192193

193194
await waitFor(() => {
194-
expect(screen.getByTestId('schema-editor-loading')).to.exist;
195+
expect(screen.getByTestId('faker-schema-editor-loader')).to.exist;
195196
});
196197

197198
userEvent.click(screen.getByText('Back'));
@@ -246,11 +247,11 @@ describe('MockDataGeneratorModal', () => {
246247
await renderModal();
247248

248249
expect(screen.getByTestId('raw-schema-confirmation')).to.exist;
249-
expect(screen.queryByTestId('faker-schema-editor')).to.not.exist;
250+
expect(screen.queryByTestId('faker-schema-editor-loader')).to.not.exist;
250251
userEvent.click(screen.getByText('Confirm'));
251252
await waitFor(() => {
252253
expect(screen.queryByTestId('raw-schema-confirmation')).to.not.exist;
253-
expect(screen.getByTestId('faker-schema-editor')).to.exist;
254+
expect(screen.getByTestId('faker-schema-editor-loader')).to.exist;
254255
});
255256
});
256257

@@ -273,6 +274,72 @@ describe('MockDataGeneratorModal', () => {
273274
});
274275
});
275276

277+
describe('on the schema editor step', () => {
278+
const mockServicesWithAiResponse = createMockServices();
279+
mockServicesWithAiResponse.atlasAiService.getMockDataSchema = () =>
280+
Promise.resolve({
281+
content: {
282+
fields: [
283+
{
284+
fieldPath: 'name',
285+
mongoType: 'string',
286+
fakerMethod: 'person.firstName',
287+
fakerArgs: [],
288+
isArray: false,
289+
probability: 1.0,
290+
},
291+
{
292+
fieldPath: 'age',
293+
mongoType: 'int',
294+
fakerMethod: 'number.int',
295+
fakerArgs: [],
296+
isArray: false,
297+
probability: 1.0,
298+
},
299+
{
300+
fieldPath: 'email',
301+
mongoType: 'string',
302+
fakerMethod: 'internet.emailAddress',
303+
fakerArgs: [],
304+
isArray: false,
305+
probability: 1.0,
306+
},
307+
],
308+
},
309+
});
310+
311+
it('shows a loading spinner when the faker schema generation is in progress', async () => {
312+
await renderModal();
313+
314+
// advance to the schema editor step
315+
userEvent.click(screen.getByText('Confirm'));
316+
expect(screen.getByTestId('faker-schema-editor-loader')).to.exist;
317+
});
318+
319+
it('shows the faker schema editor when the faker schema generation is completed', async () => {
320+
await renderModal({ mockServices: mockServicesWithAiResponse });
321+
322+
// advance to the schema editor step
323+
userEvent.click(screen.getByText('Confirm'));
324+
325+
expect(await screen.findByTestId('faker-schema-editor')).to.exist;
326+
expect(screen.getByText('name')).to.exist;
327+
expect(screen.getByText('age')).to.exist;
328+
});
329+
330+
it('disables the Next button when the faker schema mapping is not confirmed', async () => {
331+
await renderModal({
332+
mockServices: mockServicesWithAiResponse,
333+
});
334+
335+
// advance to the schema editor step
336+
userEvent.click(screen.getByText('Confirm'));
337+
expect(
338+
screen.getByTestId('next-step-button').getAttribute('aria-disabled')
339+
).to.equal('true');
340+
});
341+
});
342+
276343
describe('on the generate data step', () => {
277344
it('enables the Back button', async () => {
278345
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
@@ -14,7 +14,7 @@ import {
1414
SpinLoaderWithLabel,
1515
} from '@mongodb-js/compass-components';
1616

17-
import { type FakerSchemaMapping, MockDataGeneratorStep } from './types';
17+
import { type MockDataGeneratorState, MockDataGeneratorStep } from './types';
1818
import { StepButtonLabelMap } from './constants';
1919
import type { CollectionState } from '../../modules/collection-tab';
2020
import {
@@ -57,7 +57,7 @@ interface Props {
5757
onConfirmSchema: () => Promise<void>;
5858
onPreviousStep: () => void;
5959
namespace: string;
60-
fakerSchema?: Array<FakerSchemaMapping>;
60+
fakerSchemaGenerationState: MockDataGeneratorState;
6161
}
6262

6363
const MockDataGeneratorModal = ({
@@ -68,40 +68,51 @@ const MockDataGeneratorModal = ({
6868
onConfirmSchema,
6969
onPreviousStep,
7070
namespace,
71-
fakerSchema,
71+
fakerSchemaGenerationState,
7272
}: Props) => {
73+
const [isSchemaConfirmed, setIsSchemaConfirmed] =
74+
React.useState<boolean>(false);
75+
7376
const modalBodyContent = useMemo(() => {
7477
switch (currentStep) {
7578
case MockDataGeneratorStep.SCHEMA_CONFIRMATION:
7679
return <RawSchemaConfirmationScreen />;
7780
case MockDataGeneratorStep.SCHEMA_EDITOR:
78-
return fakerSchema === undefined ? (
79-
<SpinLoaderWithLabel
80-
className={schemaEditorLoaderStyles}
81-
progressText="Processing Documents..."
82-
/>
83-
) : (
84-
<FakerSchemaEditorScreen
85-
isSchemaConfirmed={isSchemaConfirmed}
86-
onSchemaConfirmed={() => setIsSchemaConfirmed(true)}
87-
fakerMappings={fakerSchema}
88-
/>
89-
);
81+
{
82+
if (fakerSchemaGenerationState.status === 'in-progress') {
83+
return (
84+
<div
85+
data-testid="faker-schema-editor-loader"
86+
className={schemaEditorLoaderStyles}
87+
>
88+
<SpinLoaderWithLabel progressText="Processing Documents..." />
89+
</div>
90+
);
91+
}
92+
if (fakerSchemaGenerationState.status === 'completed') {
93+
return (
94+
<FakerSchemaEditorScreen
95+
isSchemaConfirmed={isSchemaConfirmed}
96+
onSchemaConfirmed={() => setIsSchemaConfirmed(true)}
97+
fakerMappings={fakerSchemaGenerationState.fakerSchema}
98+
/>
99+
);
100+
}
101+
}
102+
break;
90103
case MockDataGeneratorStep.DOCUMENT_COUNT:
91104
return <></>; // TODO: CLOUDP-333856
92105
case MockDataGeneratorStep.PREVIEW_DATA:
93106
return <></>; // TODO: CLOUDP-333857
94107
case MockDataGeneratorStep.GENERATE_DATA:
95108
return <ScriptScreen />;
96109
}
97-
}, [currentStep]);
98-
const [isSchemaConfirmed, setIsSchemaConfirmed] =
99-
React.useState<boolean>(false);
110+
}, [currentStep, fakerSchemaGenerationState.status]);
100111

101112
const isNextButtonDisabled =
102113
currentStep === MockDataGeneratorStep.SCHEMA_EDITOR &&
103114
!isSchemaConfirmed &&
104-
fakerSchema === undefined;
115+
fakerSchemaGenerationState.status === 'in-progress';
105116

106117
const handleNextClick = () => {
107118
if (currentStep === MockDataGeneratorStep.GENERATE_DATA) {
@@ -163,10 +174,7 @@ const mapStateToProps = (state: CollectionState) => ({
163174
isOpen: state.mockDataGenerator.isModalOpen,
164175
currentStep: state.mockDataGenerator.currentStep,
165176
namespace: state.namespace,
166-
fakerSchema:
167-
state.fakerSchemaGeneration.status === 'completed'
168-
? state.fakerSchemaGeneration.fakerSchema
169-
: undefined,
177+
fakerSchemaGenerationState: state.fakerSchemaGeneration,
170178
});
171179

172180
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
@@ -43,6 +43,9 @@ import type {
4343
MockDataGeneratorState,
4444
} from '../components/mock-data-generator-modal/types';
4545

46+
/* eslint-disable @typescript-eslint/no-require-imports */
47+
const faker = require('@faker-js/faker/locale/en');
48+
4649
const DEFAULT_SAMPLE_SIZE = 100;
4750

4851
const NO_DOCUMENTS_ERROR = 'No documents found in the collection to analyze.';
@@ -696,33 +699,34 @@ export const cancelSchemaAnalysis = (): CollectionThunkAction<void> => {
696699
};
697700
};
698701

699-
const validateFakerSchema = async (
702+
const validateFakerSchema = (
700703
fakerSchema: MockDataSchemaResponse,
701704
logger: Logger
702705
) => {
703-
const { faker } = await import('@faker-js/faker');
704706
return fakerSchema.content.fields.map((field) => {
705707
const { fakerMethod, fakerArgs } = field;
706708

707709
const [first, second] = fakerMethod.split('.');
708710
try {
709711
// Try with arguments first
710-
const fakerMethodWithArgs = eval(
711-
`(faker, ...fakerArgs) => faker["${first}"]["${second}"](...fakerArgs)`
712-
);
713-
fakerMethodWithArgs(faker, ...fakerArgs);
712+
const method = (faker as any)?.[first]?.[second];
713+
if (typeof method !== 'function') {
714+
throw new Error(`Faker method ${fakerMethod} is not a function`);
715+
}
716+
method(...fakerArgs);
714717
return field;
715718
} catch (error) {
716719
// If that fails and there are arguments, try without arguments
717720
if (fakerArgs.length > 0) {
718721
try {
719-
const fakerMethodWithoutArgs = eval(
720-
`(faker) => faker["${first}"]["${second}"]()`
721-
);
722-
fakerMethodWithoutArgs(faker);
722+
const method = (faker as any)?.[first]?.[second];
723+
if (typeof method !== 'function') {
724+
throw new Error(`Faker method ${fakerMethod} is not a function`);
725+
}
726+
method();
723727
return field;
724728
} catch (error) {
725-
logger.log.debug(
729+
logger.debug(
726730
mongoLogId(1_001_000_371),
727731
'Collection',
728732
'Failed to validate faker schema with arguments',
@@ -734,7 +738,7 @@ const validateFakerSchema = async (
734738
);
735739
}
736740
}
737-
logger.log.debug(
741+
logger.debug(
738742
mongoLogId(1_001_000_372),
739743
'Collection',
740744
'Failed to validate faker schema',
@@ -819,7 +823,7 @@ export const generateFakerMappings = (): CollectionThunkAction<
819823
connectionInfoRef.current
820824
);
821825

822-
const validatedFakerSchema = await validateFakerSchema(response, logger);
826+
const validatedFakerSchema = validateFakerSchema(response, logger);
823827

824828
fakerSchemaGenerationAbortControllerRef.current = undefined;
825829
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
@@ -228,4 +228,3 @@ export { ParagraphSkeleton } from '@leafygreen-ui/skeleton-loader';
228228
export { InsightsChip } from './components/insights-chip';
229229
export * from './components/drawer-portal';
230230
export { FileSelector } from './components/file-selector';
231-
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
@@ -325,6 +325,8 @@ module.exports = (env, args) => {
325325
// bson is not that big, but is a shared dependency of compass-web,
326326
// compass-components and bson-transpilers, so splitting it out
327327
'bson',
328+
// dependency of compass-collection
329+
'@faker-js/faker',
328330
]);
329331

330332
return bundles;

0 commit comments

Comments
 (0)