Migrate noteTarget and taskTarget to Morph. (#17476)

This PR migrates `noteTarget` and `taskTarget` to morph relations behind
separate feature flags, following the Attachment/TimelineActivity
pattern.

It introduces the `IS_NOTE_TARGET_MIGRATED` and
`IS_TASK_TARGET_MIGRATED` flags, updates standard field metadata and
indexes to use morph relations, and adds two **1.17 workspace
migrations** that:
- rename `noteTarget.*Id` / `taskTarget.*Id` columns to `target*Id`
- convert the corresponding field metadata to `MORPH_RELATION` with a
shared `morphId`

On the frontend, note/task target read and write paths switch to
`target*Id` when the respective flag is enabled. Deleted targets are
filtered on reload to prevent reappearing relations.
This commit is contained in:
Abdullah.
2026-02-04 07:53:06 +05:00
committed by GitHub
parent 9aba2f62e1
commit 7867617385
43 changed files with 1998 additions and 274 deletions
@@ -1,3 +1,4 @@
import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getActivityTargetObjectFieldIdName';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { isDefined } from 'twenty-shared/utils';
@@ -8,12 +9,14 @@ type GetActivityTargetFieldNameForObjectArgs = {
| CoreObjectNameSingular.Task;
targetObjectMetadataId: string;
objectMetadataItems: ObjectMetadataItem[];
isMorphRelation?: boolean;
};
export const getActivityTargetFieldNameForObject = ({
activityObjectNameSingular,
targetObjectMetadataId,
objectMetadataItems,
isMorphRelation = false,
}: GetActivityTargetFieldNameForObjectArgs): string | undefined => {
const activityTargetObjectNameSingular =
activityObjectNameSingular === CoreObjectNameSingular.Task
@@ -29,10 +32,21 @@ export const getActivityTargetFieldNameForObject = ({
return undefined;
}
const targetField = activityTargetObjectMetadata.fields.find(
(field) =>
field.relation?.targetObjectMetadata.id === targetObjectMetadataId,
const targetObjectMetadataItem = objectMetadataItems.find(
(objectMetadataItem) => objectMetadataItem.id === targetObjectMetadataId,
);
return targetField?.name;
if (isMorphRelation && isDefined(targetObjectMetadataItem)) {
const fieldIdName = getActivityTargetObjectFieldIdName({
nameSingular: targetObjectMetadataItem.nameSingular,
isMorphRelation: true,
});
return fieldIdName.replace(/Id$/, '');
}
return activityTargetObjectMetadata.fields.find(
(field) =>
field.relation?.targetObjectMetadata.id === targetObjectMetadataId,
)?.name;
};