Skip to content

Commit 90efdeb

Browse files
ncarbonjcobis
andauthored
feat(compass-collection): Output Validation Followup - Mock Data Generator LLM CLOUDP-347048 (#7365)
* feat(compass-collection): CLOUDP-347048 LLM Output Validation Followup - Mock Data Generator * call faker method without args if invalid * update faker argument handling and improve validation logging * resolve TypeScript error by casting initialState to any * add faker argument validation for large numbers and add sample function call display * increase max number size, allow faker.helpers.arrayElements, parse args before call, update ui label * add/fix faker call preview test * replace parseFakerArgs with prepareFakerArgs for better argument handling * Address comment * Refactor util * Address comment * Comments --------- Co-authored-by: Jacob Samuel Lu <[email protected]> Co-authored-by: Jacob Lu <[email protected]>
1 parent 44cc2ed commit 90efdeb

File tree

7 files changed

+627
-39
lines changed

7 files changed

+627
-39
lines changed

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

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import {
22
Banner,
33
BannerVariant,
44
Body,
5+
Code,
56
css,
7+
Label,
68
Option,
79
palette,
810
Select,
@@ -11,6 +13,7 @@ import {
1113
import React from 'react';
1214
import { UNRECOGNIZED_FAKER_METHOD } from '../../modules/collection-tab';
1315
import type { MongoDBFieldType } from '@mongodb-js/compass-generative-ai';
16+
import type { FakerArg } from './script-generation-utils';
1417

1518
const fieldMappingSelectorsStyles = css({
1619
width: '50%',
@@ -24,16 +27,48 @@ const labelStyles = css({
2427
fontWeight: 600,
2528
});
2629

30+
const stringifyFakerArg = (arg: FakerArg): string => {
31+
if (typeof arg === 'object' && arg !== null && 'json' in arg) {
32+
try {
33+
return JSON.stringify(JSON.parse(arg.json));
34+
} catch {
35+
return '';
36+
}
37+
}
38+
39+
// Handle arrays recursively
40+
if (Array.isArray(arg)) {
41+
return `[${arg.map(stringifyFakerArg).join(', ')}]`;
42+
}
43+
44+
if (typeof arg === 'string') {
45+
return JSON.stringify(arg);
46+
}
47+
48+
// Numbers and booleans
49+
return String(arg);
50+
};
51+
52+
const formatFakerFunctionCallWithArgs = (
53+
fakerFunction: string,
54+
fakerArgs: FakerArg[]
55+
) => {
56+
const parsedFakerArgs = fakerArgs.map(stringifyFakerArg);
57+
return `faker.${fakerFunction}(${parsedFakerArgs.join(', ')})`;
58+
};
59+
2760
interface Props {
2861
activeJsonType: string;
2962
activeFakerFunction: string;
3063
onJsonTypeSelect: (jsonType: MongoDBFieldType) => void;
64+
activeFakerArgs: FakerArg[];
3165
onFakerFunctionSelect: (fakerFunction: string) => void;
3266
}
3367

3468
const FakerMappingSelector = ({
3569
activeJsonType,
3670
activeFakerFunction,
71+
activeFakerArgs,
3772
onJsonTypeSelect,
3873
onFakerFunctionSelect,
3974
}: Props) => {
@@ -66,13 +101,29 @@ const FakerMappingSelector = ({
66101
</Option>
67102
))}
68103
</Select>
69-
{activeFakerFunction === UNRECOGNIZED_FAKER_METHOD && (
104+
{activeFakerFunction === UNRECOGNIZED_FAKER_METHOD ? (
70105
<Banner variant={BannerVariant.Warning}>
71106
Please select a function or we will default fill this field with the
72107
string &quot;Unrecognized&quot;
73108
</Banner>
109+
) : (
110+
<>
111+
<Label htmlFor="faker-function-call-preview">
112+
Preview Faker Function Call
113+
</Label>
114+
<Code
115+
id="faker-function-call-preview"
116+
data-testid="faker-function-call-preview"
117+
language="javascript"
118+
copyable={false}
119+
>
120+
{formatFakerFunctionCallWithArgs(
121+
activeFakerFunction,
122+
activeFakerArgs
123+
)}
124+
</Code>
125+
</>
74126
)}
75-
{/* TODO(CLOUDP-344400): Render faker function parameters once we have a way to validate them. */}
76127
</div>
77128
);
78129
};

packages/compass-collection/src/components/mock-data-generator-modal/faker-schema-editor-screen.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ const FakerSchemaEditorContent = ({
6464

6565
const activeJsonType = fakerSchemaFormValues[activeField]?.mongoType;
6666
const activeFakerFunction = fakerSchemaFormValues[activeField]?.fakerMethod;
67+
const activeFakerArgs = fakerSchemaFormValues[activeField]?.fakerArgs;
6768

6869
const resetIsSchemaConfirmed = () => {
6970
onSchemaConfirmed(false);
@@ -109,6 +110,7 @@ const FakerSchemaEditorContent = ({
109110
<FakerMappingSelector
110111
activeJsonType={activeJsonType}
111112
activeFakerFunction={activeFakerFunction}
113+
activeFakerArgs={activeFakerArgs}
112114
onJsonTypeSelect={onJsonTypeSelect}
113115
onFakerFunctionSelect={onFakerFunctionSelect}
114116
/>

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

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ describe('MockDataGeneratorModal', () => {
7171

7272
const store = createStore(
7373
collectionTabReducer,
74-
initialState,
74+
initialState as any,
7575
applyMiddleware(thunk.withExtraArgument(mockServices))
7676
);
7777

@@ -545,6 +545,111 @@ describe('MockDataGeneratorModal', () => {
545545
);
546546
});
547547

548+
it('displays preview of the faker call without args when the args are invalid', async () => {
549+
const largeLengthArgs = Array.from({ length: 11 }, () => 'testArg');
550+
const mockServices = createMockServices();
551+
mockServices.atlasAiService.getMockDataSchema = () =>
552+
Promise.resolve({
553+
fields: [
554+
{
555+
fieldPath: 'name',
556+
mongoType: 'String',
557+
fakerMethod: 'person.firstName',
558+
fakerArgs: largeLengthArgs,
559+
isArray: false,
560+
probability: 1.0,
561+
},
562+
{
563+
fieldPath: 'age',
564+
mongoType: 'Int32',
565+
fakerMethod: 'number.int',
566+
fakerArgs: [
567+
{
568+
json: JSON.stringify({
569+
a: largeLengthArgs,
570+
}),
571+
},
572+
],
573+
isArray: false,
574+
probability: 1.0,
575+
},
576+
{
577+
fieldPath: 'username',
578+
mongoType: 'String',
579+
fakerMethod: 'string.alpha',
580+
// large string
581+
fakerArgs: ['a'.repeat(1001)],
582+
isArray: false,
583+
probability: 1.0,
584+
},
585+
{
586+
fieldPath: 'avatar',
587+
mongoType: 'String',
588+
fakerMethod: 'image.url',
589+
fakerArgs: [
590+
{
591+
json: JSON.stringify({
592+
width: 100_000,
593+
height: 100_000,
594+
}),
595+
},
596+
],
597+
isArray: false,
598+
probability: 1.0,
599+
},
600+
],
601+
});
602+
603+
await renderModal({
604+
mockServices,
605+
schemaAnalysis: {
606+
...defaultSchemaAnalysisState,
607+
processedSchema: {
608+
name: {
609+
type: 'String',
610+
probability: 1.0,
611+
},
612+
age: {
613+
type: 'Int32',
614+
probability: 1.0,
615+
},
616+
username: {
617+
type: 'String',
618+
probability: 1.0,
619+
},
620+
avatar: {
621+
type: 'String',
622+
probability: 1.0,
623+
},
624+
},
625+
},
626+
});
627+
628+
// advance to the schema editor step
629+
userEvent.click(screen.getByText('Confirm'));
630+
await waitFor(() => {
631+
expect(screen.getByTestId('faker-schema-editor')).to.exist;
632+
});
633+
634+
userEvent.click(screen.getByText('name'));
635+
expect(screen.getByTestId('faker-function-call-preview')).to.exist;
636+
expect(screen.queryByText(/testArg/)).to.not.exist;
637+
638+
userEvent.click(screen.getByText('age'));
639+
expect(screen.getByTestId('faker-function-call-preview')).to.exist;
640+
expect(screen.queryByText(/testArg/)).to.not.exist;
641+
642+
userEvent.click(screen.getByText('username'));
643+
expect(screen.queryByText(/aaaaaaa/)).to.not.exist;
644+
expect(screen.getByTestId('faker-function-call-preview')).to.exist;
645+
646+
userEvent.click(screen.getByText('avatar'));
647+
expect(screen.getByTestId('faker-function-call-preview')).to.exist;
648+
expect(screen.queryByText(/width/)).to.not.exist;
649+
expect(screen.queryByText(/height/)).to.not.exist;
650+
expect(screen.queryByText(/100000/)).to.not.exist;
651+
});
652+
548653
it('disables the Next button when the faker schema mapping is not confirmed', async () => {
549654
await renderModal({
550655
mockServices: mockServicesWithMockDataResponse,

packages/compass-collection/src/components/mock-data-generator-modal/script-generation-utils.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@ import type { MongoDBFieldType } from '@mongodb-js/compass-generative-ai';
22
import type { FakerFieldMapping } from './types';
33
import { prettify } from '@mongodb-js/compass-editor';
44

5-
export type FakerArg = string | number | boolean | { json: string };
5+
export type FakerArg =
6+
| string
7+
| number
8+
| boolean
9+
| { json: string }
10+
| FakerArg[];
611

712
const DEFAULT_ARRAY_LENGTH = 3;
813

0 commit comments

Comments
 (0)