Filter valid fields in record steps (#17145)

Fixes https://github.com/twentyhq/twenty/issues/16775

When a field is deleted from the model, the workflow action step still
stores the deleted field name in `step.settings.input.objectRecord`.

We need to filter out the fields that are not valid anymore. This logic
existed before but had been removed with the migration to tool services.
This commit is contained in:
Thomas Trompette
2026-01-14 14:43:35 +00:00
committed by GitHub
parent 93aef30046
commit 79e0207efa
4 changed files with 89 additions and 5 deletions
@@ -0,0 +1,30 @@
import { isDefined } from 'twenty-shared/utils';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { buildFieldMapsFromFlatObjectMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/build-field-maps-from-flat-object-metadata.util';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
export const filterValidFieldsInRecord = (
record: Record<string, unknown>,
flatObjectMetadata: FlatObjectMetadata,
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>,
): Record<string, unknown> => {
const { fieldIdByName, fieldIdByJoinColumnName } =
buildFieldMapsFromFlatObjectMetadata(
flatFieldMetadataMaps,
flatObjectMetadata,
);
const filteredRecord: Record<string, unknown> = {};
for (const [key, value] of Object.entries(record)) {
const fieldMetadataId = fieldIdByName[key] || fieldIdByJoinColumnName[key];
if (isDefined(fieldMetadataId)) {
filteredRecord[key] = value;
}
}
return filteredRecord;
};
@@ -14,6 +14,7 @@ import { WorkflowExecutionContextService } from 'src/modules/workflow/workflow-e
import { type WorkflowActionInput } from 'src/modules/workflow/workflow-executor/types/workflow-action-input';
import { type WorkflowActionOutput } from 'src/modules/workflow/workflow-executor/types/workflow-action-output.type';
import { buildWorkflowActorMetadata } from 'src/modules/workflow/workflow-executor/utils/build-workflow-actor-metadata.util';
import { filterValidFieldsInRecord } from 'src/modules/workflow/workflow-executor/utils/filter-valid-fields-in-record.util';
import { findStepOrThrow } from 'src/modules/workflow/workflow-executor/utils/find-step-or-throw.util';
import { resolveRichTextFieldsInRecord } from 'src/modules/workflow/workflow-executor/utils/resolve-rich-text-fields-in-record.util';
import { type WorkflowCreateRecordActionInput } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/types/workflow-record-crud-action-input.type';
@@ -61,6 +62,12 @@ export class CreateRecordWorkflowAction implements WorkflowAction {
context,
) as WorkflowCreateRecordActionInput;
const filteredObjectRecord = filterValidFieldsInRecord(
workflowActionInput.objectRecord,
objectMetadataInfo.flatObjectMetadata,
objectMetadataInfo.flatFieldMetadataMaps,
);
const executionContext =
await this.workflowExecutionContextService.getExecutionContext(runInfo);
@@ -68,7 +75,7 @@ export class CreateRecordWorkflowAction implements WorkflowAction {
const toolOutput = await this.createRecordService.execute({
objectName: workflowActionInput.objectName,
objectRecord: workflowActionInput.objectRecord,
objectRecord: filteredObjectRecord,
authContext: executionContext.authContext,
createdBy,
rolePermissionConfig: executionContext.rolePermissionConfig,
@@ -18,6 +18,7 @@ import { WorkflowExecutionContextService } from 'src/modules/workflow/workflow-e
import { type WorkflowActionInput } from 'src/modules/workflow/workflow-executor/types/workflow-action-input';
import { type WorkflowActionOutput } from 'src/modules/workflow/workflow-executor/types/workflow-action-output.type';
import { buildWorkflowActorMetadata } from 'src/modules/workflow/workflow-executor/utils/build-workflow-actor-metadata.util';
import { filterValidFieldsInRecord } from 'src/modules/workflow/workflow-executor/utils/filter-valid-fields-in-record.util';
import { findStepOrThrow } from 'src/modules/workflow/workflow-executor/utils/find-step-or-throw.util';
import { resolveRichTextFieldsInRecord } from 'src/modules/workflow/workflow-executor/utils/resolve-rich-text-fields-in-record.util';
import { isWorkflowUpdateRecordAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/guards/is-workflow-update-record-action.guard';
@@ -84,6 +85,23 @@ export class UpdateRecordWorkflowAction implements WorkflowAction {
);
}
const filteredObjectRecord = filterValidFieldsInRecord(
workflowActionInput.objectRecord,
objectMetadataInfo.flatObjectMetadata,
objectMetadataInfo.flatFieldMetadataMaps,
);
const filteredFieldsToUpdate = workflowActionInput.fieldsToUpdate?.filter(
(fieldName) => fieldName in filteredObjectRecord,
);
if (filteredFieldsToUpdate?.length === 0) {
throw new RecordCrudException(
'Failed to update: No fields to update',
RecordCrudExceptionCode.INVALID_REQUEST,
);
}
const executionContext =
await this.workflowExecutionContextService.getExecutionContext(runInfo);
@@ -92,8 +110,8 @@ export class UpdateRecordWorkflowAction implements WorkflowAction {
const toolOutput = await this.updateRecordService.execute({
objectName: workflowActionInput.objectName,
objectRecordId: workflowActionInput.objectRecordId,
objectRecord: workflowActionInput.objectRecord,
fieldsToUpdate: workflowActionInput.fieldsToUpdate,
objectRecord: filteredObjectRecord,
fieldsToUpdate: filteredFieldsToUpdate,
authContext: executionContext.authContext,
updatedBy,
rolePermissionConfig: executionContext.rolePermissionConfig,
@@ -9,6 +9,7 @@ import {
RecordCrudExceptionCode,
} from 'src/engine/core-modules/record-crud/exceptions/record-crud.exception';
import { UpsertRecordService } from 'src/engine/core-modules/record-crud/services/upsert-record.service';
import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
import {
WorkflowStepExecutorException,
WorkflowStepExecutorExceptionCode,
@@ -16,7 +17,9 @@ import {
import { WorkflowExecutionContextService } from 'src/modules/workflow/workflow-executor/services/workflow-execution-context.service';
import { type WorkflowActionInput } from 'src/modules/workflow/workflow-executor/types/workflow-action-input';
import { type WorkflowActionOutput } from 'src/modules/workflow/workflow-executor/types/workflow-action-output.type';
import { filterValidFieldsInRecord } from 'src/modules/workflow/workflow-executor/utils/filter-valid-fields-in-record.util';
import { findStepOrThrow } from 'src/modules/workflow/workflow-executor/utils/find-step-or-throw.util';
import { resolveRichTextFieldsInRecord } from 'src/modules/workflow/workflow-executor/utils/resolve-rich-text-fields-in-record.util';
import { isWorkflowUpsertRecordAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/guards/is-workflow-upsert-record-action.guard';
import { type WorkflowUpsertRecordActionInput } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/types/workflow-record-crud-action-input.type';
@@ -25,6 +28,7 @@ export class UpsertRecordWorkflowAction implements WorkflowAction {
constructor(
private readonly upsertRecordService: UpsertRecordService,
private readonly workflowExecutionContextService: WorkflowExecutionContextService,
private readonly workflowCommonWorkspaceService: WorkflowCommonWorkspaceService,
) {}
async execute({
@@ -45,8 +49,27 @@ export class UpsertRecordWorkflowAction implements WorkflowAction {
);
}
const { workspaceId } = runInfo;
const rawInput = step.settings.input as WorkflowUpsertRecordActionInput;
const objectMetadataInfo =
await this.workflowCommonWorkspaceService.getObjectMetadataInfo(
rawInput.objectName,
workspaceId,
);
const inputWithResolvedRichText = {
...rawInput,
objectRecord: resolveRichTextFieldsInRecord(
rawInput.objectRecord,
objectMetadataInfo,
context,
),
};
const workflowActionInput = resolveInput(
step.settings.input,
inputWithResolvedRichText,
context,
) as WorkflowUpsertRecordActionInput;
@@ -57,12 +80,18 @@ export class UpsertRecordWorkflowAction implements WorkflowAction {
);
}
const filteredObjectRecord = filterValidFieldsInRecord(
workflowActionInput.objectRecord,
objectMetadataInfo.flatObjectMetadata,
objectMetadataInfo.flatFieldMetadataMaps,
);
const executionContext =
await this.workflowExecutionContextService.getExecutionContext(runInfo);
const toolOutput = await this.upsertRecordService.execute({
objectName: workflowActionInput.objectName,
objectRecord: workflowActionInput.objectRecord,
objectRecord: filteredObjectRecord,
authContext: executionContext.authContext,
rolePermissionConfig: executionContext.rolePermissionConfig,
});