From a1fe1d7f47a68f351ccad27202e758a04aef7921 Mon Sep 17 00:00:00 2001 From: Thomas Trompette Date: Wed, 27 Aug 2025 14:36:05 +0200 Subject: [PATCH] Display relation field id reaching max depth (#14101) Search record action do not fetch relations. We could add it to but the logic used, based on gql filtering will be refactored soon. Issue it that the output schema of the step is displaying relation content as if there were available. This PR: - remove the relation content from search record output schema - replace it by the relation field id so the user can still use it - makes UUID selectable in object record picker field Before https://github.com/user-attachments/assets/1bdb4c24-ef32-4a15-9da9-149f270abb01 After https://github.com/user-attachments/assets/d6e97160-c5a3-4989-a603-0c7ce8128b31 --- .../__tests__/filterOutputSchema.test.ts | 4 +++- .../isFieldTypeCompatibleWithRecordId.ts | 8 ++++++- ...searchVariableThroughRecordOutputSchema.ts | 12 +++++++++- .../generate-fake-object-record.spec.ts | 1 + .../utils/generate-fake-object-record.ts | 3 +++ .../utils/generate-object-record-fields.ts | 24 ++++++++++++++----- .../workflow-schema.workspace-service.ts | 5 +++- 7 files changed, 47 insertions(+), 10 deletions(-) diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/__tests__/filterOutputSchema.test.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/__tests__/filterOutputSchema.test.ts index 84bd168551..4b5665f1a2 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/__tests__/filterOutputSchema.test.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/__tests__/filterOutputSchema.test.ts @@ -68,7 +68,7 @@ describe('filterOutputSchema', () => { }); }); - describe('shouldDisplayRecordObjects only (false, true)', () => { + describe('shouldDisplayRecordObjects and related fields only (false, true)', () => { describe('record schema', () => { it('should keep record schema with object and filter compatible fields', () => { const inputSchema = createRecordSchema('person', { @@ -78,9 +78,11 @@ describe('filterOutputSchema', () => { isLeaf: false, value: createRecordSchema('employee'), }, + domain: { isLeaf: true, type: FieldMetadataType.TEXT }, }); const expectedSchema = createRecordSchema('person', { + id: { isLeaf: true, type: FieldMetadataType.UUID }, name: { isLeaf: true, value: 'string' }, employee: { isLeaf: false, diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/isFieldTypeCompatibleWithRecordId.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/isFieldTypeCompatibleWithRecordId.ts index cde265ed15..9324f7aa4c 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/isFieldTypeCompatibleWithRecordId.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/isFieldTypeCompatibleWithRecordId.ts @@ -1,7 +1,13 @@ import { type InputSchemaPropertyType } from '@/workflow/types/InputSchema'; +import { FieldMetadataType } from '~/generated-metadata/graphql'; export const isFieldTypeCompatibleWithRecordId = ( type?: InputSchemaPropertyType, ): boolean => { - return !type || type === 'string' || type === 'unknown'; + return ( + !type || + type === 'string' || + type === 'unknown' || + type === FieldMetadataType.UUID + ); }; diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/searchVariableThroughRecordOutputSchema.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/searchVariableThroughRecordOutputSchema.ts index 0d8bf04cf9..0a0c98f809 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/searchVariableThroughRecordOutputSchema.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/searchVariableThroughRecordOutputSchema.ts @@ -34,6 +34,14 @@ const getCompositeSubFieldName = ( : undefined; }; +const isIdFieldName = (fieldName: string) => { + return ( + fieldName === 'id' || + // For database events, id field will have a prefix such as properties.after.id + fieldName.endsWith('.id') + ); +}; + const navigateToTargetField = ( startingSchema: RecordOutputSchemaV2, pathSegments: string[], @@ -73,7 +81,9 @@ const buildVariableResult = ( const targetField = getFieldFromSchema(targetFieldName, targetSchema); // Determine the variable label based on whether we want the full record or a specific field const variableLabel = - isFullRecord && isRecordOutputSchemaV2(targetSchema) + isFullRecord && + isRecordOutputSchemaV2(targetSchema) && + isIdFieldName(targetFieldName) ? getRecordObjectLabel(targetSchema) : targetField?.label; diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/utils/__tests__/generate-fake-object-record.spec.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/utils/__tests__/generate-fake-object-record.spec.ts index d0b8eaa4a3..be6955fa16 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/utils/__tests__/generate-fake-object-record.spec.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/utils/__tests__/generate-fake-object-record.spec.ts @@ -58,6 +58,7 @@ describe('generateFakeObjectRecord', () => { expect(generateObjectRecordFields).toHaveBeenCalledWith({ objectMetadataInfo, depth: 0, + maxDepth: 1, }); }); }); diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-object-record.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-object-record.ts index 1dcc8118fb..9ae8b62333 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-object-record.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-object-record.ts @@ -5,9 +5,11 @@ import { generateObjectRecordFields } from 'src/modules/workflow/workflow-builde export const generateFakeObjectRecord = ({ objectMetadataInfo, depth = 0, + maxDepth = 1, }: { objectMetadataInfo: ObjectMetadataInfo; depth?: number; + maxDepth?: number; }): RecordOutputSchema => { return { object: { @@ -22,6 +24,7 @@ export const generateFakeObjectRecord = ({ fields: generateObjectRecordFields({ objectMetadataInfo, depth, + maxDepth, }), _outputSchemaType: 'RECORD', }; diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/utils/generate-object-record-fields.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/utils/generate-object-record-fields.ts index 59bf3fa9d6..cfea670362 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/utils/generate-object-record-fields.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/utils/generate-object-record-fields.ts @@ -6,15 +6,16 @@ import { type FieldOutputSchema } from 'src/modules/workflow/workflow-builder/wo import { generateFakeObjectRecord } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-object-record'; import { generateFakeRecordField } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-record-field'; import { shouldGenerateFieldFakeValue } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/should-generate-field-fake-value'; - -const MAXIMUM_DEPTH = 1; +import { camelToTitleCase } from 'src/utils/camel-to-title-case'; export const generateObjectRecordFields = ({ objectMetadataInfo, depth = 0, + maxDepth = 1, }: { objectMetadataInfo: ObjectMetadataInfo; depth?: number; + maxDepth?: number; }): Record => { const objectMetadata = objectMetadataInfo.objectMetadataItemWithFieldsMaps; @@ -35,10 +36,11 @@ export const generateObjectRecordFields = ({ return acc; } - if ( - depth < MAXIMUM_DEPTH && - isDefined(field.relationTargetObjectMetadataId) - ) { + if (!isDefined(field.relationTargetObjectMetadataId)) { + return acc; + } + + if (depth < maxDepth) { const relationTargetObjectMetadata = objectMetadataInfo.objectMetadataMaps.byId[ field.relationTargetObjectMetadataId @@ -62,6 +64,16 @@ export const generateObjectRecordFields = ({ depth: depth + 1, }), }; + } else if (depth === maxDepth) { + const relationIdFieldName = `${field.name}Id`; + const relationIdFieldLabel = camelToTitleCase(relationIdFieldName); + + acc[relationIdFieldName] = generateFakeRecordField({ + type: FieldMetadataType.UUID, + label: relationIdFieldLabel, + icon: field.icon ?? undefined, + fieldMetadataId: field.id, + }); } return acc; diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.workspace-service.ts index 74103d9455..a26f023371 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.workspace-service.ts @@ -117,6 +117,7 @@ export class WorkflowSchemaWorkspaceService { const recordOutputSchema = await this.computeRecordOutputSchema({ objectType, workspaceId, + maxDepth: 0, }); return { @@ -138,9 +139,11 @@ export class WorkflowSchemaWorkspaceService { private async computeRecordOutputSchema({ objectType, workspaceId, + maxDepth = 1, }: { objectType: string; workspaceId: string; + maxDepth?: number; }): Promise { const objectMetadataInfo = await this.workflowCommonWorkspaceService.getObjectMetadataItemWithFieldsMaps( @@ -148,7 +151,7 @@ export class WorkflowSchemaWorkspaceService { workspaceId, ); - return generateFakeObjectRecord({ objectMetadataInfo }); + return generateFakeObjectRecord({ objectMetadataInfo, maxDepth }); } private computeSendEmailActionOutputSchema(): OutputSchema {