Add record picker in form action (#11331)
Record picker becomes a form field that could be used in another context than workflows. Settings <img width="488" alt="Capture d’écran 2025-04-02 à 10 55 53" src="https://github.com/user-attachments/assets/a9fc09ff-28cd-4ede-8aaa-af1e986cda8e" /> Execution <img width="936" alt="Capture d’écran 2025-04-02 à 10 57 36" src="https://github.com/user-attachments/assets/d796aeeb-cae1-4e59-b388-5b8d08739ea8" />
This commit is contained in:
@ -1,10 +1,24 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { mockObjectMetadataItemsWithFieldMaps } from 'src/engine/core-modules/search/__mocks__/mockObjectMetadataItemsWithFieldMaps';
|
||||
import { generateFakeFormResponse } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-form-response';
|
||||
import { FormFieldMetadata } from 'src/modules/workflow/workflow-executor/workflow-actions/form/types/workflow-form-action-settings.type';
|
||||
|
||||
const companyMockObjectMetadataItem = mockObjectMetadataItemsWithFieldMaps.find(
|
||||
(item) => item.nameSingular === 'company',
|
||||
)!;
|
||||
|
||||
describe('generateFakeFormResponse', () => {
|
||||
it('should generate fake responses for a form schema', () => {
|
||||
const schema = [
|
||||
let objectMetadataRepository;
|
||||
|
||||
beforeEach(() => {
|
||||
objectMetadataRepository = {
|
||||
findOneOrFail: jest.fn().mockResolvedValue(companyMockObjectMetadataItem),
|
||||
};
|
||||
});
|
||||
|
||||
it('should generate fake responses for a form schema', async () => {
|
||||
const schema: FormFieldMetadata[] = [
|
||||
{
|
||||
id: '96939213-49ac-4dee-949d-56e6c7be98e6',
|
||||
name: 'name',
|
||||
@ -19,34 +33,22 @@ describe('generateFakeFormResponse', () => {
|
||||
},
|
||||
{
|
||||
id: '96939213-49ac-4dee-949d-56e6c7be98e8',
|
||||
name: 'email',
|
||||
type: FieldMetadataType.EMAILS,
|
||||
label: 'Email',
|
||||
name: 'company',
|
||||
type: 'RECORD',
|
||||
label: 'Company',
|
||||
settings: {
|
||||
objectName: 'company',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const result = generateFakeFormResponse(schema);
|
||||
const result = await generateFakeFormResponse({
|
||||
formMetadata: schema,
|
||||
workspaceId: '1',
|
||||
objectMetadataRepository,
|
||||
});
|
||||
|
||||
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',
|
||||
@ -61,6 +63,22 @@ describe('generateFakeFormResponse', () => {
|
||||
value: 20,
|
||||
icon: undefined,
|
||||
},
|
||||
company: {
|
||||
isLeaf: false,
|
||||
label: 'Company',
|
||||
value: {
|
||||
_outputSchemaType: 'RECORD',
|
||||
fields: {},
|
||||
object: {
|
||||
isLeaf: true,
|
||||
label: 'Company',
|
||||
fieldIdName: 'id',
|
||||
icon: undefined,
|
||||
nameSingular: 'company',
|
||||
value: 'A company',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,19 +1,58 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
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 { generateFakeObjectRecord } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-object-record';
|
||||
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,
|
||||
});
|
||||
export const generateFakeFormResponse = async ({
|
||||
formMetadata,
|
||||
workspaceId,
|
||||
objectMetadataRepository,
|
||||
}: {
|
||||
formMetadata: FormFieldMetadata[];
|
||||
workspaceId: string;
|
||||
objectMetadataRepository: Repository<ObjectMetadataEntity>;
|
||||
}): Promise<Record<string, Leaf | Node>> => {
|
||||
const result = await Promise.all(
|
||||
formMetadata.map(async (formFieldMetadata) => {
|
||||
if (formFieldMetadata.type === 'RECORD') {
|
||||
if (!formFieldMetadata?.settings?.objectName) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return acc;
|
||||
const objectMetadata = await objectMetadataRepository.findOneOrFail({
|
||||
where: {
|
||||
nameSingular: formFieldMetadata?.settings?.objectName,
|
||||
workspaceId,
|
||||
},
|
||||
relations: ['fields'],
|
||||
});
|
||||
|
||||
return {
|
||||
[formFieldMetadata.name]: {
|
||||
isLeaf: false,
|
||||
label: formFieldMetadata.label,
|
||||
value: generateFakeObjectRecord(objectMetadata),
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
[formFieldMetadata.name]: generateFakeField({
|
||||
type: formFieldMetadata.type,
|
||||
label: formFieldMetadata.label,
|
||||
}),
|
||||
};
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
return result.filter(isDefined).reduce((acc, curr) => {
|
||||
return { ...acc, ...curr };
|
||||
}, {});
|
||||
};
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||
import { checkStringIsDatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/utils/check-string-is-database-event-action';
|
||||
@ -83,6 +83,8 @@ export class WorkflowSchemaWorkspaceService {
|
||||
case WorkflowActionType.FORM:
|
||||
return this.computeFormActionOutputSchema({
|
||||
formMetadata: step.settings.input,
|
||||
workspaceId,
|
||||
objectMetadataRepository: this.objectMetadataRepository,
|
||||
});
|
||||
case WorkflowActionType.CODE: // StepOutput schema is computed on serverlessFunction draft execution
|
||||
default:
|
||||
@ -182,11 +184,19 @@ export class WorkflowSchemaWorkspaceService {
|
||||
return { success: { isLeaf: true, type: 'boolean', value: true } };
|
||||
}
|
||||
|
||||
private computeFormActionOutputSchema({
|
||||
private async computeFormActionOutputSchema({
|
||||
formMetadata,
|
||||
workspaceId,
|
||||
objectMetadataRepository,
|
||||
}: {
|
||||
formMetadata: FormFieldMetadata[];
|
||||
}): OutputSchema {
|
||||
return generateFakeFormResponse(formMetadata);
|
||||
workspaceId: string;
|
||||
objectMetadataRepository: Repository<ObjectMetadataEntity>;
|
||||
}): Promise<OutputSchema> {
|
||||
return generateFakeFormResponse({
|
||||
formMetadata,
|
||||
workspaceId,
|
||||
objectMetadataRepository,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user