From bd24caaa5e03a8e0edee422643c323ee2b37820a Mon Sep 17 00:00:00 2001 From: Nataly Carbonell Date: Mon, 8 Sep 2025 11:15:14 -0400 Subject: [PATCH 01/13] CLOUDP-333854: WIP --- package-lock.json | 23 +++ packages/compass-collection/package.json | 1 + .../faker-schema-editor-screen.tsx | 117 +++++++++++- .../field-mapping-selectors.tsx | 64 +++++++ .../mock-data-generator-modal.tsx | 27 ++- .../schema-field-selector.tsx | 108 +++++++++++ .../mock-data-generator-modal/types.ts | 16 ++ .../validate-faker-schema.spec.ts | 174 ++++++++++++++++++ .../validate-faker-schema.ts | 53 ++++++ .../src/components/vertical-rule.tsx | 42 +++++ packages/compass-components/src/index.ts | 1 + 11 files changed, 620 insertions(+), 6 deletions(-) create mode 100644 packages/compass-collection/src/components/mock-data-generator-modal/field-mapping-selectors.tsx create mode 100644 packages/compass-collection/src/components/mock-data-generator-modal/schema-field-selector.tsx create mode 100644 packages/compass-collection/src/components/mock-data-generator-modal/validate-faker-schema.spec.ts create mode 100644 packages/compass-collection/src/components/mock-data-generator-modal/validate-faker-schema.ts create mode 100644 packages/compass-components/src/components/vertical-rule.tsx diff --git a/package-lock.json b/package-lock.json index 6cb305f884f..0ba667e876a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6046,6 +6046,22 @@ "lodash.uniq": "^4.5.0" } }, + "node_modules/@faker-js/faker": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-10.0.0.tgz", + "integrity": "sha512-UollFEUkVXutsaP+Vndjxar40Gs5JL2HeLcl8xO1QAjJgOdhc3OmBFWyEylS+RddWaaBiAzH+5/17PLQJwDiLw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/fakerjs" + } + ], + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.13.0 || ^23.5.0 || >=24.0.0", + "npm": ">=10" + } + }, "node_modules/@fastify/busboy": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", @@ -47761,6 +47777,7 @@ "version": "4.72.0", "license": "SSPL", "dependencies": { + "@faker-js/faker": "^10.0.0", "@mongodb-js/compass-app-registry": "^9.4.22", "@mongodb-js/compass-app-stores": "^7.59.0", "@mongodb-js/compass-components": "^1.51.0", @@ -58094,6 +58111,11 @@ "lodash.uniq": "^4.5.0" } }, + "@faker-js/faker": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-10.0.0.tgz", + "integrity": "sha512-UollFEUkVXutsaP+Vndjxar40Gs5JL2HeLcl8xO1QAjJgOdhc3OmBFWyEylS+RddWaaBiAzH+5/17PLQJwDiLw==" + }, "@fastify/busboy": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", @@ -61347,6 +61369,7 @@ "@mongodb-js/compass-collection": { "version": "file:packages/compass-collection", "requires": { + "@faker-js/faker": "^10.0.0", "@mongodb-js/compass-app-registry": "^9.4.22", "@mongodb-js/compass-app-stores": "^7.59.0", "@mongodb-js/compass-components": "^1.51.0", diff --git a/packages/compass-collection/package.json b/packages/compass-collection/package.json index 2b85d985f9d..006df76991e 100644 --- a/packages/compass-collection/package.json +++ b/packages/compass-collection/package.json @@ -48,6 +48,7 @@ "reformat": "npm run eslint . -- --fix && npm run prettier -- --write ." }, "dependencies": { + "@faker-js/faker": "^10.0.0", "@mongodb-js/compass-app-registry": "^9.4.22", "@mongodb-js/compass-app-stores": "^7.59.0", "@mongodb-js/compass-components": "^1.51.0", diff --git a/packages/compass-collection/src/components/mock-data-generator-modal/faker-schema-editor-screen.tsx b/packages/compass-collection/src/components/mock-data-generator-modal/faker-schema-editor-screen.tsx index c888cafdcc7..bc951b154b3 100644 --- a/packages/compass-collection/src/components/mock-data-generator-modal/faker-schema-editor-screen.tsx +++ b/packages/compass-collection/src/components/mock-data-generator-modal/faker-schema-editor-screen.tsx @@ -1,10 +1,121 @@ +import { + Body, + Button, + ButtonVariant, + css, + H3, + Link, + spacing, + VerticalRule, +} from '@mongodb-js/compass-components'; import React from 'react'; +import FieldSelector from './schema-field-selector'; +import FakerMappingSelector from './field-mapping-selectors'; +import { FakerMapping } from './types'; + +const containerStyles = css({ + display: 'flex', + flexDirection: 'column', + gap: spacing[400], +}); + +const innerEditorStyles = css({ + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + maxHeight: '500px', + overflow: 'auto', +}); + +const confirmMappingsButtonStyles = css({ + width: '200px', +}); + +const FakerSchemaEditor = ({ + fakerSchema, +}: { + fakerSchema: Array; +}) => { + const [fakerSchemaFormValues, setFakerSchemaFormValues] = + React.useState>(fakerSchema); + const [activeField, setActiveField] = React.useState( + fakerSchemaFormValues[0].fieldPath + ); + + const activeJsonType = fakerSchemaFormValues.find( + (mapping) => mapping.fieldPath === activeField + )?.mongoType; + const activeFakerFunction = fakerSchemaFormValues.find( + (mapping) => mapping.fieldPath === activeField + )?.fakerMethod; + + const onJsonTypeSelect = (newJsonType: string) => { + const updatedFakerFieldMapping = fakerSchemaFormValues.find( + (mapping) => mapping.fieldPath === activeField + ); + if (updatedFakerFieldMapping) { + updatedFakerFieldMapping.mongoType = newJsonType; + setFakerSchemaFormValues( + fakerSchemaFormValues.map((mapping) => + mapping.fieldPath === activeField ? updatedFakerFieldMapping : mapping + ) + ); + } + }; + + const onFakerFunctionSelect = (newFakerFunction: string) => { + const updatedFakerFieldMapping = fakerSchemaFormValues.find( + (mapping) => mapping.fieldPath === activeField + ); + if (updatedFakerFieldMapping) { + updatedFakerFieldMapping.fakerMethod = newFakerFunction; + setFakerSchemaFormValues( + fakerSchemaFormValues.map((mapping) => + mapping.fieldPath === activeField ? updatedFakerFieldMapping : mapping + ) + ); + } + }; + + const onConfirmMappings = () => { + console.log('Clicked confirm mappings'); + }; -// TODO: More to come from CLOUDP-333853, CLOUDP-333854 const FakerSchemaEditorScreen = () => { return ( -
- Schema Editor Content Placeholder +
+
+

