Skip to content

Commit 5717f59

Browse files
authored
feat(compass-collection): Schema Editor Dropdowns: Add mongo type to faker method mapping CLOUDP-348131 (#7425)
* feat(compass-collection): Add mongo type to faker method mappings
1 parent 48f3e0c commit 5717f59

File tree

9 files changed

+351
-36
lines changed

9 files changed

+351
-36
lines changed

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

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { MongoDBFieldType } from '@mongodb-js/compass-generative-ai';
12
import { MockDataGeneratorStep } from './types';
23

34
export const StepButtonLabelMap = {
@@ -10,3 +11,91 @@ export const StepButtonLabelMap = {
1011

1112
export const DEFAULT_DOCUMENT_COUNT = 1000;
1213
export const MAX_DOCUMENT_COUNT = 100000;
14+
15+
/**
16+
* Map of MongoDB types to available Faker v9 methods.
17+
* Not all Faker methods are included here.
18+
* More can be found in the Faker.js API: https://v9.fakerjs.dev/api/
19+
*/
20+
export const MONGO_TYPE_TO_FAKER_METHODS: Record<
21+
MongoDBFieldType,
22+
Array<string>
23+
> = {
24+
String: [
25+
'lorem.word',
26+
'lorem.words',
27+
'lorem.sentence',
28+
'lorem.paragraph',
29+
'person.firstName',
30+
'person.lastName',
31+
'person.fullName',
32+
'person.jobTitle',
33+
'internet.displayName',
34+
'internet.email',
35+
'internet.emoji',
36+
'internet.password',
37+
'internet.url',
38+
'internet.domainName',
39+
'internet.userName',
40+
'phone.number',
41+
'location.city',
42+
'location.country',
43+
'location.streetAddress',
44+
'location.zipCode',
45+
'location.state',
46+
'company.name',
47+
'company.catchPhrase',
48+
'color.human',
49+
'commerce.productName',
50+
'commerce.department',
51+
'finance.accountName',
52+
'finance.currencyCode',
53+
'git.commitSha',
54+
'string.uuid',
55+
'string.alpha',
56+
'string.alphanumeric',
57+
'system.fileName',
58+
'system.filePath',
59+
'system.mimeType',
60+
'book.title',
61+
'music.songName',
62+
'food.dish',
63+
'animal.type',
64+
'vehicle.model',
65+
'hacker.phrase',
66+
'science.chemicalElement',
67+
],
68+
Number: [
69+
'number.binary',
70+
'number.octal',
71+
'number.hex',
72+
'commerce.price',
73+
'date.weekday',
74+
'internet.port',
75+
'number.int',
76+
'number.float',
77+
'finance.amount',
78+
'location.latitude',
79+
'location.longitude',
80+
],
81+
Int32: ['number.int', 'finance.amount'],
82+
Long: ['number.int', 'number.bigInt'],
83+
Decimal128: ['number.float', 'finance.amount'],
84+
Boolean: ['datatype.boolean'],
85+
Date: [
86+
'date.recent',
87+
'date.past',
88+
'date.future',
89+
'date.anytime',
90+
'date.birthdate',
91+
],
92+
Timestamp: ['date.recent', 'date.past', 'date.future', 'date.anytime'],
93+
ObjectId: ['database.mongodbObjectId'],
94+
Binary: ['string.hexadecimal', 'string.binary'],
95+
RegExp: ['lorem.word', 'string.alpha'],
96+
Code: ['lorem.sentence', 'lorem.paragraph', 'git.commitMessage'],
97+
MinKey: ['number.int'],
98+
MaxKey: ['number.int'],
99+
Symbol: ['lorem.word', 'string.symbol'],
100+
DBRef: ['database.mongodbObjectId'],
101+
};
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import { expect } from 'chai';
2+
import React from 'react';
3+
import {
4+
screen,
5+
render,
6+
cleanup,
7+
waitFor,
8+
userEvent,
9+
} from '@mongodb-js/testing-library-compass';
10+
import sinon from 'sinon';
11+
import FakerMappingSelector from './faker-mapping-selector';
12+
import { UNRECOGNIZED_FAKER_METHOD } from '../../modules/collection-tab';
13+
import { MONGO_TYPE_TO_FAKER_METHODS } from './constants';
14+
import { MongoDBFieldTypeValues } from '@mongodb-js/compass-generative-ai';
15+
import type { FakerArg } from './script-generation-utils';
16+
17+
const mockActiveJsonType = MongoDBFieldTypeValues.String;
18+
const mockActiveFakerFunction = 'lorem.word';
19+
const mockActiveFakerArgs: Array<FakerArg> = [];
20+
const onJsonTypeSelectStub = sinon.stub();
21+
const onFakerFunctionSelectStub = sinon.stub();
22+
23+
describe('FakerMappingSelector', () => {
24+
afterEach(() => {
25+
cleanup();
26+
});
27+
28+
it('should display all MongoDB types in the dropdown', async () => {
29+
// Check that all MongoDB types from the constant are present
30+
const mongoTypes = Object.keys(MongoDBFieldTypeValues);
31+
32+
render(
33+
<FakerMappingSelector
34+
activeJsonType={mockActiveJsonType}
35+
activeFakerFunction="lorem.word"
36+
activeFakerArgs={mockActiveFakerArgs}
37+
onJsonTypeSelect={onJsonTypeSelectStub}
38+
onFakerFunctionSelect={onFakerFunctionSelectStub}
39+
/>
40+
);
41+
42+
const jsonTypeSelect = screen.getByLabelText('JSON Type');
43+
userEvent.click(jsonTypeSelect);
44+
45+
for (const type of mongoTypes) {
46+
await waitFor(() => {
47+
expect(screen.getByRole('option', { name: type })).to.exist;
48+
});
49+
}
50+
});
51+
52+
describe('should display faker methods for each MongoDB type', () => {
53+
for (const [mongoType, methods] of Object.entries(
54+
MONGO_TYPE_TO_FAKER_METHODS
55+
)) {
56+
it(`should display faker methods for ${mongoType}`, () => {
57+
const firstMethod = methods[0];
58+
59+
render(
60+
<FakerMappingSelector
61+
activeJsonType={
62+
mongoType as keyof typeof MONGO_TYPE_TO_FAKER_METHODS
63+
}
64+
activeFakerFunction={firstMethod}
65+
activeFakerArgs={mockActiveFakerArgs}
66+
onJsonTypeSelect={onJsonTypeSelectStub}
67+
onFakerFunctionSelect={onFakerFunctionSelectStub}
68+
/>
69+
);
70+
71+
const fakerFunctionSelect = screen.getByLabelText('Faker Function');
72+
userEvent.click(fakerFunctionSelect);
73+
74+
methods.forEach((method) => {
75+
expect(screen.getByRole('option', { name: method })).to.exist;
76+
});
77+
});
78+
}
79+
});
80+
81+
it('should call onJsonTypeSelect when MongoDB type changes', async () => {
82+
render(
83+
<FakerMappingSelector
84+
activeJsonType={mockActiveJsonType}
85+
activeFakerFunction={mockActiveFakerFunction}
86+
activeFakerArgs={mockActiveFakerArgs}
87+
onJsonTypeSelect={onJsonTypeSelectStub}
88+
onFakerFunctionSelect={onFakerFunctionSelectStub}
89+
/>
90+
);
91+
92+
const jsonTypeSelect = screen.getByLabelText('JSON Type');
93+
userEvent.click(jsonTypeSelect);
94+
95+
const numberOption = await screen.findByRole('option', { name: 'Number' });
96+
userEvent.click(numberOption);
97+
98+
expect(onJsonTypeSelectStub).to.have.been.calledOnceWith('Number');
99+
});
100+
101+
it('should call onFakerFunctionSelect when faker function changes', async () => {
102+
render(
103+
<FakerMappingSelector
104+
activeJsonType={mockActiveJsonType}
105+
activeFakerFunction={mockActiveFakerFunction}
106+
activeFakerArgs={mockActiveFakerArgs}
107+
onJsonTypeSelect={onJsonTypeSelectStub}
108+
onFakerFunctionSelect={onFakerFunctionSelectStub}
109+
/>
110+
);
111+
112+
const fakerFunctionSelect = screen.getByLabelText('Faker Function');
113+
userEvent.click(fakerFunctionSelect);
114+
115+
const emailOption = await screen.findByRole('option', {
116+
name: 'internet.email',
117+
});
118+
userEvent.click(emailOption);
119+
120+
expect(onFakerFunctionSelectStub).to.have.been.calledOnceWith(
121+
'internet.email'
122+
);
123+
});
124+
125+
it('should show warning banner when faker method is unrecognized', () => {
126+
render(
127+
<FakerMappingSelector
128+
activeJsonType={mockActiveJsonType}
129+
activeFakerFunction={UNRECOGNIZED_FAKER_METHOD}
130+
activeFakerArgs={mockActiveFakerArgs}
131+
onJsonTypeSelect={onJsonTypeSelectStub}
132+
onFakerFunctionSelect={onFakerFunctionSelectStub}
133+
/>
134+
);
135+
136+
expect(
137+
screen.getByText(
138+
/Please select a function or we will default fill this field/
139+
)
140+
).to.exist;
141+
});
142+
143+
it('should not show warning banner when faker method is recognized', () => {
144+
render(
145+
<FakerMappingSelector
146+
activeJsonType={mockActiveJsonType}
147+
activeFakerFunction={mockActiveFakerFunction}
148+
activeFakerArgs={mockActiveFakerArgs}
149+
onJsonTypeSelect={onJsonTypeSelectStub}
150+
onFakerFunctionSelect={onFakerFunctionSelectStub}
151+
/>
152+
);
153+
154+
expect(
155+
screen.queryByText(
156+
/Please select a function or we will default fill this field/
157+
)
158+
).to.not.exist;
159+
});
160+
});

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

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ import {
1010
Select,
1111
spacing,
1212
} from '@mongodb-js/compass-components';
13-
import React from 'react';
13+
import React, { useMemo } from 'react';
1414
import { UNRECOGNIZED_FAKER_METHOD } from '../../modules/collection-tab';
1515
import type { MongoDBFieldType } from '@mongodb-js/compass-generative-ai';
16+
import { MongoDBFieldTypeValues } from '@mongodb-js/compass-generative-ai';
17+
import { MONGO_TYPE_TO_FAKER_METHODS } from './constants';
1618
import type { FakerArg } from './script-generation-utils';
1719

1820
const fieldMappingSelectorsStyles = css({
@@ -58,7 +60,7 @@ const formatFakerFunctionCallWithArgs = (
5860
};
5961

6062
interface Props {
61-
activeJsonType: string;
63+
activeJsonType: MongoDBFieldType;
6264
activeFakerFunction: string;
6365
onJsonTypeSelect: (jsonType: MongoDBFieldType) => void;
6466
activeFakerArgs: FakerArg[];
@@ -72,6 +74,16 @@ const FakerMappingSelector = ({
7274
onJsonTypeSelect,
7375
onFakerFunctionSelect,
7476
}: Props) => {
77+
const fakerMethodOptions = useMemo(() => {
78+
const methods = MONGO_TYPE_TO_FAKER_METHODS[activeJsonType] || [];
79+
80+
if (methods.includes(activeFakerFunction)) {
81+
return methods;
82+
}
83+
84+
return [activeFakerFunction, ...methods];
85+
}, [activeJsonType, activeFakerFunction]);
86+
7587
return (
7688
<div className={fieldMappingSelectorsStyles}>
7789
<Body className={labelStyles}>Mapping</Body>
@@ -81,8 +93,7 @@ const FakerMappingSelector = ({
8193
value={activeJsonType}
8294
onChange={(value) => onJsonTypeSelect(value as MongoDBFieldType)}
8395
>
84-
{/* TODO(CLOUDP-344400) : Make the select input editable and render other options depending on the JSON type selected */}
85-
{[activeJsonType].map((type) => (
96+
{Object.values(MongoDBFieldTypeValues).map((type) => (
8697
<Option key={type} value={type}>
8798
{type}
8899
</Option>
@@ -94,10 +105,9 @@ const FakerMappingSelector = ({
94105
value={activeFakerFunction}
95106
onChange={onFakerFunctionSelect}
96107
>
97-
{/* TODO(CLOUDP-344400): Make the select input editable and render other JSON types */}
98-
{[activeFakerFunction].map((field) => (
99-
<Option key={field} value={field}>
100-
{field}
108+
{fakerMethodOptions.map((method) => (
109+
<Option key={method} value={method}>
110+
{method}
101111
</Option>
102112
))}
103113
</Select>

0 commit comments

Comments
 (0)