Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { MongoDBFieldType } from '@mongodb-js/compass-generative-ai';
import { MockDataGeneratorStep } from './types';

export const StepButtonLabelMap = {
Expand All @@ -10,3 +11,91 @@ export const StepButtonLabelMap = {

export const DEFAULT_DOCUMENT_COUNT = 1000;
export const MAX_DOCUMENT_COUNT = 100000;

/**
* Map of MongoDB types to available Faker v9 methods.
* Not all Faker methods are included here.
* More can be found in the Faker.js API: https://v9.fakerjs.dev/api/
*/
export const MONGO_TYPE_TO_FAKER_METHODS: Record<
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gave Augment link to https://v9.fakerjs.dev/api/ and had it come up with some more additions. (If you upgrade to v10 can do the same with the new API link):

String: [
  // Current methods are good, but could add:
  'internet.password',        // For password fields
  'internet.displayName',     // For display names/usernames
  'internet.emoji',           // For emoji/reaction fields
  'system.fileName',          // For file name fields
  'system.filePath',          // For file path fields
  'system.mimeType',          // For content type fields
  'book.title',               // For title fields
  'music.songName',           // For song/media titles
  'food.dish',                // For food-related strings
  'animal.type',              // For animal/pet fields
  'vehicle.model',            // For vehicle/product models
  'hacker.phrase',            // For tech-related descriptions
  'science.chemicalElement',  // For scientific data
]

Number: [
  // Current methods are good, but could add:
  'number.binary',            // For binary numbers
  'number.octal',             // For octal numbers
  'number.hex',               // For hexadecimal numbers
  'commerce.price',           // For price fields (returns number)
  'date.weekday',             // For day numbers
  'internet.port',            // For port numbers
]

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding these!

MongoDBFieldType,
Array<string>
> = {
String: [
'lorem.word',
'lorem.words',
'lorem.sentence',
'lorem.paragraph',
'person.firstName',
'person.lastName',
'person.fullName',
'person.jobTitle',
'internet.displayName',
'internet.email',
'internet.emoji',
'internet.password',
'internet.url',
'internet.domainName',
'internet.userName',
'phone.number',
'location.city',
'location.country',
'location.streetAddress',
'location.zipCode',
'location.state',
'company.name',
'company.catchPhrase',
'color.human',
'commerce.productName',
'commerce.department',
'finance.accountName',
'finance.currencyCode',
'git.commitSha',
'string.uuid',
'string.alpha',
'string.alphanumeric',
'system.fileName',
'system.filePath',
'system.mimeType',
'book.title',
'music.songName',
'food.dish',
'animal.type',
'vehicle.model',
'hacker.phrase',
'science.chemicalElement',
],
Number: [
'number.binary',
'number.octal',
'number.hex',
'commerce.price',
'date.weekday',
'internet.port',
'number.int',
'number.float',
'finance.amount',
'location.latitude',
'location.longitude',
],
Int32: ['number.int', 'finance.amount'],
Long: ['number.int', 'number.bigInt'],
Decimal128: ['number.float', 'finance.amount'],
Boolean: ['datatype.boolean'],
Date: [
'date.recent',
'date.past',
'date.future',
'date.anytime',
'date.birthdate',
],
Timestamp: ['date.recent', 'date.past', 'date.future', 'date.anytime'],
ObjectId: ['database.mongodbObjectId'],
Binary: ['string.hexadecimal', 'string.binary'],
RegExp: ['lorem.word', 'string.alpha'],
Code: ['lorem.sentence', 'lorem.paragraph', 'git.commitMessage'],
MinKey: ['number.int'],
MaxKey: ['number.int'],
Symbol: ['lorem.word', 'string.symbol'],
DBRef: ['database.mongodbObjectId'],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import { expect } from 'chai';
import React from 'react';
import {
screen,
render,
cleanup,
waitFor,
userEvent,
} from '@mongodb-js/testing-library-compass';
import sinon from 'sinon';
import FakerMappingSelector from './faker-mapping-selector';
import { UNRECOGNIZED_FAKER_METHOD } from '../../modules/collection-tab';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Drive-by suggestion]: This made me realize, due to parallel implementation there's a slight mismatch with script-generation-utils.ts, we should change that file to use UNRECOGNIZED_FAKER_METHOD instead of "unrecognized", if you could make that tweak it'd be much appreciated, I think it's just in this one spot (and then update tests accordingly)

import { MONGO_TYPE_TO_FAKER_METHODS } from './constants';
import { MongoDBFieldTypeValues } from '@mongodb-js/compass-generative-ai';
import type { FakerArg } from './script-generation-utils';

const mockActiveJsonType = MongoDBFieldTypeValues.String;
const mockActiveFakerFunction = 'lorem.word';
const mockActiveFakerArgs: Array<FakerArg> = [];
const onJsonTypeSelectStub = sinon.stub();
const onFakerFunctionSelectStub = sinon.stub();

describe('FakerMappingSelector', () => {
afterEach(() => {
cleanup();
});

it('should display all MongoDB types in the dropdown', async () => {
// Check that all MongoDB types from the constant are present
const mongoTypes = Object.keys(MongoDBFieldTypeValues);

render(
<FakerMappingSelector
activeJsonType={mockActiveJsonType}
activeFakerFunction="lorem.word"
activeFakerArgs={mockActiveFakerArgs}
onJsonTypeSelect={onJsonTypeSelectStub}
onFakerFunctionSelect={onFakerFunctionSelectStub}
/>
);

const jsonTypeSelect = screen.getByLabelText('JSON Type');
userEvent.click(jsonTypeSelect);

for (const type of mongoTypes) {
await waitFor(() => {
expect(screen.getByRole('option', { name: type })).to.exist;
});
}
});

describe('should display faker methods for each MongoDB type', () => {
for (const [mongoType, methods] of Object.entries(
MONGO_TYPE_TO_FAKER_METHODS
)) {
it(`should display faker methods for ${mongoType}`, () => {
const firstMethod = methods[0];

render(
<FakerMappingSelector
activeJsonType={
mongoType as keyof typeof MONGO_TYPE_TO_FAKER_METHODS
}
activeFakerFunction={firstMethod}
activeFakerArgs={mockActiveFakerArgs}
onJsonTypeSelect={onJsonTypeSelectStub}
onFakerFunctionSelect={onFakerFunctionSelectStub}
/>
);

const fakerFunctionSelect = screen.getByLabelText('Faker Function');
userEvent.click(fakerFunctionSelect);

methods.forEach((method) => {
expect(screen.getByRole('option', { name: method })).to.exist;
});
});
}
});

it('should call onJsonTypeSelect when MongoDB type changes', async () => {
render(
<FakerMappingSelector
activeJsonType={mockActiveJsonType}
activeFakerFunction={mockActiveFakerFunction}
activeFakerArgs={mockActiveFakerArgs}
onJsonTypeSelect={onJsonTypeSelectStub}
onFakerFunctionSelect={onFakerFunctionSelectStub}
/>
);

const jsonTypeSelect = screen.getByLabelText('JSON Type');
userEvent.click(jsonTypeSelect);

const numberOption = await screen.findByRole('option', { name: 'Number' });
userEvent.click(numberOption);

expect(onJsonTypeSelectStub).to.have.been.calledOnceWith('Number');
});

it('should call onFakerFunctionSelect when faker function changes', async () => {
render(
<FakerMappingSelector
activeJsonType={mockActiveJsonType}
activeFakerFunction={mockActiveFakerFunction}
activeFakerArgs={mockActiveFakerArgs}
onJsonTypeSelect={onJsonTypeSelectStub}
onFakerFunctionSelect={onFakerFunctionSelectStub}
/>
);

const fakerFunctionSelect = screen.getByLabelText('Faker Function');
userEvent.click(fakerFunctionSelect);

const emailOption = await screen.findByRole('option', {
name: 'internet.email',
});
userEvent.click(emailOption);

expect(onFakerFunctionSelectStub).to.have.been.calledOnceWith(
'internet.email'
);
});

it('should show warning banner when faker method is unrecognized', () => {
render(
<FakerMappingSelector
activeJsonType={mockActiveJsonType}
activeFakerFunction={UNRECOGNIZED_FAKER_METHOD}
activeFakerArgs={mockActiveFakerArgs}
onJsonTypeSelect={onJsonTypeSelectStub}
onFakerFunctionSelect={onFakerFunctionSelectStub}
/>
);

expect(
screen.getByText(
/Please select a function or we will default fill this field/
)
).to.exist;
});

it('should not show warning banner when faker method is recognized', () => {
render(
<FakerMappingSelector
activeJsonType={mockActiveJsonType}
activeFakerFunction={mockActiveFakerFunction}
activeFakerArgs={mockActiveFakerArgs}
onJsonTypeSelect={onJsonTypeSelectStub}
onFakerFunctionSelect={onFakerFunctionSelectStub}
/>
);

expect(
screen.queryByText(
/Please select a function or we will default fill this field/
)
).to.not.exist;
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ import {
Select,
spacing,
} from '@mongodb-js/compass-components';
import React from 'react';
import React, { useMemo } from 'react';
import { UNRECOGNIZED_FAKER_METHOD } from '../../modules/collection-tab';
import type { MongoDBFieldType } from '@mongodb-js/compass-generative-ai';
import { MongoDBFieldTypeValues } from '@mongodb-js/compass-generative-ai';
import { MONGO_TYPE_TO_FAKER_METHODS } from './constants';
import type { FakerArg } from './script-generation-utils';

const fieldMappingSelectorsStyles = css({
Expand Down Expand Up @@ -58,7 +60,7 @@ const formatFakerFunctionCallWithArgs = (
};

interface Props {
activeJsonType: string;
activeJsonType: MongoDBFieldType;
activeFakerFunction: string;
onJsonTypeSelect: (jsonType: MongoDBFieldType) => void;
activeFakerArgs: FakerArg[];
Expand All @@ -72,6 +74,16 @@ const FakerMappingSelector = ({
onJsonTypeSelect,
onFakerFunctionSelect,
}: Props) => {
const fakerMethodOptions = useMemo(() => {
const methods = MONGO_TYPE_TO_FAKER_METHODS[activeJsonType] || [];

if (methods.includes(activeFakerFunction)) {
return methods;
}

return [activeFakerFunction, ...methods];
}, [activeJsonType, activeFakerFunction]);

return (
<div className={fieldMappingSelectorsStyles}>
<Body className={labelStyles}>Mapping</Body>
Expand All @@ -81,8 +93,7 @@ const FakerMappingSelector = ({
value={activeJsonType}
onChange={(value) => onJsonTypeSelect(value as MongoDBFieldType)}
>
{/* TODO(CLOUDP-344400) : Make the select input editable and render other options depending on the JSON type selected */}
{[activeJsonType].map((type) => (
{Object.values(MongoDBFieldTypeValues).map((type) => (
<Option key={type} value={type}>
{type}
</Option>
Expand All @@ -94,10 +105,9 @@ const FakerMappingSelector = ({
value={activeFakerFunction}
onChange={onFakerFunctionSelect}
>
{/* TODO(CLOUDP-344400): Make the select input editable and render other JSON types */}
{[activeFakerFunction].map((field) => (
<Option key={field} value={field}>
{field}
{fakerMethodOptions.map((method) => (
<Option key={method} value={method}>
{method}
</Option>
))}
</Select>
Expand Down
Loading
Loading