TimelineActivity migration to morph (#15652)
# TimelineActivity migration to morph - Creates `timelineActivities2` relations on Company, Dashboard, Note, Opportunity, Person, Task, Workflow, WorkflowRun, and WorkflowVersion entities with proper metadata and cascade delete behavior. It was required to create standard fields as well since the mapObjectMetadataByUniqueIdentifier needs it. otherwise the fields won't be considered - Feature Flag `IS_TIMELINE_ACTIVITY_MIGRATED` necessary to have the two states in parallel. It is used as a stamp once the migration has been run - Migration is done using the coreDataSource. Why ? even though is unsafe to use, the first implementation of the migration took forever on each workspace. See [this commit](https://github.com/twentyhq/twenty/pull/15652/commits/477011e8d7d4c580f79ba7ec4a8fb002a3ec86b2) The plan for this complex migration is as follows :  Note: we will need to rename fields in the release 1.12 (there is no easy way to do all this in one release)
This commit is contained in:
+5
@@ -81,6 +81,11 @@ export const seedFeatureFlags = async ({
|
||||
workspaceId: workspaceId,
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
key: FeatureFlagKey.IS_TIMELINE_ACTIVITY_MIGRATED,
|
||||
workspaceId: workspaceId,
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
key: FeatureFlagKey.IS_GLOBAL_WORKSPACE_DATASOURCE_ENABLED,
|
||||
workspaceId: workspaceId,
|
||||
|
||||
+42
-30
@@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
|
||||
|
||||
import chunk from 'lodash.chunk';
|
||||
import { ObjectRecord } from 'twenty-shared/types';
|
||||
import { capitalize } from 'twenty-shared/utils';
|
||||
|
||||
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
|
||||
import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
||||
@@ -27,6 +28,7 @@ import { TASK_DATA_SEEDS } from 'src/engine/workspace-manager/dev-seeder/data/co
|
||||
import { TASK_TARGET_DATA_SEEDS_MAP } from 'src/engine/workspace-manager/dev-seeder/data/constants/task-target-data-seeds.constant';
|
||||
import { WORKSPACE_MEMBER_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/workspace-member-data-seeds.constant';
|
||||
import { type TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity';
|
||||
import { buildTimelineActivityRelatedMorphFieldMetadataName } from 'src/modules/timeline/utils/timeline-activity-related-morph-field-metadata-name-builder.util';
|
||||
|
||||
type RecordSeedWithId = Pick<ObjectRecord, 'id'> & Record<string, unknown>;
|
||||
|
||||
@@ -38,11 +40,11 @@ type TimelineActivitySeedData = Pick<
|
||||
| 'linkedRecordId'
|
||||
| 'linkedObjectMetadataId'
|
||||
| 'workspaceMemberId'
|
||||
| 'companyId'
|
||||
| 'personId'
|
||||
| 'noteId'
|
||||
| 'taskId'
|
||||
| 'opportunityId'
|
||||
| 'targetNoteId'
|
||||
| 'targetTaskId'
|
||||
| 'targetPersonId'
|
||||
| 'targetCompanyId'
|
||||
| 'targetOpportunityId'
|
||||
> & {
|
||||
properties: string; // JSON stringified for raw insertion
|
||||
createdAt: string; // ISO string for raw insertion
|
||||
@@ -253,11 +255,11 @@ export class TimelineActivitySeederService {
|
||||
'linkedRecordId',
|
||||
'linkedObjectMetadataId',
|
||||
'workspaceMemberId',
|
||||
'companyId',
|
||||
'personId',
|
||||
'noteId',
|
||||
'taskId',
|
||||
'opportunityId',
|
||||
'targetNoteId',
|
||||
'targetTaskId',
|
||||
'targetPersonId',
|
||||
'targetCompanyId',
|
||||
'targetOpportunityId',
|
||||
'createdAt',
|
||||
'updatedAt',
|
||||
'happensAt',
|
||||
@@ -290,21 +292,31 @@ export class TimelineActivitySeederService {
|
||||
linkedRecordId: recordId,
|
||||
linkedObjectMetadataId: null,
|
||||
workspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.TIM,
|
||||
companyId: null,
|
||||
personId: null,
|
||||
noteId: null,
|
||||
taskId: null,
|
||||
opportunityId: null,
|
||||
targetNoteId: null,
|
||||
targetTaskId: null,
|
||||
targetPersonId: null,
|
||||
targetCompanyId: null,
|
||||
targetOpportunityId: null,
|
||||
createdAt: creationDate,
|
||||
updatedAt: creationDate,
|
||||
happensAt: creationDate,
|
||||
};
|
||||
|
||||
// Set the appropriate entity ID
|
||||
const entityIdKey = `${entityType}Id`;
|
||||
// Set the appropriate target entity ID for entities that have target columns
|
||||
const entitiesWithTargetColumns = new Set([
|
||||
'note',
|
||||
'task',
|
||||
'person',
|
||||
'company',
|
||||
'opportunity',
|
||||
]);
|
||||
|
||||
// @ts-expect-error - This is okay for morph
|
||||
timelineActivity[entityIdKey] = recordId;
|
||||
if (entitiesWithTargetColumns.has(entityType)) {
|
||||
const targetIdKey = `${buildTimelineActivityRelatedMorphFieldMetadataName(entityType)}Id`;
|
||||
|
||||
// @ts-expect-error - This is okay for morph
|
||||
timelineActivity[targetIdKey] = recordId;
|
||||
}
|
||||
|
||||
return timelineActivity;
|
||||
}
|
||||
@@ -576,30 +588,30 @@ export class TimelineActivitySeederService {
|
||||
linkedRecordId: recordSeed.id,
|
||||
linkedObjectMetadataId,
|
||||
workspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.TIM,
|
||||
companyId: null,
|
||||
personId: null,
|
||||
noteId: null,
|
||||
taskId: null,
|
||||
opportunityId: null,
|
||||
targetNoteId: null,
|
||||
targetTaskId: null,
|
||||
targetPersonId: null,
|
||||
targetCompanyId: null,
|
||||
targetOpportunityId: null,
|
||||
createdAt: creationDate,
|
||||
updatedAt: creationDate,
|
||||
happensAt: creationDate,
|
||||
};
|
||||
|
||||
// Set target ID (person, company, or opportunity)
|
||||
const targetIdKey = `${targetInfo.targetType}Id`;
|
||||
const targetIdKey = `target${capitalize(targetInfo.targetType)}Id`;
|
||||
|
||||
// @ts-expect-error - This is okay for morph
|
||||
linkedActivity[targetIdKey] = targetInfo.targetId;
|
||||
|
||||
// Only set activity ID for entities that have corresponding columns
|
||||
const entitiesWithColumns = new Set(['note', 'task']);
|
||||
// Only set target activity ID for entities that have corresponding columns
|
||||
const entitiesWithTargetColumns = new Set(['note', 'task']);
|
||||
|
||||
if (entitiesWithColumns.has(activityType)) {
|
||||
const activityIdKey = `${activityType}Id`;
|
||||
if (entitiesWithTargetColumns.has(activityType)) {
|
||||
const targetActivityIdKey = `target${capitalize(activityType)}Id`;
|
||||
|
||||
// @ts-expect-error - This is okay for morph
|
||||
linkedActivity[activityIdKey] = recordSeed.id;
|
||||
linkedActivity[targetActivityIdKey] = recordSeed.id;
|
||||
}
|
||||
|
||||
return linkedActivity;
|
||||
|
||||
Reference in New Issue
Block a user