Confirm Field to Faker Function Mappings

+ + We have sampled your collection and created a schema based on your + documents. That schema has been sent to an LLM and it has returned the + following mapping between your schema fields and{' '} + faker functions. + +
+
+ mapping.fieldPath)} + onFieldSelect={setActiveField} + /> + + {activeJsonType && activeFakerFunction && ( + + )} +
+
); }; diff --git a/packages/compass-collection/src/components/mock-data-generator-modal/field-mapping-selectors.tsx b/packages/compass-collection/src/components/mock-data-generator-modal/field-mapping-selectors.tsx new file mode 100644 index 00000000000..40257d57a5f --- /dev/null +++ b/packages/compass-collection/src/components/mock-data-generator-modal/field-mapping-selectors.tsx @@ -0,0 +1,64 @@ +import { + Body, + css, + Option, + Select, + spacing, +} from '@mongodb-js/compass-components'; +import React from 'react'; + +const fieldMappingSelectorsStyles = css({ + width: '50%', + display: 'flex', + flexDirection: 'column', + gap: spacing[100], +}); + +interface Props { + activeJsonType: string; + activeFakerFunction: string; + onJsonTypeSelect: (jsonType: string) => void; + onFakerFunctionSelect: (fakerFunction: string) => void; +} + +const FakerMappingSelector = ({ + activeJsonType, + activeFakerFunction, + onJsonTypeSelect, + onFakerFunctionSelect, +}: Props) => { + return ( +
+ Mapping + + +
+ ); +}; + +export default FakerMappingSelector; diff --git a/packages/compass-collection/src/components/mock-data-generator-modal/mock-data-generator-modal.tsx b/packages/compass-collection/src/components/mock-data-generator-modal/mock-data-generator-modal.tsx index 5f6982de0cb..9efc0c08246 100644 --- a/packages/compass-collection/src/components/mock-data-generator-modal/mock-data-generator-modal.tsx +++ b/packages/compass-collection/src/components/mock-data-generator-modal/mock-data-generator-modal.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from 'react'; +import React, { useMemo, useEffect, useState } from 'react'; import { connect } from 'react-redux'; import { @@ -11,9 +11,10 @@ import { Modal, ModalFooter, spacing, + SpinLoaderWithLabel, } from '@mongodb-js/compass-components'; -import { MockDataGeneratorStep } from './types'; +import { type FakerMapping, MockDataGeneratorStep } from './types'; import { StepButtonLabelMap } from './constants'; import type { CollectionState } from '../../modules/collection-tab'; import { @@ -25,6 +26,7 @@ import { import RawSchemaConfirmationScreen from './raw-schema-confirmation-screen'; import FakerSchemaEditorScreen from './faker-schema-editor-screen'; import ScriptScreen from './script-screen'; +import type { MockDataSchemaResponse } from '@mongodb-js/compass-generative-ai'; const footerStyles = css` flex-direction: row; @@ -42,6 +44,12 @@ const namespaceStyles = css({ marginBottom: spacing[400], }); +const schemaEditorLoaderStyles = css({ + display: 'flex', + alignItems: 'center', + justifyContent: 'center', +}); + interface Props { isOpen: boolean; onClose: () => void; @@ -50,6 +58,7 @@ interface Props { onConfirmSchema: () => Promise; onPreviousStep: () => void; namespace: string; + fakerSchema?: MockDataSchemaResponse; } const MockDataGeneratorModal = ({ @@ -60,13 +69,21 @@ const MockDataGeneratorModal = ({ onConfirmSchema, onPreviousStep, namespace, + fakerSchema, }: Props) => { const modalBodyContent = useMemo(() => { switch (currentStep) { case MockDataGeneratorStep.SCHEMA_CONFIRMATION: return ; case MockDataGeneratorStep.SCHEMA_EDITOR: - return ; + return fakerSchema === undefined ? ( + + ) : ( + + ); case MockDataGeneratorStep.DOCUMENT_COUNT: return <>; // TODO: CLOUDP-333856 case MockDataGeneratorStep.PREVIEW_DATA: @@ -135,6 +152,10 @@ const mapStateToProps = (state: CollectionState) => ({ isOpen: state.mockDataGenerator.isModalOpen, currentStep: state.mockDataGenerator.currentStep, namespace: state.namespace, + fakerSchema: + state.fakerSchemaGeneration.status === 'completed' + ? state.fakerSchemaGeneration.fakerSchema + : undefined, }); const ConnectedMockDataGeneratorModal = connect(mapStateToProps, { diff --git a/packages/compass-collection/src/components/mock-data-generator-modal/schema-field-selector.tsx b/packages/compass-collection/src/components/mock-data-generator-modal/schema-field-selector.tsx new file mode 100644 index 00000000000..1fddab5222d --- /dev/null +++ b/packages/compass-collection/src/components/mock-data-generator-modal/schema-field-selector.tsx @@ -0,0 +1,108 @@ +import React from 'react'; +import { + css, + cx, + spacing, + palette, + useDarkMode, + Body, +} from '@mongodb-js/compass-components'; + +const fieldSelectorStyles = css({ + width: '40%', + display: 'flex', + flexDirection: 'column', + gap: spacing[100], +}); + +const buttonStyles = css({ + borderRadius: spacing[100], + cursor: 'pointer', + marginBottom: spacing[100], + background: 'none', + border: 'none', + width: '100%', + padding: spacing[200], + textAlign: 'left', + fontWeight: 500, +}); + +const hoverStylesLight = css({ + '&:hover,&:focus': { + backgroundColor: palette.green.light2, + color: palette.gray.dark3, + }, +}); + +const activeStylesLight = css({ + backgroundColor: palette.green.light3, + color: palette.gray.dark3, + '&:active,&:focus': { + backgroundColor: palette.green.light3, + color: palette.gray.dark3, + }, +}); + +const hoverStylesDark = css({ + '&:hover,&:focus': { + backgroundColor: palette.gray.dark3, + color: palette.white, + }, +}); + +const activeStylesDark = css({ + backgroundColor: palette.gray.dark2, + color: palette.white, + '&:active,&:focus': { + backgroundColor: palette.gray.dark2, + color: palette.white, + }, +}); + +type SidebarProps = { + activeField: string; + onFieldSelect: (field: string) => void; + fields: Array; +}; + +const FieldSelector: React.FunctionComponent = ({ + activeField, + fields, + onFieldSelect, +}) => { + const darkMode = useDarkMode(); + + return ( +
+ Document fields + + {fields.map((field) => ( + + ))} +
+ ); +}; + +export default FieldSelector; diff --git a/packages/compass-collection/src/components/mock-data-generator-modal/types.ts b/packages/compass-collection/src/components/mock-data-generator-modal/types.ts index 5812f3693a4..a89f8dd4339 100644 --- a/packages/compass-collection/src/components/mock-data-generator-modal/types.ts +++ b/packages/compass-collection/src/components/mock-data-generator-modal/types.ts @@ -34,3 +34,19 @@ export type MockDataGeneratorState = | MockDataGeneratorInProgressState | MockDataGeneratorCompletedState | MockDataGeneratorErrorState; + +export type FakerMapping = { + fieldPath: string; + mongoType: string; + fakerMethod: string; + fakerArgs: Array< + | string + | number + | boolean + | { + json: string; + } + >; + isArray: boolean; + probability: number; +}; diff --git a/packages/compass-collection/src/components/mock-data-generator-modal/validate-faker-schema.spec.ts b/packages/compass-collection/src/components/mock-data-generator-modal/validate-faker-schema.spec.ts new file mode 100644 index 00000000000..1fa84241b77 --- /dev/null +++ b/packages/compass-collection/src/components/mock-data-generator-modal/validate-faker-schema.spec.ts @@ -0,0 +1,174 @@ +import { expect } from 'chai'; +import sinon from 'sinon'; + +import { validateFakerSchema } from './validate-faker-schema'; +import type { MockDataSchemaResponse } from '@mongodb-js/compass-generative-ai'; + +describe('validateFakerSchema', () => { + let sandbox: sinon.SinonSandbox; + let fakeFaker: any; + let fakerModule: any; + + beforeEach(async () => { + sandbox = sinon.createSandbox(); + // Create a fake faker object with nested methods + fakeFaker = { + name: { + firstName: sandbox.stub().returns('John'), + lastName: sandbox.stub().returns('Doe'), + }, + address: { + city: sandbox.stub().returns('New York'), + }, + mongodbObjectId: sandbox.stub().returns('507f1f77bcf86cd799439011'), + }; + fakerModule = await import('@faker-js/faker'); + // Stub the imported 'faker' in the module + sandbox.stub(fakerModule, 'faker').value(fakeFaker); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should return the same field if the faker method exists and works with arguments', async () => { + const schema: MockDataSchemaResponse = { + content: { + fields: [ + { + fieldPath: 'name', + mongoType: 'string', + fakerMethod: 'name.firstName', + fakerArgs: ['male'], + isArray: false, + probability: 1, + }, + ], + }, + }; + // Make sure the stub can accept arguments + fakeFaker.name.firstName.withArgs(['male']).returns('John'); + + const result = await validateFakerSchema(schema); + expect(result[0].fakerMethod).to.equal('name.firstName'); + expect(result[0].fakerArgs).to.deep.equal(['male']); + }); + + it('should return the same field if the faker method exists and works without arguments', async () => { + const schema: MockDataSchemaResponse = { + content: { + fields: [ + { + fieldPath: 'city', + fakerMethod: 'address.city', + fakerArgs: [], + isArray: false, + probability: 1, + mongoType: 'string', + }, + ], + }, + }; + + const result = await validateFakerSchema(schema); + expect(result[0].fakerMethod).to.equal('address.city'); + expect(result[0].fakerArgs).to.deep.equal([]); + }); + + it('should mark the method as Unrecognized if method does not exist', async () => { + const schema: MockDataSchemaResponse = { + content: { + fields: [ + { + fieldPath: 'foo', + fakerMethod: 'foo.bar', + fakerArgs: [], + isArray: false, + probability: 1, + mongoType: 'string', + }, + ], + }, + }; + + const result = await validateFakerSchema(schema); + expect(result[0].fakerMethod).to.equal('Unrecognized'); + expect(result[0].fakerArgs).to.deep.equal([]); + }); + + it('should mark the method as Unrecognized if method throws with and without arguments', async () => { + fakeFaker.name.firstName.throws(new Error('fail')); + const schema: MockDataSchemaResponse = { + content: { + fields: [ + { + fieldPath: 'name', + fakerMethod: 'name.firstName', + fakerArgs: ['male'], + isArray: false, + probability: 1, + mongoType: 'string', + }, + ], + }, + }; + + const result = await validateFakerSchema(schema); + expect(result[0].fakerMethod).to.equal('Unrecognized'); + expect(result[0].fakerArgs).to.deep.equal([]); + }); + + it('should try without arguments if method throws with arguments', async () => { + fakeFaker.name.firstName.withArgs(['male']).throws(new Error('fail')); + fakeFaker.name.firstName.withArgs().returns('John'); + const schema: MockDataSchemaResponse = { + content: { + fields: [ + { + fieldPath: 'name', + fakerMethod: 'name.firstName', + fakerArgs: ['male'], + isArray: false, + probability: 1, + mongoType: 'string', + }, + ], + }, + }; + + const result = await validateFakerSchema(schema); + expect(result[0].fakerMethod).to.equal('name.firstName'); + expect(result[0].fakerArgs).to.deep.equal(['male']); + }); + + it('should handle multiple fields with mixed validity', async () => { + fakeFaker.name.firstName.returns('John'); + fakeFaker.address.city.throws(new Error('fail')); + const schema: MockDataSchemaResponse = { + content: { + fields: [ + { + fieldPath: 'name', + fakerMethod: 'name.firstName', + fakerArgs: [], + isArray: false, + probability: 1, + mongoType: 'string', + }, + { + fieldPath: 'city', + fakerMethod: 'address.city', + fakerArgs: [], + isArray: false, + probability: 1, + mongoType: 'string', + }, + ], + }, + }; + + const result = await validateFakerSchema(schema); + expect(result[0].fakerMethod).to.equal('name.firstName'); + expect(result[1].fakerMethod).to.equal('Unrecognized'); + }); +}); diff --git a/packages/compass-collection/src/components/mock-data-generator-modal/validate-faker-schema.ts b/packages/compass-collection/src/components/mock-data-generator-modal/validate-faker-schema.ts new file mode 100644 index 00000000000..c65a6c42bb5 --- /dev/null +++ b/packages/compass-collection/src/components/mock-data-generator-modal/validate-faker-schema.ts @@ -0,0 +1,53 @@ +import type { MockDataSchemaResponse } from '@mongodb-js/compass-generative-ai'; + +const UNRECOGNIZED_FAKER_METHOD = 'Unrecognized'; + +/** + * Validates each field in the provided faker schema by attempting to execute the specified faker method + * with and without arguments. If the method execution fails, it marks it as 'Unrecognized'. + * + * @param {MockDataSchemaResponse} fakerSchema - The schema containing fields with faker methods and arguments. + * @returns {Array} The array of fields with validation results, marking unrecognized methods as needed. + */ +export const validateFakerSchema = async ( + fakerSchema: MockDataSchemaResponse +) => { + const { faker } = await import('@faker-js/faker'); + return fakerSchema.content.fields.map((field) => { + const { fakerMethod, fakerArgs } = field; + + const [first, second] = fakerMethod.split('.'); + try { + // Try with arguments first + const fakerMethodWithArgs = eval( + `(faker, ...fakerArgs) => faker["${first}"]["${second}"](...fakerArgs)` + ); + fakerMethodWithArgs(faker, ...fakerArgs); + return field; + } catch (error) { + console.error(error); + // If that fails and there are arguments, try without arguments + if (fakerArgs.length > 0) { + try { + const fakerMethodWithoutArgs = eval( + `(faker) => faker["${first}"]["${second}"]()` + ); + fakerMethodWithoutArgs(faker); + return field; + } catch (error) { + console.error(error); + return { + ...field, + fakerMethod: UNRECOGNIZED_FAKER_METHOD, + fakerArgs: [], + }; + } + } + return { + ...field, + fakerMethod: 'Unrecognized', + fakerArgs: [], + }; + } + }); +}; diff --git a/packages/compass-components/src/components/vertical-rule.tsx b/packages/compass-components/src/components/vertical-rule.tsx new file mode 100644 index 00000000000..d12a3b05519 --- /dev/null +++ b/packages/compass-components/src/components/vertical-rule.tsx @@ -0,0 +1,42 @@ +import { css, cx } from '@leafygreen-ui/emotion'; +import { palette } from '@leafygreen-ui/palette'; +import type { HTMLProps } from 'react'; +import React, { forwardRef } from 'react'; + +import { useDarkMode } from '../hooks/use-theme'; + +const verticalRuleStyles = css({ + height: '100%', + width: 0, + display: 'block', + borderTopWidth: 1, + borderTopStyle: 'solid', +}); + +const verticalRuleStylesLight = css({ + borderTopColor: palette.gray.light2, +}); + +const verticalRuleStylesDark = css({ + borderTopColor: palette.gray.dark2, +}); + +export const VerticalRule = forwardRef< + HTMLDivElement, + HTMLProps +>(({ className, ...props }, ref) => { + const darkMode = useDarkMode(); + return ( +
+ ); +}); + +VerticalRule.displayName = 'VerticalRule'; diff --git a/packages/compass-components/src/index.ts b/packages/compass-components/src/index.ts index ffacf0f8226..fa96d79d394 100644 --- a/packages/compass-components/src/index.ts +++ b/packages/compass-components/src/index.ts @@ -188,6 +188,7 @@ export { KeylineCard } from './components/keyline-card'; export { variantColors as codePalette } from '@leafygreen-ui/code'; export { useEffectOnChange } from './hooks/use-effect-on-change'; export { HorizontalRule } from './components/horizontal-rule'; +export { VerticalRule } from './components/vertical-rule'; export { IndexBadge, IndexKeysBadge } from './components/index-keys-badge'; export { ConfirmationModalVariant, From 64ac4b09d7d9f59e3587627c60b89666a679f329 Mon Sep 17 00:00:00 2001 From: Nataly Carbonell Date: Tue, 9 Sep 2025 00:51:35 -0400 Subject: [PATCH 02/13] reorganize faker schema validation and improve component structure --- package-lock.json | 5 +- packages/compass-collection/package.json | 1 + .../faker-mapping-selector.tsx | 122 ++++++++++++ .../faker-schema-editor-screen.tsx | 49 +++-- .../field-mapping-selectors.tsx | 64 ------- .../mock-data-generator-modal.spec.tsx | 4 +- .../mock-data-generator-modal.tsx | 21 ++- .../schema-field-selector.tsx | 93 +++++----- .../mock-data-generator-modal/types.ts | 6 +- .../validate-faker-schema.spec.ts | 174 ------------------ .../validate-faker-schema.ts | 53 ------ .../src/modules/collection-tab.ts | 74 +++++++- packages/compass-components/src/index.ts | 2 +- 13 files changed, 306 insertions(+), 362 deletions(-) create mode 100644 packages/compass-collection/src/components/mock-data-generator-modal/faker-mapping-selector.tsx delete mode 100644 packages/compass-collection/src/components/mock-data-generator-modal/field-mapping-selectors.tsx delete mode 100644 packages/compass-collection/src/components/mock-data-generator-modal/validate-faker-schema.spec.ts delete mode 100644 packages/compass-collection/src/components/mock-data-generator-modal/validate-faker-schema.ts diff --git a/package-lock.json b/package-lock.json index 0ba667e876a..26b6505f96a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6050,6 +6050,7 @@ "version": "10.0.0", "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-10.0.0.tgz", "integrity": "sha512-UollFEUkVXutsaP+Vndjxar40Gs5JL2HeLcl8xO1QAjJgOdhc3OmBFWyEylS+RddWaaBiAzH+5/17PLQJwDiLw==", + "dev": true, "funding": [ { "type": "opencollective", @@ -47802,6 +47803,7 @@ "redux-thunk": "^2.4.2" }, "devDependencies": { + "@faker-js/faker": "^10.0.0", "@mongodb-js/eslint-config-compass": "^1.4.9", "@mongodb-js/mdb-experiment-js": "1.9.0", "@mongodb-js/mocha-config-compass": "^1.7.1", @@ -58114,7 +58116,8 @@ "@faker-js/faker": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-10.0.0.tgz", - "integrity": "sha512-UollFEUkVXutsaP+Vndjxar40Gs5JL2HeLcl8xO1QAjJgOdhc3OmBFWyEylS+RddWaaBiAzH+5/17PLQJwDiLw==" + "integrity": "sha512-UollFEUkVXutsaP+Vndjxar40Gs5JL2HeLcl8xO1QAjJgOdhc3OmBFWyEylS+RddWaaBiAzH+5/17PLQJwDiLw==", + "dev": true }, "@fastify/busboy": { "version": "2.1.1", diff --git a/packages/compass-collection/package.json b/packages/compass-collection/package.json index 006df76991e..c6cf7383cb9 100644 --- a/packages/compass-collection/package.json +++ b/packages/compass-collection/package.json @@ -73,6 +73,7 @@ "redux-thunk": "^2.4.2" }, "devDependencies": { + "@faker-js/faker": "^10.0.0", "@mongodb-js/eslint-config-compass": "^1.4.9", "@mongodb-js/mdb-experiment-js": "1.9.0", "@mongodb-js/mocha-config-compass": "^1.7.1", diff --git a/packages/compass-collection/src/components/mock-data-generator-modal/faker-mapping-selector.tsx b/packages/compass-collection/src/components/mock-data-generator-modal/faker-mapping-selector.tsx new file mode 100644 index 00000000000..ff016989ccf --- /dev/null +++ b/packages/compass-collection/src/components/mock-data-generator-modal/faker-mapping-selector.tsx @@ -0,0 +1,122 @@ +import { + Banner, + BannerVariant, + Body, + css, + Option, + palette, + Select, + spacing, + TextInput, +} from '@mongodb-js/compass-components'; +import React from 'react'; +import { UNRECOGNIZED_FAKER_METHOD } from '../../modules/collection-tab'; + +const fieldMappingSelectorsStyles = css({ + width: '50%', + display: 'flex', + flexDirection: 'column', + gap: spacing[200], +}); + +const labelStyles = css({ + color: palette.gray.dark1, + fontWeight: 600, +}); + +interface Props { + activeJsonType: string; + activeFakerFunction: string; + activeFakerArgs: Array; + onJsonTypeSelect: (jsonType: string) => void; + onFakerFunctionSelect: (fakerFunction: string) => void; +} + +const FakerMappingSelector = ({ + activeJsonType, + activeFakerFunction, + activeFakerArgs, + onJsonTypeSelect, + onFakerFunctionSelect, +}: Props) => { + return ( +
+ Mapping + + + {activeFakerFunction === UNRECOGNIZED_FAKER_METHOD && ( + + Please select a function or we will default fill this field with the + string “PLACEHOLDER” + + )} + {activeFakerArgs.map((arg, idx) => { + if (typeof arg === 'string') { + return ( + + ); + } + if (typeof arg === 'number') { + return ( + + ); + } + if (typeof arg === 'boolean') { + return ( + + ); + } + return ( + + ); + })} +
+ ); +}; + +export default FakerMappingSelector; diff --git a/packages/compass-collection/src/components/mock-data-generator-modal/faker-schema-editor-screen.tsx b/packages/compass-collection/src/components/mock-data-generator-modal/faker-schema-editor-screen.tsx index bc951b154b3..4bc3439ea53 100644 --- a/packages/compass-collection/src/components/mock-data-generator-modal/faker-schema-editor-screen.tsx +++ b/packages/compass-collection/src/components/mock-data-generator-modal/faker-schema-editor-screen.tsx @@ -1,17 +1,17 @@ import { Body, Button, + ButtonSize, ButtonVariant, css, - H3, Link, + palette, spacing, - VerticalRule, } from '@mongodb-js/compass-components'; import React from 'react'; import FieldSelector from './schema-field-selector'; -import FakerMappingSelector from './field-mapping-selectors'; -import { FakerMapping } from './types'; +import FakerMappingSelector from './faker-mapping-selector'; +import type { FakerSchemaMapping } from './types'; const containerStyles = css({ display: 'flex', @@ -23,8 +23,18 @@ const innerEditorStyles = css({ display: 'flex', flexDirection: 'row', justifyContent: 'space-between', - maxHeight: '500px', - overflow: 'auto', +}); + +const titleStyles = css({ + color: palette.black, + fontWeight: 600, + fontSize: '16px', + lineHeight: '20px', + marginBottom: 0, +}); + +const bodyStyles = css({ + color: palette.gray.dark1, }); const confirmMappingsButtonStyles = css({ @@ -32,12 +42,15 @@ const confirmMappingsButtonStyles = css({ }); const FakerSchemaEditor = ({ - fakerSchema, + onSchemaConfirmed, + fakerMappings, }: { - fakerSchema: Array; + isSchemaConfirmed: boolean; + onSchemaConfirmed: () => void; + fakerMappings: Array; }) => { const [fakerSchemaFormValues, setFakerSchemaFormValues] = - React.useState>(fakerSchema); + React.useState>(fakerMappings); const [activeField, setActiveField] = React.useState( fakerSchemaFormValues[0].fieldPath ); @@ -49,6 +62,10 @@ const FakerSchemaEditor = ({ (mapping) => mapping.fieldPath === activeField )?.fakerMethod; + const activeFakerArgs = fakerSchemaFormValues.find( + (mapping) => mapping.fieldPath === activeField + )?.fakerArgs; + const onJsonTypeSelect = (newJsonType: string) => { const updatedFakerFieldMapping = fakerSchemaFormValues.find( (mapping) => mapping.fieldPath === activeField @@ -78,19 +95,22 @@ const FakerSchemaEditor = ({ }; const onConfirmMappings = () => { - console.log('Clicked confirm mappings'); + onSchemaConfirmed(); }; const FakerSchemaEditorScreen = () => { return (
-

Confirm Field to Faker Function Mappings

- +

+ Confirm Field to Faker Function Mappings +

+ We have sampled your collection and created a schema based on your documents. That schema has been sent to an LLM and it has returned the following mapping between your schema fields and{' '} - faker functions. + faker functions + .
@@ -99,17 +119,18 @@ const FakerSchemaEditorScreen = () => { fields={fakerSchemaFormValues.map((mapping) => mapping.fieldPath)} onFieldSelect={setActiveField} /> - {activeJsonType && activeFakerFunction && ( )}
diff --git a/packages/compass-collection/src/components/mock-data-generator-modal/schema-field-selector.tsx b/packages/compass-collection/src/components/mock-data-generator-modal/schema-field-selector.tsx index 1fddab5222d..ccfeff58482 100644 --- a/packages/compass-collection/src/components/mock-data-generator-modal/schema-field-selector.tsx +++ b/packages/compass-collection/src/components/mock-data-generator-modal/schema-field-selector.tsx @@ -8,13 +8,18 @@ import { Body, } from '@mongodb-js/compass-components'; -const fieldSelectorStyles = css({ +const fieldsContainerStyles = css({ width: '40%', display: 'flex', flexDirection: 'column', gap: spacing[100], }); +const fieldSelectorStyles = css({ + maxHeight: '300px', + overflow: 'auto', +}); + const buttonStyles = css({ borderRadius: spacing[100], cursor: 'pointer', @@ -27,38 +32,43 @@ const buttonStyles = css({ fontWeight: 500, }); -const hoverStylesLight = css({ - '&:hover,&:focus': { - backgroundColor: palette.green.light2, - color: palette.gray.dark3, - }, -}); - const activeStylesLight = css({ + color: palette.green.dark2, backgroundColor: palette.green.light3, - color: palette.gray.dark3, + fontWeight: 600, + '&:active,&:focus': { backgroundColor: palette.green.light3, - color: palette.gray.dark3, }, }); -const hoverStylesDark = css({ - '&:hover,&:focus': { +const activeStylesDark = css({ + color: palette.white, + '&:active,&:focus': { backgroundColor: palette.gray.dark3, color: palette.white, }, }); -const activeStylesDark = css({ - backgroundColor: palette.gray.dark2, - color: palette.white, - '&:active,&:focus': { - backgroundColor: palette.gray.dark2, - color: palette.white, +const hoverStylesLight = css({ + '&:hover,&:focus': { + backgroundColor: palette.gray.light2, + color: palette.black, }, }); +const hoverStylesDark = css({ + '&:hover,&:focus': { + backgroundColor: palette.gray.dark3, + color: palette.gray.light2, + }, +}); + +const labelStyles = css({ + color: palette.gray.dark1, + fontWeight: 600, +}); + type SidebarProps = { activeField: string; onFieldSelect: (field: string) => void; @@ -77,30 +87,31 @@ const FieldSelector: React.FunctionComponent = ({ data-testid="schema-field-selector" role="tablist" aria-label="Schema Field Selector" - className={fieldSelectorStyles} + className={fieldsContainerStyles} > - Document fields - - {fields.map((field) => ( - - ))} + Document Fields +
+ {fields.map((field) => ( + + ))} +
); }; diff --git a/packages/compass-collection/src/components/mock-data-generator-modal/types.ts b/packages/compass-collection/src/components/mock-data-generator-modal/types.ts index a89f8dd4339..563f5a8bb2a 100644 --- a/packages/compass-collection/src/components/mock-data-generator-modal/types.ts +++ b/packages/compass-collection/src/components/mock-data-generator-modal/types.ts @@ -1,5 +1,3 @@ -import type { MockDataSchemaResponse } from '@mongodb-js/compass-generative-ai'; - export enum MockDataGeneratorStep { SCHEMA_CONFIRMATION = 'SCHEMA_CONFIRMATION', SCHEMA_EDITOR = 'SCHEMA_EDITOR', @@ -19,7 +17,7 @@ type MockDataGeneratorInProgressState = { type MockDataGeneratorCompletedState = { status: 'completed'; - fakerSchema: MockDataSchemaResponse; + fakerSchema: Array; requestId: string; }; @@ -35,7 +33,7 @@ export type MockDataGeneratorState = | MockDataGeneratorCompletedState | MockDataGeneratorErrorState; -export type FakerMapping = { +export type FakerSchemaMapping = { fieldPath: string; mongoType: string; fakerMethod: string; diff --git a/packages/compass-collection/src/components/mock-data-generator-modal/validate-faker-schema.spec.ts b/packages/compass-collection/src/components/mock-data-generator-modal/validate-faker-schema.spec.ts deleted file mode 100644 index 1fa84241b77..00000000000 --- a/packages/compass-collection/src/components/mock-data-generator-modal/validate-faker-schema.spec.ts +++ /dev/null @@ -1,174 +0,0 @@ -import { expect } from 'chai'; -import sinon from 'sinon'; - -import { validateFakerSchema } from './validate-faker-schema'; -import type { MockDataSchemaResponse } from '@mongodb-js/compass-generative-ai'; - -describe('validateFakerSchema', () => { - let sandbox: sinon.SinonSandbox; - let fakeFaker: any; - let fakerModule: any; - - beforeEach(async () => { - sandbox = sinon.createSandbox(); - // Create a fake faker object with nested methods - fakeFaker = { - name: { - firstName: sandbox.stub().returns('John'), - lastName: sandbox.stub().returns('Doe'), - }, - address: { - city: sandbox.stub().returns('New York'), - }, - mongodbObjectId: sandbox.stub().returns('507f1f77bcf86cd799439011'), - }; - fakerModule = await import('@faker-js/faker'); - // Stub the imported 'faker' in the module - sandbox.stub(fakerModule, 'faker').value(fakeFaker); - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('should return the same field if the faker method exists and works with arguments', async () => { - const schema: MockDataSchemaResponse = { - content: { - fields: [ - { - fieldPath: 'name', - mongoType: 'string', - fakerMethod: 'name.firstName', - fakerArgs: ['male'], - isArray: false, - probability: 1, - }, - ], - }, - }; - // Make sure the stub can accept arguments - fakeFaker.name.firstName.withArgs(['male']).returns('John'); - - const result = await validateFakerSchema(schema); - expect(result[0].fakerMethod).to.equal('name.firstName'); - expect(result[0].fakerArgs).to.deep.equal(['male']); - }); - - it('should return the same field if the faker method exists and works without arguments', async () => { - const schema: MockDataSchemaResponse = { - content: { - fields: [ - { - fieldPath: 'city', - fakerMethod: 'address.city', - fakerArgs: [], - isArray: false, - probability: 1, - mongoType: 'string', - }, - ], - }, - }; - - const result = await validateFakerSchema(schema); - expect(result[0].fakerMethod).to.equal('address.city'); - expect(result[0].fakerArgs).to.deep.equal([]); - }); - - it('should mark the method as Unrecognized if method does not exist', async () => { - const schema: MockDataSchemaResponse = { - content: { - fields: [ - { - fieldPath: 'foo', - fakerMethod: 'foo.bar', - fakerArgs: [], - isArray: false, - probability: 1, - mongoType: 'string', - }, - ], - }, - }; - - const result = await validateFakerSchema(schema); - expect(result[0].fakerMethod).to.equal('Unrecognized'); - expect(result[0].fakerArgs).to.deep.equal([]); - }); - - it('should mark the method as Unrecognized if method throws with and without arguments', async () => { - fakeFaker.name.firstName.throws(new Error('fail')); - const schema: MockDataSchemaResponse = { - content: { - fields: [ - { - fieldPath: 'name', - fakerMethod: 'name.firstName', - fakerArgs: ['male'], - isArray: false, - probability: 1, - mongoType: 'string', - }, - ], - }, - }; - - const result = await validateFakerSchema(schema); - expect(result[0].fakerMethod).to.equal('Unrecognized'); - expect(result[0].fakerArgs).to.deep.equal([]); - }); - - it('should try without arguments if method throws with arguments', async () => { - fakeFaker.name.firstName.withArgs(['male']).throws(new Error('fail')); - fakeFaker.name.firstName.withArgs().returns('John'); - const schema: MockDataSchemaResponse = { - content: { - fields: [ - { - fieldPath: 'name', - fakerMethod: 'name.firstName', - fakerArgs: ['male'], - isArray: false, - probability: 1, - mongoType: 'string', - }, - ], - }, - }; - - const result = await validateFakerSchema(schema); - expect(result[0].fakerMethod).to.equal('name.firstName'); - expect(result[0].fakerArgs).to.deep.equal(['male']); - }); - - it('should handle multiple fields with mixed validity', async () => { - fakeFaker.name.firstName.returns('John'); - fakeFaker.address.city.throws(new Error('fail')); - const schema: MockDataSchemaResponse = { - content: { - fields: [ - { - fieldPath: 'name', - fakerMethod: 'name.firstName', - fakerArgs: [], - isArray: false, - probability: 1, - mongoType: 'string', - }, - { - fieldPath: 'city', - fakerMethod: 'address.city', - fakerArgs: [], - isArray: false, - probability: 1, - mongoType: 'string', - }, - ], - }, - }; - - const result = await validateFakerSchema(schema); - expect(result[0].fakerMethod).to.equal('name.firstName'); - expect(result[1].fakerMethod).to.equal('Unrecognized'); - }); -}); diff --git a/packages/compass-collection/src/components/mock-data-generator-modal/validate-faker-schema.ts b/packages/compass-collection/src/components/mock-data-generator-modal/validate-faker-schema.ts deleted file mode 100644 index c65a6c42bb5..00000000000 --- a/packages/compass-collection/src/components/mock-data-generator-modal/validate-faker-schema.ts +++ /dev/null @@ -1,53 +0,0 @@ -import type { MockDataSchemaResponse } from '@mongodb-js/compass-generative-ai'; - -const UNRECOGNIZED_FAKER_METHOD = 'Unrecognized'; - -/** - * Validates each field in the provided faker schema by attempting to execute the specified faker method - * with and without arguments. If the method execution fails, it marks it as 'Unrecognized'. - * - * @param {MockDataSchemaResponse} fakerSchema - The schema containing fields with faker methods and arguments. - * @returns {Array} The array of fields with validation results, marking unrecognized methods as needed. - */ -export const validateFakerSchema = async ( - fakerSchema: MockDataSchemaResponse -) => { - const { faker } = await import('@faker-js/faker'); - return fakerSchema.content.fields.map((field) => { - const { fakerMethod, fakerArgs } = field; - - const [first, second] = fakerMethod.split('.'); - try { - // Try with arguments first - const fakerMethodWithArgs = eval( - `(faker, ...fakerArgs) => faker["${first}"]["${second}"](...fakerArgs)` - ); - fakerMethodWithArgs(faker, ...fakerArgs); - return field; - } catch (error) { - console.error(error); - // If that fails and there are arguments, try without arguments - if (fakerArgs.length > 0) { - try { - const fakerMethodWithoutArgs = eval( - `(faker) => faker["${first}"]["${second}"]()` - ); - fakerMethodWithoutArgs(faker); - return field; - } catch (error) { - console.error(error); - return { - ...field, - fakerMethod: UNRECOGNIZED_FAKER_METHOD, - fakerArgs: [], - }; - } - } - return { - ...field, - fakerMethod: 'Unrecognized', - fakerArgs: [], - }; - } - }); -}; diff --git a/packages/compass-collection/src/modules/collection-tab.ts b/packages/compass-collection/src/modules/collection-tab.ts index 8c7323045f6..c40e169a86c 100644 --- a/packages/compass-collection/src/modules/collection-tab.ts +++ b/packages/compass-collection/src/modules/collection-tab.ts @@ -38,7 +38,10 @@ import { } from '../transform-schema-to-field-info'; import type { Document, MongoError } from 'mongodb'; import { MockDataGeneratorStep } from '../components/mock-data-generator-modal/types'; -import type { MockDataGeneratorState } from '../components/mock-data-generator-modal/types'; +import type { + FakerSchemaMapping, + MockDataGeneratorState, +} from '../components/mock-data-generator-modal/types'; const DEFAULT_SAMPLE_SIZE = 100; @@ -52,6 +55,7 @@ function isAction( } const ERROR_CODE_MAX_TIME_MS_EXPIRED = 50; +export const UNRECOGNIZED_FAKER_METHOD = 'Unrecognized'; function getErrorDetails(error: Error): SchemaAnalysisError { if (error instanceof ProcessSchemaUnsupportedStateError) { @@ -178,7 +182,7 @@ export interface FakerMappingGenerationStartedAction { export interface FakerMappingGenerationCompletedAction { type: CollectionActions.FakerMappingGenerationCompleted; - fakerSchema: MockDataSchemaResponse; + fakerSchema: Array; requestId: string; } @@ -692,6 +696,68 @@ export const cancelSchemaAnalysis = (): CollectionThunkAction => { }; }; +const validateFakerSchema = async ( + fakerSchema: MockDataSchemaResponse, + logger: Logger +) => { + const { faker } = await import('@faker-js/faker'); + return fakerSchema.content.fields.map((field) => { + const { fakerMethod, fakerArgs } = field; + + const [first, second] = fakerMethod.split('.'); + try { + // Try with arguments first + const fakerMethodWithArgs = eval( + `(faker, ...fakerArgs) => faker["${first}"]["${second}"](...fakerArgs)` + ); + fakerMethodWithArgs(faker, ...fakerArgs); + return field; + } catch (error) { + // If that fails and there are arguments, try without arguments + if (fakerArgs.length > 0) { + try { + const fakerMethodWithoutArgs = eval( + `(faker) => faker["${first}"]["${second}"]()` + ); + fakerMethodWithoutArgs(faker); + return field; + } catch (error) { + logger.log.debug( + mongoLogId(1_001_000_371), + 'Collection', + 'Failed to validate faker schema with arguments', + { + error: error instanceof Error ? error.message : String(error), + fakerMethod, + fakerArgs, + } + ); + return { + ...field, + fakerMethod: UNRECOGNIZED_FAKER_METHOD, + fakerArgs: [], + }; + } + } + logger.log.debug( + mongoLogId(1_001_000_372), + 'Collection', + 'Failed to validate faker schema', + { + error: error instanceof Error ? error.message : String(error), + fakerMethod, + fakerArgs, + } + ); + return { + ...field, + fakerMethod: UNRECOGNIZED_FAKER_METHOD, + fakerArgs: [], + }; + } + }); +}; + export const generateFakerMappings = (): CollectionThunkAction< Promise > => { @@ -758,10 +824,12 @@ export const generateFakerMappings = (): CollectionThunkAction< connectionInfoRef.current ); + const validatedFakerSchema = await validateFakerSchema(response, logger); + fakerSchemaGenerationAbortControllerRef.current = undefined; dispatch({ type: CollectionActions.FakerMappingGenerationCompleted, - fakerSchema: response, + fakerSchema: validatedFakerSchema, requestId: requestId, }); } catch (e) { diff --git a/packages/compass-components/src/index.ts b/packages/compass-components/src/index.ts index fa96d79d394..e2399f9a47e 100644 --- a/packages/compass-components/src/index.ts +++ b/packages/compass-components/src/index.ts @@ -188,7 +188,6 @@ export { KeylineCard } from './components/keyline-card'; export { variantColors as codePalette } from '@leafygreen-ui/code'; export { useEffectOnChange } from './hooks/use-effect-on-change'; export { HorizontalRule } from './components/horizontal-rule'; -export { VerticalRule } from './components/vertical-rule'; export { IndexBadge, IndexKeysBadge } from './components/index-keys-badge'; export { ConfirmationModalVariant, @@ -229,3 +228,4 @@ export { ParagraphSkeleton } from '@leafygreen-ui/skeleton-loader'; export { InsightsChip } from './components/insights-chip'; export * from './components/drawer-portal'; export { FileSelector } from './components/file-selector'; +export { transitionDuration } from '@leafygreen-ui/tokens'; From 24b1b1b46243ac6a8100da01c580026704389513 Mon Sep 17 00:00:00 2001 From: Nataly Carbonell Date: Tue, 9 Sep 2025 00:58:12 -0400 Subject: [PATCH 03/13] remove duplicate return --- packages/compass-collection/src/modules/collection-tab.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/compass-collection/src/modules/collection-tab.ts b/packages/compass-collection/src/modules/collection-tab.ts index c40e169a86c..95f2568167c 100644 --- a/packages/compass-collection/src/modules/collection-tab.ts +++ b/packages/compass-collection/src/modules/collection-tab.ts @@ -732,11 +732,6 @@ const validateFakerSchema = async ( fakerArgs, } ); - return { - ...field, - fakerMethod: UNRECOGNIZED_FAKER_METHOD, - fakerArgs: [], - }; } } logger.log.debug( From f8d3723cde01bf93e5f936b248923a593bbc627f Mon Sep 17 00:00:00 2001 From: Nataly Carbonell Date: Tue, 9 Sep 2025 22:55:19 -0400 Subject: [PATCH 04/13] address comments - safely call faker methods --- .../faker-mapping-selector.tsx | 4 +- .../mock-data-generator-modal.spec.tsx | 79 +++++++++++++++++-- .../mock-data-generator-modal.tsx | 54 +++++++------ .../src/modules/collection-tab.ts | 30 ++++--- .../src/components/vertical-rule.tsx | 42 ---------- packages/compass-components/src/index.ts | 1 - packages/compass-web/webpack.config.js | 2 + 7 files changed, 125 insertions(+), 87 deletions(-) delete mode 100644 packages/compass-components/src/components/vertical-rule.tsx diff --git a/packages/compass-collection/src/components/mock-data-generator-modal/faker-mapping-selector.tsx b/packages/compass-collection/src/components/mock-data-generator-modal/faker-mapping-selector.tsx index ff016989ccf..d418eb357f9 100644 --- a/packages/compass-collection/src/components/mock-data-generator-modal/faker-mapping-selector.tsx +++ b/packages/compass-collection/src/components/mock-data-generator-modal/faker-mapping-selector.tsx @@ -58,7 +58,7 @@ const FakerMappingSelector = ({ ))} + {/* TODO(CLOUDP-344400) : Make the select input editable and render other options depending on the JSON type selected */} {[activeJsonType].map((type) => (