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 e2aa91a67a8..a02857ed1bc 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
@@ -25,6 +25,7 @@ import {
import RawSchemaConfirmationScreen from './raw-schema-confirmation-screen';
import FakerSchemaEditorScreen from './faker-schema-editor-screen';
import ScriptScreen from './script-screen';
+import PreviewScreen from './preview-screen';
const footerStyles = css`
flex-direction: row;
@@ -79,9 +80,22 @@ const MockDataGeneratorModal = ({
/>
);
case MockDataGeneratorStep.DOCUMENT_COUNT:
- return <>>; // TODO: CLOUDP-333856
+ return <>>; // TODO(CLOUDP-333856)
case MockDataGeneratorStep.PREVIEW_DATA:
- return <>>; // TODO: CLOUDP-333857
+ // TODO(CLOUDP-333855): Apply results from schema editor confirmation
+ //
+ // function validateFakerSchema(input: FakerSchemaMapping): asserts input is ValidatedFakerSchemaMapping {
+ // ...
+ // }
+ 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..1eb5b2a631f
--- /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 = 5;
+
+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 82dcf6cb1da..705e7f873cb 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';
/**
@@ -1253,4 +1253,167 @@ describe('Script Generation', () => {
}
});
});
+
+ describe('generateDocument', () => {
+ it('should generate document with simple flat fields of mixed types', () => {
+ const schema = {
+ name: {
+ mongoType: 'String' as const,
+ fakerMethod: 'person.fullName',
+ fakerArgs: [],
+ probability: 1.0,
+ },
+ age: {
+ mongoType: 'Number' as const,
+ fakerMethod: 'number.int',
+ fakerArgs: [],
+ probability: 1.0,
+ },
+ isActive: {
+ mongoType: 'Boolean' as const,
+ fakerMethod: 'datatype.boolean',
+ fakerArgs: [],
+ probability: 1.0,
+ },
+ createdAt: {
+ mongoType: 'Date' as const,
+ fakerMethod: 'date.recent',
+ fakerArgs: [],
+ probability: 1.0,
+ },
+ _id: {
+ mongoType: 'ObjectId' as const,
+ fakerMethod: 'database.mongodbObjectId',
+ 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('age');
+ expect(document.age).to.be.a('number');
+ expect(document).to.have.property('isActive');
+ expect(document.isActive).to.be.a('boolean');
+ expect(document).to.have.property('createdAt');
+ expect(document.createdAt).to.be.a('date');
+ expect(document).to.have.property('_id');
+ expect(document._id).to.be.a('string');
+ });
+
+ it('should generate document with multi-dimensional arrays of mixed types', () => {
+ const schema = {
+ 'numberMatrix[][]': {
+ mongoType: 'Number' as const,
+ fakerMethod: 'number.int',
+ fakerArgs: [],
+ probability: 1.0,
+ },
+ 'stringMatrix[][]': {
+ mongoType: 'String' as const,
+ fakerMethod: 'lorem.word',
+ fakerArgs: [],
+ probability: 1.0,
+ },
+ 'booleanGrid[][]': {
+ mongoType: 'Boolean' as const,
+ fakerMethod: 'datatype.boolean',
+ fakerArgs: [],
+ probability: 1.0,
+ },
+ };
+
+ const arrayLengthMap = {
+ numberMatrix: [2, 3],
+ stringMatrix: [2, 2],
+ booleanGrid: [3, 2],
+ };
+
+ const document = generateDocument(schema, arrayLengthMap);
+
+ expect(document).to.be.an('object');
+ expect(document).to.have.property('numberMatrix');
+ expect(document.numberMatrix).to.be.an('array').with.length(2);
+ expect(document.numberMatrix[0]).to.be.an('array').with.length(3);
+ expect(document.numberMatrix[0][0]).to.be.a('number');
+
+ expect(document).to.have.property('stringMatrix');
+ expect(document.stringMatrix).to.be.an('array').with.length(2);
+ expect(document.stringMatrix[0]).to.be.an('array').with.length(2);
+ expect(document.stringMatrix[0][0]).to.be.a('string').and.not.be.empty;
+
+ expect(document).to.have.property('booleanGrid');
+ expect(document.booleanGrid).to.be.an('array').with.length(3);
+ expect(document.booleanGrid[0]).to.be.an('array').with.length(2);
+ expect(document.booleanGrid[0][0]).to.be.a('boolean');
+ });
+
+ it('should handle complex nested structures with arrays and objects', () => {
+ const schema = {
+ 'company.name': {
+ mongoType: 'String' as const,
+ fakerMethod: 'company.name',
+ fakerArgs: [],
+ probability: 1.0,
+ },
+ 'company.employees[].name': {
+ mongoType: 'String' as const,
+ fakerMethod: 'person.fullName',
+ fakerArgs: [],
+ probability: 1.0,
+ },
+ 'company.employees[].email': {
+ mongoType: 'String' as const,
+ fakerMethod: 'internet.email',
+ fakerArgs: [],
+ probability: 1.0,
+ },
+ 'company.employees[].skills[]': {
+ mongoType: 'String' as const,
+ fakerMethod: 'lorem.word',
+ fakerArgs: [],
+ probability: 1.0,
+ },
+ 'company.founded': {
+ mongoType: 'Date' as const,
+ fakerMethod: 'date.past',
+ fakerArgs: [],
+ probability: 1.0,
+ },
+ 'company.isActive': {
+ mongoType: 'Boolean' as const,
+ fakerMethod: 'datatype.boolean',
+ fakerArgs: [],
+ probability: 1.0,
+ },
+ };
+
+ const document = generateDocument(schema);
+
+ expect(document).to.be.an('object');
+ expect(document).to.have.property('company');
+ expect(document.company).to.be.an('object');
+ expect(document.company).to.have.property('name');
+ expect(document.company.name).to.be.a('string').and.not.be.empty;
+ expect(document.company).to.have.property('founded');
+ expect(document.company.founded).to.be.a('date');
+ expect(document.company).to.have.property('isActive');
+ expect(document.company.isActive).to.be.a('boolean');
+ expect(document.company).to.have.property('employees');
+ expect(document.company.employees).to.be.an('array').with.length(3);
+
+ const firstEmployee = document.company.employees[0];
+ expect(firstEmployee).to.be.an('object');
+ expect(firstEmployee).to.have.property('name');
+ expect(firstEmployee.name).to.be.a('string').and.not.be.empty;
+ expect(firstEmployee).to.have.property('email');
+ expect(firstEmployee.email).to.be.a('string').and.include('@');
+ expect(firstEmployee).to.have.property('skills');
+ expect(firstEmployee.skills).to.be.an('array').with.length(3);
+ expect(firstEmployee.skills[0]).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 6d465e6628a..ef4c3a6ac2f 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,11 +1,21 @@
+import type { Document } from 'mongodb';
import type { MongoDBFieldType } from '@mongodb-js/compass-generative-ai';
-import type { FakerFieldMapping } from './types';
+import type { FakerFieldMapping, FakerArg } from './types';
+import type { SampleValue } from '../../schema-analysis-types';
-export type FakerArg = string | number | boolean | { json: string };
+import { faker } from '@faker-js/faker/locale/en';
const DEFAULT_ARRAY_LENGTH = 3;
const INDENT_SIZE = 2;
+/**
+ * Type representing the possible values that can be stored in a generated array. This includes:
+ * - Primitive values
+ * - MongoDB documents (objects)
+ * - Nested arrays (for multi-dimensional arrays)
+ */
+type ArrayElementValue = SampleValue | Document | ArrayElementValue[];
+
// Array length configuration for different array types
export type ArrayLengthMap = {
[fieldName: string]:
@@ -585,3 +595,138 @@ export function formatFakerArgs(fakerArgs: FakerArg[]): string {
return stringifiedArgs.join(', ');
}
+
+/**
+ * Generates documents for the PreviewScreen component.
+ * Now works directly with the optimized object format.
+ */
+export function generateDocument(
+ fakerSchema: Record,
+ arrayLengthMap: ArrayLengthMap = {}
+): Document {
+ const structure = buildDocumentStructure(fakerSchema);
+ return constructDocumentValues(structure, arrayLengthMap);
+}
+
+function generateFakerValue(
+ mapping: FakerFieldMapping
+): string | number | boolean | null | undefined {
+ const method =
+ mapping.fakerMethod === 'unrecognized'
+ ? getDefaultFakerMethod(mapping.mongoType)
+ : mapping.fakerMethod;
+
+ try {
+ // e.g., "person.firstName" -> ["person", "firstName"])
+ const [moduleName, methodName] = method.split('.');
+
+ // This check should not fail if fakerSchema is validated properly
+ if (typeof (faker as any)[moduleName]?.[methodName] !== 'function') {
+ return null;
+ }
+
+ const processedArgs = mapping.fakerArgs.map((arg) => {
+ if (typeof arg === 'object' && arg !== null && 'json' in arg) {
+ return JSON.parse(arg.json);
+ }
+ return arg;
+ });
+
+ // Call the faker method with processed arguments
+ const fakerModule = (faker as any)[moduleName];
+ return fakerModule[methodName](...processedArgs);
+ } catch {
+ return null;
+ }
+}
+
+function constructDocumentValues(
+ structure: DocumentStructure,
+ arrayLengthMap: ArrayLengthMap = {}
+): Document {
+ const result: Document = {};
+
+ for (const [fieldName, value] of Object.entries(structure)) {
+ if ('mongoType' in value) {
+ // It's a field mapping - generate the actual value
+ const mapping = value as FakerFieldMapping;
+ result[fieldName] = generateFakerValue(mapping);
+ } else if ('type' in value && value.type === 'array') {
+ // It's an array - generate array of values
+ const arrayStructure = value as ArrayStructure;
+ result[fieldName] = constructArrayValues(
+ arrayStructure,
+ fieldName,
+ arrayLengthMap,
+ 0 // Start at dimension 0
+ );
+ } else {
+ // It's a nested object - recursively generate
+ const arrayInfo = arrayLengthMap[fieldName];
+ const nestedArrayLengthMap =
+ arrayInfo && !Array.isArray(arrayInfo) && 'elements' in arrayInfo
+ ? arrayInfo.elements
+ : {};
+
+ result[fieldName] = constructDocumentValues(
+ value as DocumentStructure,
+ nestedArrayLengthMap
+ );
+ }
+ }
+
+ return result;
+}
+
+function constructArrayValues(
+ arrayStructure: ArrayStructure,
+ fieldName: string = '',
+ arrayLengthMap: ArrayLengthMap = {},
+ dimensionIndex: number = 0
+): ArrayElementValue[] {
+ const elementType = arrayStructure.elementType;
+
+ // Get array length for this dimension (same logic as renderArrayCode)
+ const arrayInfo = arrayLengthMap[fieldName];
+ let arrayLength = DEFAULT_ARRAY_LENGTH;
+
+ if (Array.isArray(arrayInfo)) {
+ // single or multi-dimensional array: eg. [2, 3, 4] or [6]
+ arrayLength = arrayInfo[dimensionIndex] ?? DEFAULT_ARRAY_LENGTH;
+ } else if (arrayInfo && 'length' in arrayInfo) {
+ // Array of objects/documents
+ arrayLength = arrayInfo.length ?? DEFAULT_ARRAY_LENGTH;
+ }
+
+ const result: ArrayElementValue[] = [];
+ for (let i = 0; i < arrayLength; i++) {
+ if ('mongoType' in elementType) {
+ // Array of primitives
+ result.push(generateFakerValue(elementType as FakerFieldMapping));
+ } else if ('type' in elementType && elementType.type === 'array') {
+ // Nested array (e.g., matrix[][]) - keep same fieldName, increment dimension
+ result.push(
+ constructArrayValues(
+ elementType as ArrayStructure,
+ fieldName,
+ arrayLengthMap,
+ dimensionIndex + 1 // Next dimension
+ )
+ );
+ } else {
+ // Array of objects
+ const nestedArrayLengthMap =
+ arrayInfo && !Array.isArray(arrayInfo) && 'elements' in arrayInfo
+ ? arrayInfo.elements
+ : {};
+ result.push(
+ constructDocumentValues(
+ elementType as DocumentStructure,
+ nestedArrayLengthMap
+ )
+ );
+ }
+ }
+
+ return result;
+}
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 af5150fbd55..8f8add2a3f0 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,6 +1,7 @@
import type { MockDataSchemaResponse } from '@mongodb-js/compass-generative-ai';
import type { MongoDBFieldType } from '@mongodb-js/compass-generative-ai';
-import type { FakerArg } from './script-generation-utils';
+
+export type FakerArg = string | number | boolean | { json: string };
export enum MockDataGeneratorStep {
SCHEMA_CONFIRMATION = 'SCHEMA_CONFIRMATION',
@@ -37,8 +38,10 @@ export type MockDataGeneratorState =
| MockDataGeneratorCompletedState
| MockDataGeneratorErrorState;
+// LLM output format (array with fieldPath as property)
export type LlmFakerMapping = MockDataSchemaResponse['fields'][number];
+// Processed format (object value without fieldPath)
export interface FakerFieldMapping {
mongoType: MongoDBFieldType;
fakerMethod: string;
@@ -46,4 +49,14 @@ export interface FakerFieldMapping {
probability?: number; // 0.0 - 1.0 frequency of field (defaults to 1.0)
}
+// Optimized object format (fieldPath as key, FakerFieldMapping as value)
export type FakerSchema = Record;
+
+/**
+ * The faker schema is validated if it has been (1) confirmed by the user and
+ * (2) TODO(CLOUDP-333855): pre-processed to prevent harmful calls like those that
+ * block the main thread or cause out of memory errors
+ */
+export type ValidatedFakerSchema = FakerSchema & {
+ readonly __brand: unique symbol;
+};