From 7db2f1c0c02d0a94e5772cd19118e6087ffeaa4c Mon Sep 17 00:00:00 2001 From: Jacob Samuel Lu Date: Wed, 8 Oct 2025 15:58:16 -0400 Subject: [PATCH 01/12] WIP --- .../mock-data-generator-modal.tsx | 11 +- .../preview-screen.tsx | 42 ++++ .../script-generation-utils.spec.ts | 49 ++++- .../script-generation-utils.ts | 186 ++++++++++++++++++ 4 files changed, 286 insertions(+), 2 deletions(-) create mode 100644 packages/compass-collection/src/components/mock-data-generator-modal/preview-screen.tsx 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 f20bb70d2f5..4e7a7b2e991 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 @@ -31,6 +31,7 @@ import RawSchemaConfirmationScreen from './raw-schema-confirmation-screen'; import FakerSchemaEditorScreen from './faker-schema-editor-screen'; import ScriptScreen from './script-screen'; import DocumentCountScreen from './document-count-screen'; +import PreviewScreen from './preview-screen'; const footerStyles = css` flex-direction: row; @@ -95,7 +96,15 @@ const MockDataGeneratorModal = ({ /> ); case MockDataGeneratorStep.PREVIEW_DATA: - return <>; // TODO: CLOUDP-333857 + return ( + + ); case MockDataGeneratorStep.GENERATE_DATA: return ; } diff --git a/packages/compass-collection/src/components/mock-data-generator-modal/preview-screen.tsx b/packages/compass-collection/src/components/mock-data-generator-modal/preview-screen.tsx new file mode 100644 index 00000000000..4146909752f --- /dev/null +++ b/packages/compass-collection/src/components/mock-data-generator-modal/preview-screen.tsx @@ -0,0 +1,42 @@ +import React, { useMemo } from 'react'; +import { css, spacing, Body, Code } from '@mongodb-js/compass-components'; +import type { FakerSchema } from './types'; +import { generateDocument } from './script-generation-utils'; + +const descriptionStyles = css({ + marginBottom: spacing[200], +}); + +interface PreviewScreenProps { + confirmedFakerSchema: FakerSchema; +} + +const NUM_SAMPLE_DOCUMENTS = 3; + +function PreviewScreen({ confirmedFakerSchema }: PreviewScreenProps) { + const sampleDocuments = useMemo(() => { + const documents = []; + for (let i = 0; i < NUM_SAMPLE_DOCUMENTS; i++) { + documents.push(generateDocument(confirmedFakerSchema)); + } + + return documents; + }, [confirmedFakerSchema]); + + return ( +
+ + Preview Mock Data + + + Below is a sample of documents that will be generated based on your + script + + + {JSON.stringify(sampleDocuments, null, 2)} + +
+ ); +} + +export default PreviewScreen; diff --git a/packages/compass-collection/src/components/mock-data-generator-modal/script-generation-utils.spec.ts b/packages/compass-collection/src/components/mock-data-generator-modal/script-generation-utils.spec.ts index a18cefb4427..97adf4cef8b 100644 --- a/packages/compass-collection/src/components/mock-data-generator-modal/script-generation-utils.spec.ts +++ b/packages/compass-collection/src/components/mock-data-generator-modal/script-generation-utils.spec.ts @@ -1,6 +1,6 @@ import { expect } from 'chai'; import { faker } from '@faker-js/faker/locale/en'; -import { generateScript } from './script-generation-utils'; +import { generateScript, generateDocument } from './script-generation-utils'; import type { FakerFieldMapping } from './types'; /** @@ -1421,4 +1421,51 @@ describe('Script Generation', () => { } }); }); + + describe('generateDocument', () => { + it('should generate document with simple flat fields', () => { + const schema = { + name: { + mongoType: 'String' as const, + fakerMethod: 'person.fullName', + fakerArgs: [], + probability: 1.0, + }, + age: { + mongoType: 'Number' as const, + fakerMethod: 'number.int', + fakerArgs: [{ json: '{"min": 18, "max": 65}' }], + probability: 1.0, + }, + }; + + const document = generateDocument(schema); + + expect(document).to.be.an('object'); + expect(document).to.have.property('name'); + expect(document.name).to.be.a('string').and.not.be.empty; + expect(document).to.have.property('age'); + expect(document.age).to.be.a('number'); + expect(document.age).to.be.at.least(18).and.at.most(65); + }); + + it('should generate document with arrays', () => { + const schema = { + 'tags[]': { + mongoType: 'String' as const, + fakerMethod: 'lorem.word', + fakerArgs: [], + probability: 1.0, + }, + }; + + const document = generateDocument(schema, { 'tags[]': 2 }); + + expect(document).to.be.an('object'); + expect(document).to.have.property('tags'); + expect(document.tags).to.be.an('array').with.length(2); + expect(document.tags[0]).to.be.a('string').and.not.be.empty; + expect(document.tags[1]).to.be.a('string').and.not.be.empty; + }); + }); }); diff --git a/packages/compass-collection/src/components/mock-data-generator-modal/script-generation-utils.ts b/packages/compass-collection/src/components/mock-data-generator-modal/script-generation-utils.ts index 3138663fd55..6acba75ca8a 100644 --- a/packages/compass-collection/src/components/mock-data-generator-modal/script-generation-utils.ts +++ b/packages/compass-collection/src/components/mock-data-generator-modal/script-generation-utils.ts @@ -1,6 +1,7 @@ import type { MongoDBFieldType } from '@mongodb-js/compass-generative-ai'; import type { FakerFieldMapping } from './types'; import { prettify } from '@mongodb-js/compass-editor'; +import { faker } from '@faker-js/faker/locale/en'; export type FakerArg = | string @@ -568,3 +569,188 @@ export function formatFakerArgs(fakerArgs: FakerArg[]): string { return stringifiedArgs.join(', '); } + +type Document = Record; + +/** + * Generates documents for the PreviewScreen component. + * Executes faker methods to create actual document objects. + */ +export function generateDocument( + fakerSchema: Record, + arrayLengthMap: ArrayLengthMap = {} +): Document { + const structure = buildDocumentStructure(fakerSchema); + return constructDocumentValues(structure, arrayLengthMap); +} + +/** + * Construct actual document values from document structure. + * Mirrors renderDocumentCode but executes faker calls instead of generating code. + */ +function constructDocumentValues( + structure: DocumentStructure, + arrayLengthMap: ArrayLengthMap = {}, + currentPath: string = '' +): Document { + const result: Document = {}; + + for (const [fieldName, value] of Object.entries(structure)) { + try { + if ('mongoType' in value) { + // It's a field mapping - execute faker call + const mapping = value as FakerFieldMapping; + const fakerValue = generateFakerValue(mapping); + if (fakerValue !== undefined) { + result[fieldName] = fakerValue; + } + } else if ('elementType' in value) { + // It's an array structure + const arrayStructure = value as ArrayStructure; + const fieldPath = currentPath + ? `${currentPath}.${fieldName}[]` + : `${fieldName}[]`; + const arrayValue = constructArrayValues( + arrayStructure, + arrayLengthMap, + fieldPath + ); + result[fieldName] = arrayValue; + } else { + // It's a nested object structure + const nestedPath = currentPath + ? `${currentPath}.${fieldName}` + : fieldName; + const nestedValue = constructDocumentValues( + value, + arrayLengthMap, + nestedPath + ); + result[fieldName] = nestedValue; + } + } catch { + continue; + } + } + + return result; +} + +/** + * Construct array values from array structure. + * Mirrors renderArrayCode but executes faker calls instead of generating code. + */ +function constructArrayValues( + arrayStructure: ArrayStructure, + arrayLengthMap: ArrayLengthMap, + fieldPath: string +): unknown[] { + const { elementType } = arrayStructure; + + let arrayLength = DEFAULT_ARRAY_LENGTH; + if (fieldPath && arrayLengthMap[fieldPath] !== undefined) { + arrayLength = arrayLengthMap[fieldPath]; + } + + const result: unknown[] = []; + + for (let i = 0; i < arrayLength; i++) { + try { + if ('mongoType' in elementType) { + // Array of field mappings + const mapping = elementType as FakerFieldMapping; + const value = generateFakerValue(mapping); + if (value !== undefined) { + result.push(value); + } + } else if ('elementType' in elementType) { + // Nested array + const nestedArrayValue = constructArrayValues( + elementType as ArrayStructure, + arrayLengthMap, + `${fieldPath}[]` + ); + result.push(nestedArrayValue); + } else { + // Array of objects + const objectValue = constructDocumentValues( + elementType, + arrayLengthMap, + `${fieldPath}[]` + ); + result.push(objectValue); + } + } catch { + continue; + } + } + + return result; +} + +/** + * Prepare faker arguments for execution. + * Converts FakerArg[] to actual values that can be passed to faker methods. + */ +function prepareFakerArgs(fakerArgs: FakerArg[]): unknown[] { + const preparedArgs: unknown[] = []; + + for (const arg of fakerArgs) { + if ( + typeof arg === 'string' || + typeof arg === 'number' || + typeof arg === 'boolean' + ) { + preparedArgs.push(arg); + } else if (typeof arg === 'object' && arg !== null && 'json' in arg) { + // Parse JSON objects + try { + const jsonArg = arg as { json: string }; + preparedArgs.push(JSON.parse(jsonArg.json)); + } catch { + // Skip invalid JSON + continue; + } + } + } + + return preparedArgs; +} + +/** + * Execute faker method to generate actual values. + * Mirrors generateFakerCall but executes the call instead of generating code. + */ +function generateFakerValue( + mapping: FakerFieldMapping +): string | number | boolean | Date | null | undefined { + const method = + mapping.fakerMethod === 'unrecognized' + ? getDefaultFakerMethod(mapping.mongoType) + : mapping.fakerMethod; + + try { + // Navigate to the faker method + const methodParts = method.split('.'); + let fakerMethod: unknown = faker; + for (const part of methodParts) { + fakerMethod = (fakerMethod as Record)[part]; + if (!fakerMethod) { + throw new Error(`Faker method not found: ${method}`); + } + } + + // Prepare arguments + const args = prepareFakerArgs(mapping.fakerArgs); + + // Call the faker method + const result = (fakerMethod as (...args: unknown[]) => unknown).apply( + faker, + args + ); + + return result as string | number | boolean | Date | null | undefined; + } catch { + return undefined; + } +} From 1be3845893b9ee941eba23bd66c36cc4e8ecd1b7 Mon Sep 17 00:00:00 2001 From: Jacob Samuel Lu Date: Wed, 8 Oct 2025 16:16:33 -0400 Subject: [PATCH 02/12] WIP --- .../script-generation-utils.spec.ts | 104 +++++++++++++++++- .../script-generation-utils.ts | 69 ++++++++---- 2 files changed, 150 insertions(+), 23 deletions(-) diff --git a/packages/compass-collection/src/components/mock-data-generator-modal/script-generation-utils.spec.ts b/packages/compass-collection/src/components/mock-data-generator-modal/script-generation-utils.spec.ts index 97adf4cef8b..884805b0043 100644 --- a/packages/compass-collection/src/components/mock-data-generator-modal/script-generation-utils.spec.ts +++ b/packages/compass-collection/src/components/mock-data-generator-modal/script-generation-utils.spec.ts @@ -1464,8 +1464,108 @@ describe('Script Generation', () => { expect(document).to.be.an('object'); expect(document).to.have.property('tags'); expect(document.tags).to.be.an('array').with.length(2); - expect(document.tags[0]).to.be.a('string').and.not.be.empty; - expect(document.tags[1]).to.be.a('string').and.not.be.empty; + (document.tags as string[]).forEach((tag: string) => { + expect(tag).to.be.a('string').and.not.be.empty; + }); + }); + + it('should generate document with complex nested arrays and custom lengths', () => { + const schema = { + 'users[].posts[].tags[]': { + mongoType: 'String' as const, + fakerMethod: 'lorem.word', + fakerArgs: [], + probability: 1.0, + }, + 'matrix[][]': { + mongoType: 'Number' as const, + fakerMethod: 'number.int', + fakerArgs: [{ json: '{"min": 1, "max": 10}' }], + probability: 1.0, + }, + }; + + const arrayLengthMap = { + 'users[]': 2, + 'users[].posts[]': 3, + 'users[].posts[].tags[]': 4, + 'matrix[]': 2, + 'matrix[][]': 3, + }; + + const document = generateDocument(schema, arrayLengthMap); + + expect(document).to.be.an('object'); + + // Check users array structure + expect(document).to.have.property('users'); + expect(document.users).to.be.an('array').with.length(2); + + // Check nested structure with proper types + const users = document.users as Array<{ + posts: Array<{ tags: string[] }>; + }>; + + users.forEach((user) => { + expect(user).to.be.an('object'); + expect(user).to.have.property('posts'); + expect(user.posts).to.be.an('array').with.length(3); + + user.posts.forEach((post) => { + expect(post).to.be.an('object'); + expect(post).to.have.property('tags'); + expect(post.tags).to.be.an('array').with.length(4); + + post.tags.forEach((tag) => { + expect(tag).to.be.a('string').and.not.be.empty; + }); + }); + }); + + // Check matrix (2D array) + expect(document).to.have.property('matrix'); + expect(document.matrix).to.be.an('array').with.length(2); + + const matrix = document.matrix as number[][]; + matrix.forEach((row) => { + expect(row).to.be.an('array').with.length(3); + row.forEach((cell) => { + expect(cell).to.be.a('number').and.be.at.least(1).and.at.most(10); + }); + }); + }); + + it('should handle probability fields correctly', () => { + const schema = { + name: { + mongoType: 'String' as const, + fakerMethod: 'person.fullName', + fakerArgs: [], + probability: 1.0, + }, + optionalField: { + mongoType: 'String' as const, + fakerMethod: 'lorem.word', + fakerArgs: [], + probability: 0.0, // Should never appear + }, + alwaysPresent: { + mongoType: 'Number' as const, + fakerMethod: 'number.int', + fakerArgs: [], + probability: 1.0, + }, + }; + + const document = generateDocument(schema); + + expect(document).to.be.an('object'); + expect(document).to.have.property('name'); + expect(document.name).to.be.a('string').and.not.be.empty; + expect(document).to.have.property('alwaysPresent'); + expect(document.alwaysPresent).to.be.a('number'); + // optionalField should not be present due to 0.0 probability + expect(document).to.not.have.property('optionalField'); }); }); }); diff --git a/packages/compass-collection/src/components/mock-data-generator-modal/script-generation-utils.ts b/packages/compass-collection/src/components/mock-data-generator-modal/script-generation-utils.ts index 6acba75ca8a..872411a1813 100644 --- a/packages/compass-collection/src/components/mock-data-generator-modal/script-generation-utils.ts +++ b/packages/compass-collection/src/components/mock-data-generator-modal/script-generation-utils.ts @@ -598,31 +598,54 @@ function constructDocumentValues( for (const [fieldName, value] of Object.entries(structure)) { try { if ('mongoType' in value) { - // It's a field mapping - execute faker call + // It's a field mapping const mapping = value as FakerFieldMapping; - const fakerValue = generateFakerValue(mapping); - if (fakerValue !== undefined) { - result[fieldName] = fakerValue; + + // Default to 1.0 for invalid probability values + let probability = 1.0; + if ( + mapping.probability !== undefined && + typeof mapping.probability === 'number' && + mapping.probability >= 0 && + mapping.probability <= 1 + ) { + probability = mapping.probability; } - } else if ('elementType' in value) { - // It's an array structure - const arrayStructure = value as ArrayStructure; + + if (probability < 1.0) { + // Use Math.random for conditional field inclusion + if (Math.random() < probability) { + const fakerValue = generateFakerValue(mapping); + if (fakerValue !== undefined) { + result[fieldName] = fakerValue; + } + } + } else { + // Normal field inclusion + const fakerValue = generateFakerValue(mapping); + if (fakerValue !== undefined) { + result[fieldName] = fakerValue; + } + } + } else if ('type' in value && value.type === 'array') { + // It's an array const fieldPath = currentPath ? `${currentPath}.${fieldName}[]` : `${fieldName}[]`; const arrayValue = constructArrayValues( - arrayStructure, + value as ArrayStructure, + fieldName, arrayLengthMap, fieldPath ); result[fieldName] = arrayValue; } else { - // It's a nested object structure + // It's a nested object: recursive call const nestedPath = currentPath ? `${currentPath}.${fieldName}` : fieldName; const nestedValue = constructDocumentValues( - value, + value as DocumentStructure, arrayLengthMap, nestedPath ); @@ -642,14 +665,16 @@ function constructDocumentValues( */ function constructArrayValues( arrayStructure: ArrayStructure, - arrayLengthMap: ArrayLengthMap, - fieldPath: string + fieldName: string = '', + arrayLengthMap: ArrayLengthMap = {}, + currentFieldPath: string = '' ): unknown[] { - const { elementType } = arrayStructure; + const elementType = arrayStructure.elementType; + // Get array length for this dimension let arrayLength = DEFAULT_ARRAY_LENGTH; - if (fieldPath && arrayLengthMap[fieldPath] !== undefined) { - arrayLength = arrayLengthMap[fieldPath]; + if (currentFieldPath && arrayLengthMap[currentFieldPath] !== undefined) { + arrayLength = arrayLengthMap[currentFieldPath]; } const result: unknown[] = []; @@ -657,26 +682,28 @@ function constructArrayValues( for (let i = 0; i < arrayLength; i++) { try { if ('mongoType' in elementType) { - // Array of field mappings + // Array of primitives const mapping = elementType as FakerFieldMapping; const value = generateFakerValue(mapping); if (value !== undefined) { result.push(value); } - } else if ('elementType' in elementType) { - // Nested array + } else if ('type' in elementType && elementType.type === 'array') { + // Nested array (e.g., matrix[][]) - append another [] to the path + const fieldPath = currentFieldPath + '[]'; const nestedArrayValue = constructArrayValues( elementType as ArrayStructure, + fieldName, arrayLengthMap, - `${fieldPath}[]` + fieldPath ); result.push(nestedArrayValue); } else { // Array of objects const objectValue = constructDocumentValues( - elementType, + elementType as DocumentStructure, arrayLengthMap, - `${fieldPath}[]` + currentFieldPath ); result.push(objectValue); } From 553d8dca77a9a4b6525ee86725e7d83cfaa7f4e3 Mon Sep 17 00:00:00 2001 From: Jacob Samuel Lu Date: Wed, 8 Oct 2025 16:46:24 -0400 Subject: [PATCH 03/12] fix(compass-collection): Fix field name mismatch in Mock Data Generator API - Change 'sample_values' to 'sampleValues' in FieldInfo interface to match backend API expectations - Update all test files to use the new field name - Resolves 400 Bad Request error when generating faker mappings The backend API expects 'sampleValues' (camelCase) but frontend was sending 'sample_values' (snake_case), causing the LLM request to fail with 'Invalid request' error. Fixes CLOUDP-350280 --- .../collection-header.spec.tsx | 10 ++-- .../mock-data-generator-modal.spec.tsx | 4 +- .../to-simplified-field-info.spec.ts | 60 +++++++++---------- .../src/schema-analysis-types.ts | 6 +- .../transform-schema-to-field-info.spec.ts | 58 +++++++++--------- .../src/transform-schema-to-field-info.ts | 2 +- 6 files changed, 70 insertions(+), 70 deletions(-) diff --git a/packages/compass-collection/src/components/collection-header/collection-header.spec.tsx b/packages/compass-collection/src/components/collection-header/collection-header.spec.tsx index 82553cc22af..42ff1aa3e87 100644 --- a/packages/compass-collection/src/components/collection-header/collection-header.spec.tsx +++ b/packages/compass-collection/src/components/collection-header/collection-header.spec.tsx @@ -429,7 +429,7 @@ describe('CollectionHeader [Component]', function () { schemaAnalysis: { status: SCHEMA_ANALYSIS_STATE_COMPLETE, processedSchema: { - field1: { type: 'String', sample_values: ['value1'] }, + field1: { type: 'String', sampleValues: ['value1'] }, }, schemaMetadata: { maxNestingDepth: 2, // Below the limit of 4 @@ -485,7 +485,7 @@ describe('CollectionHeader [Component]', function () { schemaAnalysis: { status: SCHEMA_ANALYSIS_STATE_COMPLETE, processedSchema: { - field1: { type: 'String', sample_values: ['value1'] }, + field1: { type: 'String', sampleValues: ['value1'] }, }, schemaMetadata: { maxNestingDepth: 4, // Exceeds the limit @@ -514,7 +514,7 @@ describe('CollectionHeader [Component]', function () { schemaAnalysis: { status: SCHEMA_ANALYSIS_STATE_COMPLETE, processedSchema: { - field1: { type: 'String', sample_values: ['value1'] }, + field1: { type: 'String', sampleValues: ['value1'] }, }, schemaMetadata: { maxNestingDepth: 2, @@ -541,7 +541,7 @@ describe('CollectionHeader [Component]', function () { schemaAnalysis: { status: SCHEMA_ANALYSIS_STATE_COMPLETE, processedSchema: { - field1: { type: 'String', sample_values: ['value1'] }, + field1: { type: 'String', sampleValues: ['value1'] }, }, schemaMetadata: { maxNestingDepth: 2, @@ -576,7 +576,7 @@ describe('CollectionHeader [Component]', function () { schemaAnalysis: { status: SCHEMA_ANALYSIS_STATE_COMPLETE, processedSchema: { - field1: { type: 'String', sample_values: ['value1'] }, + field1: { type: 'String', sampleValues: ['value1'] }, }, schemaMetadata: { maxNestingDepth: 2, diff --git a/packages/compass-collection/src/components/mock-data-generator-modal/mock-data-generator-modal.spec.tsx b/packages/compass-collection/src/components/mock-data-generator-modal/mock-data-generator-modal.spec.tsx index 43be38d08b0..8a786a36197 100644 --- a/packages/compass-collection/src/components/mock-data-generator-modal/mock-data-generator-modal.spec.tsx +++ b/packages/compass-collection/src/components/mock-data-generator-modal/mock-data-generator-modal.spec.tsx @@ -27,7 +27,7 @@ const defaultSchemaAnalysisState: SchemaAnalysisState = { name: { type: 'String', probability: 1.0, - sample_values: ['John', 'Jane'], + sampleValues: ['John', 'Jane'], }, }, arrayLengthMap: {}, @@ -510,7 +510,7 @@ describe('MockDataGeneratorModal', () => { type: { type: 'String', probability: 1.0, - sample_values: ['cat', 'dog'], + sampleValues: ['cat', 'dog'], }, }, sampleDocument: { name: 'Peaches', age: 10, type: 'cat' }, diff --git a/packages/compass-collection/src/components/mock-data-generator-modal/to-simplified-field-info.spec.ts b/packages/compass-collection/src/components/mock-data-generator-modal/to-simplified-field-info.spec.ts index 36040a301ef..758f9c272c7 100644 --- a/packages/compass-collection/src/components/mock-data-generator-modal/to-simplified-field-info.spec.ts +++ b/packages/compass-collection/src/components/mock-data-generator-modal/to-simplified-field-info.spec.ts @@ -7,32 +7,32 @@ describe('toSimplifiedFieldInfo', function () { const input = { 'user.name': { type: 'String' as const, - sample_values: ['John'], + sampleValues: ['John'], probability: 1.0, }, 'user.age': { type: 'Number' as const, - sample_values: [25, 30], + sampleValues: [25, 30], probability: 0.8, }, 'user.profile.bio': { type: 'String' as const, - sample_values: ['Software engineer'], + sampleValues: ['Software engineer'], probability: 0.9, }, 'user.profile.isVerified': { type: 'Boolean' as const, - sample_values: [true, false], + sampleValues: [true, false], probability: 0.7, }, 'metadata.createdAt': { type: 'Date' as const, - sample_values: [new Date('2023-01-01')], + sampleValues: [new Date('2023-01-01')], probability: 1.0, }, 'metadata.objectId': { type: 'ObjectId' as const, - sample_values: ['642d766b7300158b1f22e972'], + sampleValues: ['642d766b7300158b1f22e972'], probability: 1.0, }, }; @@ -61,32 +61,32 @@ describe('toSimplifiedFieldInfo', function () { const input = { 'tags[]': { type: 'String' as const, - sample_values: ['red', 'blue', 'green'], + sampleValues: ['red', 'blue', 'green'], probability: 1.0, }, 'scores[]': { type: 'Number' as const, - sample_values: [85, 92, 78], + sampleValues: [85, 92, 78], probability: 0.9, }, 'matrix[][]': { type: 'Number' as const, - sample_values: [1, 2, 3, 4], + sampleValues: [1, 2, 3, 4], probability: 1.0, }, 'flags[]': { type: 'Boolean' as const, - sample_values: [true, false], + sampleValues: [true, false], probability: 0.8, }, 'timestamps[]': { type: 'Date' as const, - sample_values: [new Date('2023-01-01'), new Date('2023-06-15')], + sampleValues: [new Date('2023-01-01'), new Date('2023-06-15')], probability: 0.7, }, 'ids[]': { type: 'ObjectId' as const, - sample_values: ['642d766b7300158b1f22e972', '642d766b7300158b1f22e973'], + sampleValues: ['642d766b7300158b1f22e972', '642d766b7300158b1f22e973'], probability: 1.0, }, }; @@ -109,32 +109,32 @@ describe('toSimplifiedFieldInfo', function () { const input = { 'items[].id': { type: 'Number' as const, - sample_values: [1, 2], + sampleValues: [1, 2], probability: 1.0, }, 'items[].name': { type: 'String' as const, - sample_values: ['Item A', 'Item B'], + sampleValues: ['Item A', 'Item B'], probability: 1.0, }, 'items[].metadata.createdBy': { type: 'String' as const, - sample_values: ['admin', 'user'], + sampleValues: ['admin', 'user'], probability: 0.9, }, 'items[].metadata.tags[]': { type: 'String' as const, - sample_values: ['urgent', 'review', 'approved'], + sampleValues: ['urgent', 'review', 'approved'], probability: 0.8, }, 'items[].price': { type: 'Decimal128' as const, - sample_values: [19.99, 29.99], + sampleValues: [19.99, 29.99], probability: 0.95, }, 'items[].binary': { type: 'Binary' as const, - sample_values: ['dGVzdA=='], + sampleValues: ['dGVzdA=='], probability: 0.3, }, }; @@ -162,62 +162,62 @@ describe('toSimplifiedFieldInfo', function () { const input = { 'cube[][][]': { type: 'Number' as const, - sample_values: [1, 2, 3, 4, 5, 6, 7, 8], + sampleValues: [1, 2, 3, 4, 5, 6, 7, 8], probability: 1.0, }, 'matrix[][].x': { type: 'Number' as const, - sample_values: [1, 3], + sampleValues: [1, 3], probability: 1.0, }, 'matrix[][].y': { type: 'Number' as const, - sample_values: [2, 4], + sampleValues: [2, 4], probability: 1.0, }, 'teams[].members[]': { type: 'String' as const, - sample_values: ['Alice', 'Bob', 'Charlie'], + sampleValues: ['Alice', 'Bob', 'Charlie'], probability: 1.0, }, 'teams[].name': { type: 'String' as const, - sample_values: ['Team A', 'Team B'], + sampleValues: ['Team A', 'Team B'], probability: 1.0, }, 'complex[][].data[]': { type: 'Long' as const, - sample_values: [123456789, 987654321], + sampleValues: [123456789, 987654321], probability: 0.9, }, 'complex[][].regex': { type: 'RegExp' as const, - sample_values: ['pattern'], + sampleValues: ['pattern'], probability: 0.6, }, 'complex[][].code': { type: 'Code' as const, - sample_values: ['function() {}'], + sampleValues: ['function() {}'], probability: 0.4, }, 'nested[][].symbols[]': { type: 'Symbol' as const, - sample_values: ['symbol1', 'symbol2'], + sampleValues: ['symbol1', 'symbol2'], probability: 0.5, }, 'timestamps[][].created': { type: 'Timestamp' as const, - sample_values: [4294967297], + sampleValues: [4294967297], probability: 0.8, }, 'keys[][].max': { type: 'MaxKey' as const, - sample_values: ['MaxKey'], + sampleValues: ['MaxKey'], probability: 0.2, }, 'keys[][].min': { type: 'MinKey' as const, - sample_values: ['MinKey'], + sampleValues: ['MinKey'], probability: 0.2, }, }; diff --git a/packages/compass-collection/src/schema-analysis-types.ts b/packages/compass-collection/src/schema-analysis-types.ts index f4e90419889..48ee808e17b 100644 --- a/packages/compass-collection/src/schema-analysis-types.ts +++ b/packages/compass-collection/src/schema-analysis-types.ts @@ -31,11 +31,11 @@ export type SchemaAnalysisErrorState = { }; /** - * Primitive values that can appear in sample_values after BSON-to-primitive conversion. + * Primitive values that can appear in sampleValues after BSON-to-primitive conversion. * These are the JavaScript primitive equivalents of BSON values. */ export type SampleValue = - | string // String, Symbol, ObjectId, Binary, RegExp, Code, etc. (converted to string) + | string // String, Symbol, Binary, RegExp, Code, etc. (converted to string) | number // Number, Int32, Long, Double, Decimal128, Timestamp (converted via valueOf()) | boolean | Date @@ -47,7 +47,7 @@ export type SampleValue = */ export interface FieldInfo { type: MongoDBFieldType; // MongoDB primitive type - sample_values?: SampleValue[]; // Primitive sample values (limited to 10) + sampleValues?: SampleValue[]; // Primitive sample values (limited to 10) probability?: number; // 0.0 - 1.0 field frequency } diff --git a/packages/compass-collection/src/transform-schema-to-field-info.spec.ts b/packages/compass-collection/src/transform-schema-to-field-info.spec.ts index a38e4fe32de..e4c52c05b4a 100644 --- a/packages/compass-collection/src/transform-schema-to-field-info.spec.ts +++ b/packages/compass-collection/src/transform-schema-to-field-info.spec.ts @@ -56,7 +56,7 @@ describe('processSchema', function () { expect(result.fieldInfo).to.deep.equal({ mixed: { type: 'String', // Should pick the most probable type - sample_values: ['text'], + sampleValues: ['text'], probability: 1.0, }, }); @@ -107,7 +107,7 @@ describe('processSchema', function () { expect(result.fieldInfo).to.deep.equal({ optional: { type: 'String', - sample_values: ['value'], + sampleValues: ['value'], probability: 0.67, }, }); @@ -177,8 +177,8 @@ describe('processSchema', function () { const result = processSchema(schema); - expect(result.fieldInfo.field.sample_values).to.have.length(10); - expect(result.fieldInfo.field.sample_values).to.deep.equal( + expect(result.fieldInfo.field.sampleValues).to.have.length(10); + expect(result.fieldInfo.field.sampleValues).to.deep.equal( manyValues.slice(0, 10) ); }); @@ -267,22 +267,22 @@ describe('processSchema', function () { expect(result.fieldInfo).to.deep.equal({ name: { type: 'String', - sample_values: ['John', 'Jane', 'Bob'], + sampleValues: ['John', 'Jane', 'Bob'], probability: 1.0, }, age: { type: 'Number', - sample_values: [25, 30, 35], + sampleValues: [25, 30, 35], probability: 0.9, }, isActive: { type: 'Boolean', - sample_values: [true, false, true], + sampleValues: [true, false, true], probability: 0.8, }, createdAt: { type: 'Date', - sample_values: [new Date('2023-01-01'), new Date('2023-06-15')], + sampleValues: [new Date('2023-01-01'), new Date('2023-06-15')], probability: 0.7, }, }); @@ -481,52 +481,52 @@ describe('processSchema', function () { expect(result.fieldInfo).to.deep.equal({ objectId: { type: 'ObjectId', - sample_values: ['642d766b7300158b1f22e972'], + sampleValues: ['642d766b7300158b1f22e972'], probability: 1.0, }, binary: { type: 'Binary', - sample_values: ['dGVzdA=='], + sampleValues: ['dGVzdA=='], probability: 1.0, }, regex: { type: 'RegExp', - sample_values: ['pattern'], + sampleValues: ['pattern'], probability: 1.0, }, code: { type: 'Code', - sample_values: ['function() {}'], + sampleValues: ['function() {}'], probability: 1.0, }, long: { type: 'Long', - sample_values: [123456789], + sampleValues: [123456789], probability: 1.0, }, decimal: { type: 'Decimal128', - sample_values: [123.456], + sampleValues: [123.456], probability: 1.0, }, timestamp: { type: 'Timestamp', - sample_values: [4294967297], + sampleValues: [4294967297], probability: 1.0, }, maxKey: { type: 'MaxKey', - sample_values: ['MaxKey'], + sampleValues: ['MaxKey'], probability: 1.0, }, minKey: { type: 'MinKey', - sample_values: ['MinKey'], + sampleValues: ['MinKey'], probability: 1.0, }, symbol: { type: 'Symbol', - sample_values: ['symbol'], + sampleValues: ['symbol'], probability: 1.0, }, }); @@ -600,12 +600,12 @@ describe('processSchema', function () { expect(result.fieldInfo).to.deep.equal({ 'user.name': { type: 'String', - sample_values: ['John'], + sampleValues: ['John'], probability: 1.0, }, 'user.age': { type: 'Number', - sample_values: [25, 30], + sampleValues: [25, 30], probability: 0.8, }, }); @@ -655,7 +655,7 @@ describe('processSchema', function () { expect(result.fieldInfo).to.deep.equal({ 'tags[]': { type: 'String', - sample_values: ['red', 'blue', 'green'], + sampleValues: ['red', 'blue', 'green'], probability: 1.0, }, }); @@ -732,7 +732,7 @@ describe('processSchema', function () { expect(result.fieldInfo).to.deep.equal({ 'level1.level2.value': { type: 'String', - sample_values: ['deep'], + sampleValues: ['deep'], probability: 1.0, }, }); @@ -818,12 +818,12 @@ describe('processSchema', function () { expect(result.fieldInfo).to.deep.equal({ 'items[].id': { type: 'Number', - sample_values: [1, 2], + sampleValues: [1, 2], probability: 1.0, }, 'items[].cost': { type: 'Number', - sample_values: [10.5, 25.0], + sampleValues: [10.5, 25.0], probability: 1.0, }, }); @@ -908,7 +908,7 @@ describe('processSchema', function () { expect(result.fieldInfo).to.deep.equal({ 'cube[][][]': { type: 'Number', - sample_values: [1, 2, 3, 4, 5, 6, 7, 8], + sampleValues: [1, 2, 3, 4, 5, 6, 7, 8], probability: 1.0, }, }); @@ -1012,12 +1012,12 @@ describe('processSchema', function () { expect(result.fieldInfo).to.deep.equal({ 'matrix[][].x': { type: 'Number', - sample_values: [1, 3], + sampleValues: [1, 3], probability: 1.0, }, 'matrix[][].y': { type: 'Number', - sample_values: [2, 4], + sampleValues: [2, 4], probability: 1.0, }, }); @@ -1120,12 +1120,12 @@ describe('processSchema', function () { expect(result.fieldInfo).to.deep.equal({ 'teams[].name': { type: 'String', - sample_values: ['Team A', 'Team B'], + sampleValues: ['Team A', 'Team B'], probability: 1.0, }, 'teams[].members[]': { type: 'String', - sample_values: ['Alice', 'Bob', 'Charlie'], + sampleValues: ['Alice', 'Bob', 'Charlie'], probability: 1.0, }, }); diff --git a/packages/compass-collection/src/transform-schema-to-field-info.ts b/packages/compass-collection/src/transform-schema-to-field-info.ts index 760f8c4a7e1..4e58f6f21b2 100644 --- a/packages/compass-collection/src/transform-schema-to-field-info.ts +++ b/packages/compass-collection/src/transform-schema-to-field-info.ts @@ -281,7 +281,7 @@ function processType( // Primitive: Create entry const fieldInfo: FieldInfo = { type: type.name, - sample_values: type.values + sampleValues: type.values .slice(0, MAX_SAMPLE_VALUES) .map(convertBSONToPrimitive), probability: fieldProbability, From 3b307d4ba5a1664b35de1809f6d707bc9883b834 Mon Sep 17 00:00:00 2001 From: Jacob Samuel Lu Date: Wed, 8 Oct 2025 16:58:05 -0400 Subject: [PATCH 04/12] Revert comment --- packages/compass-collection/src/schema-analysis-types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/compass-collection/src/schema-analysis-types.ts b/packages/compass-collection/src/schema-analysis-types.ts index 48ee808e17b..c975f136130 100644 --- a/packages/compass-collection/src/schema-analysis-types.ts +++ b/packages/compass-collection/src/schema-analysis-types.ts @@ -35,7 +35,7 @@ export type SchemaAnalysisErrorState = { * These are the JavaScript primitive equivalents of BSON values. */ export type SampleValue = - | string // String, Symbol, Binary, RegExp, Code, etc. (converted to string) + | string // String, Symbol, ObjectId, Binary, RegExp, Code, etc. (converted to string) | number // Number, Int32, Long, Double, Decimal128, Timestamp (converted via valueOf()) | boolean | Date From 618105cd4e41ffe3a49e32a403b2f8eeac285bab Mon Sep 17 00:00:00 2001 From: Jacob Samuel Lu Date: Wed, 8 Oct 2025 16:46:24 -0400 Subject: [PATCH 05/12] fix(compass-collection): Fix field name mismatch in Mock Data Generator API - Change 'sample_values' to 'sampleValues' in FieldInfo interface to match backend API expectations - Update all test files to use the new field name - Resolves 400 Bad Request error when generating faker mappings The backend API expects 'sampleValues' (camelCase) but frontend was sending 'sample_values' (snake_case), causing the LLM request to fail with 'Invalid request' error. Fixes CLOUDP-350280 --- .../collection-header.spec.tsx | 10 ++-- .../mock-data-generator-modal.spec.tsx | 4 +- .../to-simplified-field-info.spec.ts | 60 +++++++++---------- .../src/schema-analysis-types.ts | 6 +- .../transform-schema-to-field-info.spec.ts | 58 +++++++++--------- .../src/transform-schema-to-field-info.ts | 2 +- 6 files changed, 70 insertions(+), 70 deletions(-) diff --git a/packages/compass-collection/src/components/collection-header/collection-header.spec.tsx b/packages/compass-collection/src/components/collection-header/collection-header.spec.tsx index 82553cc22af..42ff1aa3e87 100644 --- a/packages/compass-collection/src/components/collection-header/collection-header.spec.tsx +++ b/packages/compass-collection/src/components/collection-header/collection-header.spec.tsx @@ -429,7 +429,7 @@ describe('CollectionHeader [Component]', function () { schemaAnalysis: { status: SCHEMA_ANALYSIS_STATE_COMPLETE, processedSchema: { - field1: { type: 'String', sample_values: ['value1'] }, + field1: { type: 'String', sampleValues: ['value1'] }, }, schemaMetadata: { maxNestingDepth: 2, // Below the limit of 4 @@ -485,7 +485,7 @@ describe('CollectionHeader [Component]', function () { schemaAnalysis: { status: SCHEMA_ANALYSIS_STATE_COMPLETE, processedSchema: { - field1: { type: 'String', sample_values: ['value1'] }, + field1: { type: 'String', sampleValues: ['value1'] }, }, schemaMetadata: { maxNestingDepth: 4, // Exceeds the limit @@ -514,7 +514,7 @@ describe('CollectionHeader [Component]', function () { schemaAnalysis: { status: SCHEMA_ANALYSIS_STATE_COMPLETE, processedSchema: { - field1: { type: 'String', sample_values: ['value1'] }, + field1: { type: 'String', sampleValues: ['value1'] }, }, schemaMetadata: { maxNestingDepth: 2, @@ -541,7 +541,7 @@ describe('CollectionHeader [Component]', function () { schemaAnalysis: { status: SCHEMA_ANALYSIS_STATE_COMPLETE, processedSchema: { - field1: { type: 'String', sample_values: ['value1'] }, + field1: { type: 'String', sampleValues: ['value1'] }, }, schemaMetadata: { maxNestingDepth: 2, @@ -576,7 +576,7 @@ describe('CollectionHeader [Component]', function () { schemaAnalysis: { status: SCHEMA_ANALYSIS_STATE_COMPLETE, processedSchema: { - field1: { type: 'String', sample_values: ['value1'] }, + field1: { type: 'String', sampleValues: ['value1'] }, }, schemaMetadata: { maxNestingDepth: 2, diff --git a/packages/compass-collection/src/components/mock-data-generator-modal/mock-data-generator-modal.spec.tsx b/packages/compass-collection/src/components/mock-data-generator-modal/mock-data-generator-modal.spec.tsx index 43be38d08b0..8a786a36197 100644 --- a/packages/compass-collection/src/components/mock-data-generator-modal/mock-data-generator-modal.spec.tsx +++ b/packages/compass-collection/src/components/mock-data-generator-modal/mock-data-generator-modal.spec.tsx @@ -27,7 +27,7 @@ const defaultSchemaAnalysisState: SchemaAnalysisState = { name: { type: 'String', probability: 1.0, - sample_values: ['John', 'Jane'], + sampleValues: ['John', 'Jane'], }, }, arrayLengthMap: {}, @@ -510,7 +510,7 @@ describe('MockDataGeneratorModal', () => { type: { type: 'String', probability: 1.0, - sample_values: ['cat', 'dog'], + sampleValues: ['cat', 'dog'], }, }, sampleDocument: { name: 'Peaches', age: 10, type: 'cat' }, diff --git a/packages/compass-collection/src/components/mock-data-generator-modal/to-simplified-field-info.spec.ts b/packages/compass-collection/src/components/mock-data-generator-modal/to-simplified-field-info.spec.ts index 36040a301ef..758f9c272c7 100644 --- a/packages/compass-collection/src/components/mock-data-generator-modal/to-simplified-field-info.spec.ts +++ b/packages/compass-collection/src/components/mock-data-generator-modal/to-simplified-field-info.spec.ts @@ -7,32 +7,32 @@ describe('toSimplifiedFieldInfo', function () { const input = { 'user.name': { type: 'String' as const, - sample_values: ['John'], + sampleValues: ['John'], probability: 1.0, }, 'user.age': { type: 'Number' as const, - sample_values: [25, 30], + sampleValues: [25, 30], probability: 0.8, }, 'user.profile.bio': { type: 'String' as const, - sample_values: ['Software engineer'], + sampleValues: ['Software engineer'], probability: 0.9, }, 'user.profile.isVerified': { type: 'Boolean' as const, - sample_values: [true, false], + sampleValues: [true, false], probability: 0.7, }, 'metadata.createdAt': { type: 'Date' as const, - sample_values: [new Date('2023-01-01')], + sampleValues: [new Date('2023-01-01')], probability: 1.0, }, 'metadata.objectId': { type: 'ObjectId' as const, - sample_values: ['642d766b7300158b1f22e972'], + sampleValues: ['642d766b7300158b1f22e972'], probability: 1.0, }, }; @@ -61,32 +61,32 @@ describe('toSimplifiedFieldInfo', function () { const input = { 'tags[]': { type: 'String' as const, - sample_values: ['red', 'blue', 'green'], + sampleValues: ['red', 'blue', 'green'], probability: 1.0, }, 'scores[]': { type: 'Number' as const, - sample_values: [85, 92, 78], + sampleValues: [85, 92, 78], probability: 0.9, }, 'matrix[][]': { type: 'Number' as const, - sample_values: [1, 2, 3, 4], + sampleValues: [1, 2, 3, 4], probability: 1.0, }, 'flags[]': { type: 'Boolean' as const, - sample_values: [true, false], + sampleValues: [true, false], probability: 0.8, }, 'timestamps[]': { type: 'Date' as const, - sample_values: [new Date('2023-01-01'), new Date('2023-06-15')], + sampleValues: [new Date('2023-01-01'), new Date('2023-06-15')], probability: 0.7, }, 'ids[]': { type: 'ObjectId' as const, - sample_values: ['642d766b7300158b1f22e972', '642d766b7300158b1f22e973'], + sampleValues: ['642d766b7300158b1f22e972', '642d766b7300158b1f22e973'], probability: 1.0, }, }; @@ -109,32 +109,32 @@ describe('toSimplifiedFieldInfo', function () { const input = { 'items[].id': { type: 'Number' as const, - sample_values: [1, 2], + sampleValues: [1, 2], probability: 1.0, }, 'items[].name': { type: 'String' as const, - sample_values: ['Item A', 'Item B'], + sampleValues: ['Item A', 'Item B'], probability: 1.0, }, 'items[].metadata.createdBy': { type: 'String' as const, - sample_values: ['admin', 'user'], + sampleValues: ['admin', 'user'], probability: 0.9, }, 'items[].metadata.tags[]': { type: 'String' as const, - sample_values: ['urgent', 'review', 'approved'], + sampleValues: ['urgent', 'review', 'approved'], probability: 0.8, }, 'items[].price': { type: 'Decimal128' as const, - sample_values: [19.99, 29.99], + sampleValues: [19.99, 29.99], probability: 0.95, }, 'items[].binary': { type: 'Binary' as const, - sample_values: ['dGVzdA=='], + sampleValues: ['dGVzdA=='], probability: 0.3, }, }; @@ -162,62 +162,62 @@ describe('toSimplifiedFieldInfo', function () { const input = { 'cube[][][]': { type: 'Number' as const, - sample_values: [1, 2, 3, 4, 5, 6, 7, 8], + sampleValues: [1, 2, 3, 4, 5, 6, 7, 8], probability: 1.0, }, 'matrix[][].x': { type: 'Number' as const, - sample_values: [1, 3], + sampleValues: [1, 3], probability: 1.0, }, 'matrix[][].y': { type: 'Number' as const, - sample_values: [2, 4], + sampleValues: [2, 4], probability: 1.0, }, 'teams[].members[]': { type: 'String' as const, - sample_values: ['Alice', 'Bob', 'Charlie'], + sampleValues: ['Alice', 'Bob', 'Charlie'], probability: 1.0, }, 'teams[].name': { type: 'String' as const, - sample_values: ['Team A', 'Team B'], + sampleValues: ['Team A', 'Team B'], probability: 1.0, }, 'complex[][].data[]': { type: 'Long' as const, - sample_values: [123456789, 987654321], + sampleValues: [123456789, 987654321], probability: 0.9, }, 'complex[][].regex': { type: 'RegExp' as const, - sample_values: ['pattern'], + sampleValues: ['pattern'], probability: 0.6, }, 'complex[][].code': { type: 'Code' as const, - sample_values: ['function() {}'], + sampleValues: ['function() {}'], probability: 0.4, }, 'nested[][].symbols[]': { type: 'Symbol' as const, - sample_values: ['symbol1', 'symbol2'], + sampleValues: ['symbol1', 'symbol2'], probability: 0.5, }, 'timestamps[][].created': { type: 'Timestamp' as const, - sample_values: [4294967297], + sampleValues: [4294967297], probability: 0.8, }, 'keys[][].max': { type: 'MaxKey' as const, - sample_values: ['MaxKey'], + sampleValues: ['MaxKey'], probability: 0.2, }, 'keys[][].min': { type: 'MinKey' as const, - sample_values: ['MinKey'], + sampleValues: ['MinKey'], probability: 0.2, }, }; diff --git a/packages/compass-collection/src/schema-analysis-types.ts b/packages/compass-collection/src/schema-analysis-types.ts index f4e90419889..48ee808e17b 100644 --- a/packages/compass-collection/src/schema-analysis-types.ts +++ b/packages/compass-collection/src/schema-analysis-types.ts @@ -31,11 +31,11 @@ export type SchemaAnalysisErrorState = { }; /** - * Primitive values that can appear in sample_values after BSON-to-primitive conversion. + * Primitive values that can appear in sampleValues after BSON-to-primitive conversion. * These are the JavaScript primitive equivalents of BSON values. */ export type SampleValue = - | string // String, Symbol, ObjectId, Binary, RegExp, Code, etc. (converted to string) + | string // String, Symbol, Binary, RegExp, Code, etc. (converted to string) | number // Number, Int32, Long, Double, Decimal128, Timestamp (converted via valueOf()) | boolean | Date @@ -47,7 +47,7 @@ export type SampleValue = */ export interface FieldInfo { type: MongoDBFieldType; // MongoDB primitive type - sample_values?: SampleValue[]; // Primitive sample values (limited to 10) + sampleValues?: SampleValue[]; // Primitive sample values (limited to 10) probability?: number; // 0.0 - 1.0 field frequency } diff --git a/packages/compass-collection/src/transform-schema-to-field-info.spec.ts b/packages/compass-collection/src/transform-schema-to-field-info.spec.ts index a38e4fe32de..e4c52c05b4a 100644 --- a/packages/compass-collection/src/transform-schema-to-field-info.spec.ts +++ b/packages/compass-collection/src/transform-schema-to-field-info.spec.ts @@ -56,7 +56,7 @@ describe('processSchema', function () { expect(result.fieldInfo).to.deep.equal({ mixed: { type: 'String', // Should pick the most probable type - sample_values: ['text'], + sampleValues: ['text'], probability: 1.0, }, }); @@ -107,7 +107,7 @@ describe('processSchema', function () { expect(result.fieldInfo).to.deep.equal({ optional: { type: 'String', - sample_values: ['value'], + sampleValues: ['value'], probability: 0.67, }, }); @@ -177,8 +177,8 @@ describe('processSchema', function () { const result = processSchema(schema); - expect(result.fieldInfo.field.sample_values).to.have.length(10); - expect(result.fieldInfo.field.sample_values).to.deep.equal( + expect(result.fieldInfo.field.sampleValues).to.have.length(10); + expect(result.fieldInfo.field.sampleValues).to.deep.equal( manyValues.slice(0, 10) ); }); @@ -267,22 +267,22 @@ describe('processSchema', function () { expect(result.fieldInfo).to.deep.equal({ name: { type: 'String', - sample_values: ['John', 'Jane', 'Bob'], + sampleValues: ['John', 'Jane', 'Bob'], probability: 1.0, }, age: { type: 'Number', - sample_values: [25, 30, 35], + sampleValues: [25, 30, 35], probability: 0.9, }, isActive: { type: 'Boolean', - sample_values: [true, false, true], + sampleValues: [true, false, true], probability: 0.8, }, createdAt: { type: 'Date', - sample_values: [new Date('2023-01-01'), new Date('2023-06-15')], + sampleValues: [new Date('2023-01-01'), new Date('2023-06-15')], probability: 0.7, }, }); @@ -481,52 +481,52 @@ describe('processSchema', function () { expect(result.fieldInfo).to.deep.equal({ objectId: { type: 'ObjectId', - sample_values: ['642d766b7300158b1f22e972'], + sampleValues: ['642d766b7300158b1f22e972'], probability: 1.0, }, binary: { type: 'Binary', - sample_values: ['dGVzdA=='], + sampleValues: ['dGVzdA=='], probability: 1.0, }, regex: { type: 'RegExp', - sample_values: ['pattern'], + sampleValues: ['pattern'], probability: 1.0, }, code: { type: 'Code', - sample_values: ['function() {}'], + sampleValues: ['function() {}'], probability: 1.0, }, long: { type: 'Long', - sample_values: [123456789], + sampleValues: [123456789], probability: 1.0, }, decimal: { type: 'Decimal128', - sample_values: [123.456], + sampleValues: [123.456], probability: 1.0, }, timestamp: { type: 'Timestamp', - sample_values: [4294967297], + sampleValues: [4294967297], probability: 1.0, }, maxKey: { type: 'MaxKey', - sample_values: ['MaxKey'], + sampleValues: ['MaxKey'], probability: 1.0, }, minKey: { type: 'MinKey', - sample_values: ['MinKey'], + sampleValues: ['MinKey'], probability: 1.0, }, symbol: { type: 'Symbol', - sample_values: ['symbol'], + sampleValues: ['symbol'], probability: 1.0, }, }); @@ -600,12 +600,12 @@ describe('processSchema', function () { expect(result.fieldInfo).to.deep.equal({ 'user.name': { type: 'String', - sample_values: ['John'], + sampleValues: ['John'], probability: 1.0, }, 'user.age': { type: 'Number', - sample_values: [25, 30], + sampleValues: [25, 30], probability: 0.8, }, }); @@ -655,7 +655,7 @@ describe('processSchema', function () { expect(result.fieldInfo).to.deep.equal({ 'tags[]': { type: 'String', - sample_values: ['red', 'blue', 'green'], + sampleValues: ['red', 'blue', 'green'], probability: 1.0, }, }); @@ -732,7 +732,7 @@ describe('processSchema', function () { expect(result.fieldInfo).to.deep.equal({ 'level1.level2.value': { type: 'String', - sample_values: ['deep'], + sampleValues: ['deep'], probability: 1.0, }, }); @@ -818,12 +818,12 @@ describe('processSchema', function () { expect(result.fieldInfo).to.deep.equal({ 'items[].id': { type: 'Number', - sample_values: [1, 2], + sampleValues: [1, 2], probability: 1.0, }, 'items[].cost': { type: 'Number', - sample_values: [10.5, 25.0], + sampleValues: [10.5, 25.0], probability: 1.0, }, }); @@ -908,7 +908,7 @@ describe('processSchema', function () { expect(result.fieldInfo).to.deep.equal({ 'cube[][][]': { type: 'Number', - sample_values: [1, 2, 3, 4, 5, 6, 7, 8], + sampleValues: [1, 2, 3, 4, 5, 6, 7, 8], probability: 1.0, }, }); @@ -1012,12 +1012,12 @@ describe('processSchema', function () { expect(result.fieldInfo).to.deep.equal({ 'matrix[][].x': { type: 'Number', - sample_values: [1, 3], + sampleValues: [1, 3], probability: 1.0, }, 'matrix[][].y': { type: 'Number', - sample_values: [2, 4], + sampleValues: [2, 4], probability: 1.0, }, }); @@ -1120,12 +1120,12 @@ describe('processSchema', function () { expect(result.fieldInfo).to.deep.equal({ 'teams[].name': { type: 'String', - sample_values: ['Team A', 'Team B'], + sampleValues: ['Team A', 'Team B'], probability: 1.0, }, 'teams[].members[]': { type: 'String', - sample_values: ['Alice', 'Bob', 'Charlie'], + sampleValues: ['Alice', 'Bob', 'Charlie'], probability: 1.0, }, }); diff --git a/packages/compass-collection/src/transform-schema-to-field-info.ts b/packages/compass-collection/src/transform-schema-to-field-info.ts index 760f8c4a7e1..4e58f6f21b2 100644 --- a/packages/compass-collection/src/transform-schema-to-field-info.ts +++ b/packages/compass-collection/src/transform-schema-to-field-info.ts @@ -281,7 +281,7 @@ function processType( // Primitive: Create entry const fieldInfo: FieldInfo = { type: type.name, - sample_values: type.values + sampleValues: type.values .slice(0, MAX_SAMPLE_VALUES) .map(convertBSONToPrimitive), probability: fieldProbability, From 8eb00a2ef2bf7bd3dde0baa407aa68a617278139 Mon Sep 17 00:00:00 2001 From: Jacob Samuel Lu Date: Wed, 8 Oct 2025 16:58:05 -0400 Subject: [PATCH 06/12] Revert comment --- packages/compass-collection/src/schema-analysis-types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/compass-collection/src/schema-analysis-types.ts b/packages/compass-collection/src/schema-analysis-types.ts index 48ee808e17b..c975f136130 100644 --- a/packages/compass-collection/src/schema-analysis-types.ts +++ b/packages/compass-collection/src/schema-analysis-types.ts @@ -35,7 +35,7 @@ export type SchemaAnalysisErrorState = { * These are the JavaScript primitive equivalents of BSON values. */ export type SampleValue = - | string // String, Symbol, Binary, RegExp, Code, etc. (converted to string) + | string // String, Symbol, ObjectId, Binary, RegExp, Code, etc. (converted to string) | number // Number, Int32, Long, Double, Decimal128, Timestamp (converted via valueOf()) | boolean | Date From 236e40fb82bef04e82e89ffe7e46b4dfe02ec034 Mon Sep 17 00:00:00 2001 From: Jacob Samuel Lu Date: Thu, 9 Oct 2025 12:15:48 -0400 Subject: [PATCH 07/12] For of instead of foreach --- .../script-generation-utils.spec.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/compass-collection/src/components/mock-data-generator-modal/script-generation-utils.spec.ts b/packages/compass-collection/src/components/mock-data-generator-modal/script-generation-utils.spec.ts index 884805b0043..ef62a3cba18 100644 --- a/packages/compass-collection/src/components/mock-data-generator-modal/script-generation-utils.spec.ts +++ b/packages/compass-collection/src/components/mock-data-generator-modal/script-generation-utils.spec.ts @@ -1464,9 +1464,9 @@ describe('Script Generation', () => { expect(document).to.be.an('object'); expect(document).to.have.property('tags'); expect(document.tags).to.be.an('array').with.length(2); - (document.tags as string[]).forEach((tag: string) => { + for (const tag of document.tags as string[]) { expect(tag).to.be.a('string').and.not.be.empty; - }); + } }); it('should generate document with complex nested arrays and custom lengths', () => { @@ -1506,33 +1506,33 @@ describe('Script Generation', () => { posts: Array<{ tags: string[] }>; }>; - users.forEach((user) => { + for (const user of users) { expect(user).to.be.an('object'); expect(user).to.have.property('posts'); expect(user.posts).to.be.an('array').with.length(3); - user.posts.forEach((post) => { + for (const post of user.posts) { expect(post).to.be.an('object'); expect(post).to.have.property('tags'); expect(post.tags).to.be.an('array').with.length(4); - post.tags.forEach((tag) => { + for (const tag of post.tags) { expect(tag).to.be.a('string').and.not.be.empty; - }); - }); - }); + } + } + } // Check matrix (2D array) expect(document).to.have.property('matrix'); expect(document.matrix).to.be.an('array').with.length(2); const matrix = document.matrix as number[][]; - matrix.forEach((row) => { + for (const row of matrix) { expect(row).to.be.an('array').with.length(3); - row.forEach((cell) => { + for (const cell of row) { expect(cell).to.be.a('number').and.be.at.least(1).and.at.most(10); - }); - }); + } + } }); it('should handle probability fields correctly', () => { From b62de6e57079355370e4b3082c2306361dc4f1b7 Mon Sep 17 00:00:00 2001 From: Jacob Samuel Lu Date: Thu, 9 Oct 2025 12:21:16 -0400 Subject: [PATCH 08/12] Remove undefined check --- .../mock-data-generator-modal/script-generation-utils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/compass-collection/src/components/mock-data-generator-modal/script-generation-utils.ts b/packages/compass-collection/src/components/mock-data-generator-modal/script-generation-utils.ts index 872411a1813..7b5db534db3 100644 --- a/packages/compass-collection/src/components/mock-data-generator-modal/script-generation-utils.ts +++ b/packages/compass-collection/src/components/mock-data-generator-modal/script-generation-utils.ts @@ -604,7 +604,6 @@ function constructDocumentValues( // Default to 1.0 for invalid probability values let probability = 1.0; if ( - mapping.probability !== undefined && typeof mapping.probability === 'number' && mapping.probability >= 0 && mapping.probability <= 1 From d662069754443d09b290c67e85015c28f7586af0 Mon Sep 17 00:00:00 2001 From: Jacob Samuel Lu Date: Thu, 9 Oct 2025 12:23:23 -0400 Subject: [PATCH 09/12] Refactor for readability --- .../script-generation-utils.ts | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/packages/compass-collection/src/components/mock-data-generator-modal/script-generation-utils.ts b/packages/compass-collection/src/components/mock-data-generator-modal/script-generation-utils.ts index 7b5db534db3..1fe20efedee 100644 --- a/packages/compass-collection/src/components/mock-data-generator-modal/script-generation-utils.ts +++ b/packages/compass-collection/src/components/mock-data-generator-modal/script-generation-utils.ts @@ -611,20 +611,11 @@ function constructDocumentValues( probability = mapping.probability; } - if (probability < 1.0) { - // Use Math.random for conditional field inclusion - if (Math.random() < probability) { - const fakerValue = generateFakerValue(mapping); - if (fakerValue !== undefined) { - result[fieldName] = fakerValue; - } - } - } else { - // Normal field inclusion - const fakerValue = generateFakerValue(mapping); - if (fakerValue !== undefined) { - result[fieldName] = fakerValue; - } + const shouldIncludeField = + probability >= 1.0 || Math.random() < probability; + const fakerValue = generateFakerValue(mapping); + if (fakerValue !== undefined && shouldIncludeField) { + result[fieldName] = fakerValue; } } else if ('type' in value && value.type === 'array') { // It's an array From 69a4693bdb1bdc66efcee6925a8d51ca1b5d2761 Mon Sep 17 00:00:00 2001 From: Jacob Samuel Lu Date: Thu, 9 Oct 2025 12:42:56 -0400 Subject: [PATCH 10/12] EJSON --- .../components/mock-data-generator-modal/preview-screen.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/compass-collection/src/components/mock-data-generator-modal/preview-screen.tsx b/packages/compass-collection/src/components/mock-data-generator-modal/preview-screen.tsx index 4146909752f..b911eac2f84 100644 --- a/packages/compass-collection/src/components/mock-data-generator-modal/preview-screen.tsx +++ b/packages/compass-collection/src/components/mock-data-generator-modal/preview-screen.tsx @@ -1,5 +1,6 @@ import React, { useMemo } from 'react'; import { css, spacing, Body, Code } from '@mongodb-js/compass-components'; +import { EJSON } from 'bson'; import type { FakerSchema } from './types'; import { generateDocument } from './script-generation-utils'; @@ -33,7 +34,7 @@ function PreviewScreen({ confirmedFakerSchema }: PreviewScreenProps) { script - {JSON.stringify(sampleDocuments, null, 2)} + {EJSON.stringify(sampleDocuments, undefined, 2)} ); From f16ff28bd0c359b5071eec27e60a9ce38cac47cd Mon Sep 17 00:00:00 2001 From: Jacob Samuel Lu Date: Tue, 14 Oct 2025 12:22:27 -0400 Subject: [PATCH 11/12] Refactor --- .../script-generation-utils.ts | 143 +++++++----------- 1 file changed, 54 insertions(+), 89 deletions(-) diff --git a/packages/compass-collection/src/components/mock-data-generator-modal/script-generation-utils.ts b/packages/compass-collection/src/components/mock-data-generator-modal/script-generation-utils.ts index 1fe20efedee..7b1f5d74e35 100644 --- a/packages/compass-collection/src/components/mock-data-generator-modal/script-generation-utils.ts +++ b/packages/compass-collection/src/components/mock-data-generator-modal/script-generation-utils.ts @@ -584,6 +584,49 @@ export function generateDocument( return constructDocumentValues(structure, arrayLengthMap); } +function computeValue( + elementType: ArrayStructure | FakerFieldMapping | DocumentStructure, + arrayLengthMap: ArrayLengthMap, + currentPath: string +) { + try { + if ('mongoType' in elementType) { + // It's a field mapping + const mapping = elementType as FakerFieldMapping; + + // Default to 1.0 for invalid probability values + let probability = 1.0; + if ( + typeof mapping.probability === 'number' && + mapping.probability >= 0 && + mapping.probability <= 1 + ) { + probability = mapping.probability; + } + + const shouldIncludeField = + probability >= 1.0 || Math.random() < probability; + if (shouldIncludeField) { + return generateFakerValue(mapping); + } + } else if ('type' in elementType && elementType.type === 'array') { + return constructArrayValues( + elementType as ArrayStructure, + arrayLengthMap, + `${currentPath}[]` + ); + } else { + return constructDocumentValues( + elementType as DocumentStructure, + arrayLengthMap, + currentPath + ); + } + } catch { + // Skip invalid faker methods + } +} + /** * Construct actual document values from document structure. * Mirrors renderDocumentCode but executes faker calls instead of generating code. @@ -592,60 +635,15 @@ function constructDocumentValues( structure: DocumentStructure, arrayLengthMap: ArrayLengthMap = {}, currentPath: string = '' -): Document { +) { const result: Document = {}; - for (const [fieldName, value] of Object.entries(structure)) { - try { - if ('mongoType' in value) { - // It's a field mapping - const mapping = value as FakerFieldMapping; - - // Default to 1.0 for invalid probability values - let probability = 1.0; - if ( - typeof mapping.probability === 'number' && - mapping.probability >= 0 && - mapping.probability <= 1 - ) { - probability = mapping.probability; - } - - const shouldIncludeField = - probability >= 1.0 || Math.random() < probability; - const fakerValue = generateFakerValue(mapping); - if (fakerValue !== undefined && shouldIncludeField) { - result[fieldName] = fakerValue; - } - } else if ('type' in value && value.type === 'array') { - // It's an array - const fieldPath = currentPath - ? `${currentPath}.${fieldName}[]` - : `${fieldName}[]`; - const arrayValue = constructArrayValues( - value as ArrayStructure, - fieldName, - arrayLengthMap, - fieldPath - ); - result[fieldName] = arrayValue; - } else { - // It's a nested object: recursive call - const nestedPath = currentPath - ? `${currentPath}.${fieldName}` - : fieldName; - const nestedValue = constructDocumentValues( - value as DocumentStructure, - arrayLengthMap, - nestedPath - ); - result[fieldName] = nestedValue; - } - } catch { - continue; + const newPath = currentPath ? `${currentPath}.${fieldName}` : fieldName; + const val = computeValue(value, arrayLengthMap, newPath); + if (val !== undefined) { + result[fieldName] = val; } } - return result; } @@ -655,53 +653,20 @@ function constructDocumentValues( */ function constructArrayValues( arrayStructure: ArrayStructure, - fieldName: string = '', - arrayLengthMap: ArrayLengthMap = {}, - currentFieldPath: string = '' -): unknown[] { + arrayLengthMap: ArrayLengthMap, + currentPath: string +) { const elementType = arrayStructure.elementType; // Get array length for this dimension let arrayLength = DEFAULT_ARRAY_LENGTH; - if (currentFieldPath && arrayLengthMap[currentFieldPath] !== undefined) { - arrayLength = arrayLengthMap[currentFieldPath]; + if (arrayLengthMap[currentPath] !== undefined) { + arrayLength = arrayLengthMap[currentPath]; } - const result: unknown[] = []; - for (let i = 0; i < arrayLength; i++) { - try { - if ('mongoType' in elementType) { - // Array of primitives - const mapping = elementType as FakerFieldMapping; - const value = generateFakerValue(mapping); - if (value !== undefined) { - result.push(value); - } - } else if ('type' in elementType && elementType.type === 'array') { - // Nested array (e.g., matrix[][]) - append another [] to the path - const fieldPath = currentFieldPath + '[]'; - const nestedArrayValue = constructArrayValues( - elementType as ArrayStructure, - fieldName, - arrayLengthMap, - fieldPath - ); - result.push(nestedArrayValue); - } else { - // Array of objects - const objectValue = constructDocumentValues( - elementType as DocumentStructure, - arrayLengthMap, - currentFieldPath - ); - result.push(objectValue); - } - } catch { - continue; - } + result.push(computeValue(elementType, arrayLengthMap, currentPath)); } - return result; } From e2eaafcd682fe35d4d5978b31f4f453f73825697 Mon Sep 17 00:00:00 2001 From: Jacob Samuel Lu Date: Tue, 14 Oct 2025 12:36:04 -0400 Subject: [PATCH 12/12] Preview Screen DocumentList instead of EJSON --- .../preview-screen.tsx | 36 +++++++++++++++---- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/packages/compass-collection/src/components/mock-data-generator-modal/preview-screen.tsx b/packages/compass-collection/src/components/mock-data-generator-modal/preview-screen.tsx index b911eac2f84..33df025ed52 100644 --- a/packages/compass-collection/src/components/mock-data-generator-modal/preview-screen.tsx +++ b/packages/compass-collection/src/components/mock-data-generator-modal/preview-screen.tsx @@ -1,6 +1,11 @@ import React, { useMemo } from 'react'; -import { css, spacing, Body, Code } from '@mongodb-js/compass-components'; -import { EJSON } from 'bson'; +import { + css, + spacing, + Body, + DocumentList, +} from '@mongodb-js/compass-components'; +import HadronDocument from 'hadron-document'; import type { FakerSchema } from './types'; import { generateDocument } from './script-generation-utils'; @@ -8,6 +13,18 @@ const descriptionStyles = css({ marginBottom: spacing[200], }); +const documentContainerStyles = css({ + display: 'flex', + flexDirection: 'column', + gap: spacing[300], +}); + +const documentWrapperStyles = css({ + border: '1px solid #E8EDEB', + borderRadius: '6px', + padding: spacing[200], +}); + interface PreviewScreenProps { confirmedFakerSchema: FakerSchema; } @@ -18,7 +35,10 @@ function PreviewScreen({ confirmedFakerSchema }: PreviewScreenProps) { const sampleDocuments = useMemo(() => { const documents = []; for (let i = 0; i < NUM_SAMPLE_DOCUMENTS; i++) { - documents.push(generateDocument(confirmedFakerSchema)); + const plainDoc = generateDocument(confirmedFakerSchema); + const hadronDoc = new HadronDocument(plainDoc); + hadronDoc.expand(); // Expand by default for better preview + documents.push(hadronDoc); } return documents; @@ -33,9 +53,13 @@ function PreviewScreen({ confirmedFakerSchema }: PreviewScreenProps) { Below is a sample of documents that will be generated based on your script - - {EJSON.stringify(sampleDocuments, undefined, 2)} - +
+ {sampleDocuments.map((doc, index) => ( +
+ +
+ ))} +
); }