Generate fake form from metadata (#10641)
- add name to form field metadata - extract field generation from object record schema - use field generation to generate field from metadata - add tests
This commit is contained in:
@ -0,0 +1,217 @@
|
||||
import { FieldMetadataType } from 'twenty-shared';
|
||||
|
||||
import { generateFakeValue } from 'src/engine/utils/generate-fake-value';
|
||||
import { generateFakeField } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-field';
|
||||
import { camelToTitleCase } from 'src/utils/camel-to-title-case';
|
||||
|
||||
jest.mock('src/engine/utils/generate-fake-value');
|
||||
jest.mock('src/utils/camel-to-title-case');
|
||||
jest.mock('src/engine/metadata-modules/field-metadata/composite-types', () => {
|
||||
const mockCompositeTypeDefinitions = new Map();
|
||||
|
||||
mockCompositeTypeDefinitions.set(FieldMetadataType.LINKS, {
|
||||
properties: [
|
||||
{ name: 'label', type: FieldMetadataType.TEXT },
|
||||
{ name: 'url', type: FieldMetadataType.TEXT },
|
||||
],
|
||||
});
|
||||
|
||||
mockCompositeTypeDefinitions.set(FieldMetadataType.CURRENCY, {
|
||||
properties: [
|
||||
{ name: 'amount', type: FieldMetadataType.NUMBER },
|
||||
{ name: 'currencyCode', type: FieldMetadataType.TEXT },
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
compositeTypeDefinitions: mockCompositeTypeDefinitions,
|
||||
};
|
||||
});
|
||||
|
||||
describe('generateFakeField', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
// Default mock implementations
|
||||
(generateFakeValue as jest.Mock).mockImplementation(
|
||||
(type) => `fake-${type}`,
|
||||
);
|
||||
(camelToTitleCase as jest.Mock).mockImplementation((str) => `Title ${str}`);
|
||||
});
|
||||
|
||||
describe('for simple field types', () => {
|
||||
it('should generate a leaf node for TEXT type', () => {
|
||||
(generateFakeValue as jest.Mock).mockReturnValueOnce('Fake Text');
|
||||
|
||||
const result = generateFakeField({
|
||||
type: FieldMetadataType.TEXT,
|
||||
label: 'Text Field',
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
isLeaf: true,
|
||||
type: FieldMetadataType.TEXT,
|
||||
icon: undefined,
|
||||
label: 'Text Field',
|
||||
value: 'Fake Text',
|
||||
});
|
||||
|
||||
expect(generateFakeValue).toHaveBeenCalledWith(
|
||||
FieldMetadataType.TEXT,
|
||||
'FieldMetadataType',
|
||||
);
|
||||
});
|
||||
|
||||
it('should generate a leaf node for NUMBER type with icon', () => {
|
||||
(generateFakeValue as jest.Mock).mockReturnValueOnce(42);
|
||||
|
||||
const result = generateFakeField({
|
||||
type: FieldMetadataType.NUMBER,
|
||||
label: 'Number Field',
|
||||
icon: 'IconNumber',
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
isLeaf: true,
|
||||
type: FieldMetadataType.NUMBER,
|
||||
icon: 'IconNumber',
|
||||
label: 'Number Field',
|
||||
value: 42,
|
||||
});
|
||||
});
|
||||
|
||||
it('should generate a leaf node for DATE type', () => {
|
||||
const fakeDate = new Date('2023-01-01');
|
||||
|
||||
(generateFakeValue as jest.Mock).mockReturnValueOnce(fakeDate);
|
||||
|
||||
const result = generateFakeField({
|
||||
type: FieldMetadataType.DATE,
|
||||
label: 'Date Field',
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
isLeaf: true,
|
||||
type: FieldMetadataType.DATE,
|
||||
icon: undefined,
|
||||
label: 'Date Field',
|
||||
value: fakeDate,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('for composite field types', () => {
|
||||
it('should generate a node with properties for LINKS type', () => {
|
||||
(generateFakeValue as jest.Mock)
|
||||
.mockReturnValueOnce('Fake Label')
|
||||
.mockReturnValueOnce('https://example.com');
|
||||
|
||||
(camelToTitleCase as jest.Mock)
|
||||
.mockReturnValueOnce('Label')
|
||||
.mockReturnValueOnce('Url');
|
||||
|
||||
const result = generateFakeField({
|
||||
type: FieldMetadataType.LINKS,
|
||||
label: 'Links Field',
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
isLeaf: false,
|
||||
icon: undefined,
|
||||
label: 'Links Field',
|
||||
value: {
|
||||
label: {
|
||||
isLeaf: true,
|
||||
type: FieldMetadataType.TEXT,
|
||||
label: 'Label',
|
||||
value: 'Fake Label',
|
||||
},
|
||||
url: {
|
||||
isLeaf: true,
|
||||
type: FieldMetadataType.TEXT,
|
||||
label: 'Url',
|
||||
value: 'https://example.com',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(generateFakeValue).toHaveBeenCalledTimes(2);
|
||||
expect(camelToTitleCase).toHaveBeenCalledWith('label');
|
||||
expect(camelToTitleCase).toHaveBeenCalledWith('url');
|
||||
});
|
||||
|
||||
it('should generate a node with properties for CURRENCY type', () => {
|
||||
(generateFakeValue as jest.Mock)
|
||||
.mockReturnValueOnce(100)
|
||||
.mockReturnValueOnce('USD');
|
||||
|
||||
(camelToTitleCase as jest.Mock)
|
||||
.mockReturnValueOnce('Amount')
|
||||
.mockReturnValueOnce('Currency Code');
|
||||
|
||||
const result = generateFakeField({
|
||||
type: FieldMetadataType.CURRENCY,
|
||||
label: 'Currency Field',
|
||||
icon: 'IconCurrency',
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
isLeaf: false,
|
||||
icon: 'IconCurrency',
|
||||
label: 'Currency Field',
|
||||
value: {
|
||||
amount: {
|
||||
isLeaf: true,
|
||||
type: FieldMetadataType.NUMBER,
|
||||
label: 'Amount',
|
||||
value: 100,
|
||||
},
|
||||
currencyCode: {
|
||||
isLeaf: true,
|
||||
type: FieldMetadataType.TEXT,
|
||||
label: 'Currency Code',
|
||||
value: 'USD',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('edge cases', () => {
|
||||
it('should handle unknown field types as leaf nodes', () => {
|
||||
const unknownType = 'UNKNOWN_TYPE' as FieldMetadataType;
|
||||
|
||||
(generateFakeValue as jest.Mock).mockReturnValueOnce('Unknown Value');
|
||||
|
||||
const result = generateFakeField({
|
||||
type: unknownType,
|
||||
label: 'Unknown Field',
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
isLeaf: true,
|
||||
type: unknownType,
|
||||
icon: undefined,
|
||||
label: 'Unknown Field',
|
||||
value: 'Unknown Value',
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle empty label', () => {
|
||||
(generateFakeValue as jest.Mock).mockReturnValueOnce('Fake Boolean');
|
||||
|
||||
const result = generateFakeField({
|
||||
type: FieldMetadataType.BOOLEAN,
|
||||
label: '',
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
isLeaf: true,
|
||||
type: FieldMetadataType.BOOLEAN,
|
||||
icon: undefined,
|
||||
label: '',
|
||||
value: 'Fake Boolean',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,63 @@
|
||||
import { FieldMetadataType } from 'twenty-shared';
|
||||
|
||||
import { generateFakeFormResponse } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-form-response';
|
||||
|
||||
describe('generateFakeFormResponse', () => {
|
||||
it('should generate fake responses for a form schema', () => {
|
||||
const schema = [
|
||||
{
|
||||
name: 'name',
|
||||
type: FieldMetadataType.TEXT,
|
||||
label: 'Name',
|
||||
},
|
||||
{
|
||||
name: 'age',
|
||||
type: FieldMetadataType.NUMBER,
|
||||
label: 'Age',
|
||||
},
|
||||
{
|
||||
name: 'email',
|
||||
type: FieldMetadataType.EMAILS,
|
||||
label: 'Email',
|
||||
},
|
||||
];
|
||||
|
||||
const result = generateFakeFormResponse(schema);
|
||||
|
||||
expect(result).toEqual({
|
||||
email: {
|
||||
isLeaf: false,
|
||||
label: 'Email',
|
||||
value: {
|
||||
additionalEmails: {
|
||||
isLeaf: true,
|
||||
label: ' Additional Emails',
|
||||
type: FieldMetadataType.RAW_JSON,
|
||||
value: null,
|
||||
},
|
||||
primaryEmail: {
|
||||
isLeaf: true,
|
||||
label: ' Primary Email',
|
||||
type: FieldMetadataType.TEXT,
|
||||
value: 'My text',
|
||||
},
|
||||
},
|
||||
icon: undefined,
|
||||
},
|
||||
name: {
|
||||
isLeaf: true,
|
||||
label: 'Name',
|
||||
type: FieldMetadataType.TEXT,
|
||||
value: 'My text',
|
||||
icon: undefined,
|
||||
},
|
||||
age: {
|
||||
isLeaf: true,
|
||||
label: 'Age',
|
||||
type: FieldMetadataType.NUMBER,
|
||||
value: 20,
|
||||
icon: undefined,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,47 @@
|
||||
import { FieldMetadataType } from 'twenty-shared';
|
||||
|
||||
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
||||
import { generateFakeValue } from 'src/engine/utils/generate-fake-value';
|
||||
import {
|
||||
Leaf,
|
||||
Node,
|
||||
} from 'src/modules/workflow/workflow-builder/workflow-schema/types/output-schema.type';
|
||||
import { camelToTitleCase } from 'src/utils/camel-to-title-case';
|
||||
|
||||
export const generateFakeField = ({
|
||||
type,
|
||||
label,
|
||||
icon,
|
||||
}: {
|
||||
type: FieldMetadataType;
|
||||
label: string;
|
||||
icon?: string;
|
||||
}): Leaf | Node => {
|
||||
const compositeType = compositeTypeDefinitions.get(type);
|
||||
|
||||
if (!compositeType) {
|
||||
return {
|
||||
isLeaf: true,
|
||||
type: type,
|
||||
icon: icon,
|
||||
label: label,
|
||||
value: generateFakeValue(type, 'FieldMetadataType'),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
isLeaf: false,
|
||||
icon: icon,
|
||||
label: label,
|
||||
value: compositeType.properties.reduce((acc, property) => {
|
||||
acc[property.name] = {
|
||||
isLeaf: true,
|
||||
type: property.type,
|
||||
label: camelToTitleCase(property.name),
|
||||
value: generateFakeValue(property.type, 'FieldMetadataType'),
|
||||
};
|
||||
|
||||
return acc;
|
||||
}, {}),
|
||||
};
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,19 @@
|
||||
import {
|
||||
Leaf,
|
||||
Node,
|
||||
} from 'src/modules/workflow/workflow-builder/workflow-schema/types/output-schema.type';
|
||||
import { generateFakeField } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-field';
|
||||
import { FormFieldMetadata } from 'src/modules/workflow/workflow-executor/workflow-actions/form/types/workflow-form-action-settings.type';
|
||||
|
||||
export const generateFakeFormResponse = (
|
||||
formMetadata: FormFieldMetadata[],
|
||||
): Record<string, Leaf | Node> => {
|
||||
return formMetadata.reduce((acc, formFieldMetadata) => {
|
||||
acc[formFieldMetadata.name] = generateFakeField({
|
||||
type: formFieldMetadata.type,
|
||||
label: formFieldMetadata.label,
|
||||
});
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
};
|
||||
@ -1,13 +1,11 @@
|
||||
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { generateFakeValue } from 'src/engine/utils/generate-fake-value';
|
||||
import {
|
||||
Leaf,
|
||||
Node,
|
||||
RecordOutputSchema,
|
||||
} from 'src/modules/workflow/workflow-builder/workflow-schema/types/output-schema.type';
|
||||
import { generateFakeField } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-field';
|
||||
import { shouldGenerateFieldFakeValue } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/should-generate-field-fake-value';
|
||||
import { camelToTitleCase } from 'src/utils/camel-to-title-case';
|
||||
|
||||
const generateObjectRecordFields = (
|
||||
objectMetadataEntity: ObjectMetadataEntity,
|
||||
@ -17,33 +15,12 @@ const generateObjectRecordFields = (
|
||||
if (!shouldGenerateFieldFakeValue(field)) {
|
||||
return acc;
|
||||
}
|
||||
const compositeType = compositeTypeDefinitions.get(field.type);
|
||||
|
||||
if (!compositeType) {
|
||||
acc[field.name] = {
|
||||
isLeaf: true,
|
||||
type: field.type,
|
||||
icon: field.icon,
|
||||
label: field.label,
|
||||
value: generateFakeValue(field.type, 'FieldMetadataType'),
|
||||
};
|
||||
} else {
|
||||
acc[field.name] = {
|
||||
isLeaf: false,
|
||||
icon: field.icon,
|
||||
label: field.label,
|
||||
value: compositeType.properties.reduce((acc, property) => {
|
||||
acc[property.name] = {
|
||||
isLeaf: true,
|
||||
type: property.type,
|
||||
label: camelToTitleCase(property.name),
|
||||
value: generateFakeValue(property.type, 'FieldMetadataType'),
|
||||
};
|
||||
|
||||
return acc;
|
||||
}, {}),
|
||||
};
|
||||
}
|
||||
acc[field.name] = generateFakeField({
|
||||
type: field.type,
|
||||
label: field.label,
|
||||
icon: field.icon,
|
||||
});
|
||||
|
||||
return acc;
|
||||
},
|
||||
|
||||
@ -9,8 +9,10 @@ import { checkStringIsDatabaseEventAction } from 'src/engine/api/graphql/graphql
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { generateFakeValue } from 'src/engine/utils/generate-fake-value';
|
||||
import { OutputSchema } from 'src/modules/workflow/workflow-builder/workflow-schema/types/output-schema.type';
|
||||
import { generateFakeFormResponse } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-form-response';
|
||||
import { generateFakeObjectRecord } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-object-record';
|
||||
import { generateFakeObjectRecordEvent } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-object-record-event';
|
||||
import { FormFieldMetadata } from 'src/modules/workflow/workflow-executor/workflow-actions/form/types/workflow-form-action-settings.type';
|
||||
import {
|
||||
WorkflowAction,
|
||||
WorkflowActionType,
|
||||
@ -77,6 +79,10 @@ export class WorkflowSchemaWorkspaceService {
|
||||
workspaceId,
|
||||
objectMetadataRepository: this.objectMetadataRepository,
|
||||
});
|
||||
case WorkflowActionType.FORM:
|
||||
return this.computeFormActionOutputSchema({
|
||||
formMetadata: step.settings.input,
|
||||
});
|
||||
case WorkflowActionType.CODE: // StepOutput schema is computed on serverlessFunction draft execution
|
||||
default:
|
||||
return {};
|
||||
@ -174,4 +180,12 @@ export class WorkflowSchemaWorkspaceService {
|
||||
private computeSendEmailActionOutputSchema(): OutputSchema {
|
||||
return { success: { isLeaf: true, type: 'boolean', value: true } };
|
||||
}
|
||||
|
||||
private computeFormActionOutputSchema({
|
||||
formMetadata,
|
||||
}: {
|
||||
formMetadata: FormFieldMetadata[];
|
||||
}): OutputSchema {
|
||||
return generateFakeFormResponse(formMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
import { FieldMetadataType } from 'twenty-shared';
|
||||
|
||||
import { BaseWorkflowActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-settings.type';
|
||||
|
||||
export type FormFieldMetadata = {
|
||||
label: string;
|
||||
type: string;
|
||||
name: string;
|
||||
type: FieldMetadataType;
|
||||
placeholder?: string;
|
||||
settings?: Record<string, any>;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user