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:
+299
@@ -0,0 +1,299 @@
|
||||
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
|
||||
|
||||
// Maps indexName to its related field universal identifiers for each standard object
|
||||
export const STANDARD_INDEX_FIELD_UNIVERSAL_IDENTIFIERS: Record<
|
||||
string,
|
||||
Record<string, string[]>
|
||||
> = {
|
||||
attachment: {
|
||||
taskIdIndex: [
|
||||
STANDARD_OBJECTS.attachment.fields.targetTask.universalIdentifier,
|
||||
],
|
||||
noteIdIndex: [
|
||||
STANDARD_OBJECTS.attachment.fields.targetNote.universalIdentifier,
|
||||
],
|
||||
personIdIndex: [
|
||||
STANDARD_OBJECTS.attachment.fields.targetPerson.universalIdentifier,
|
||||
],
|
||||
companyIdIndex: [
|
||||
STANDARD_OBJECTS.attachment.fields.targetCompany.universalIdentifier,
|
||||
],
|
||||
opportunityIdIndex: [
|
||||
STANDARD_OBJECTS.attachment.fields.targetOpportunity.universalIdentifier,
|
||||
],
|
||||
dashboardIdIndex: [
|
||||
STANDARD_OBJECTS.attachment.fields.targetDashboard.universalIdentifier,
|
||||
],
|
||||
workflowIdIndex: [
|
||||
STANDARD_OBJECTS.attachment.fields.targetWorkflow.universalIdentifier,
|
||||
],
|
||||
},
|
||||
blocklist: {
|
||||
workspaceMemberIdIndex: [
|
||||
STANDARD_OBJECTS.blocklist.fields.workspaceMember.universalIdentifier,
|
||||
],
|
||||
},
|
||||
calendarChannelEventAssociation: {
|
||||
calendarChannelIdIndex: [
|
||||
STANDARD_OBJECTS.calendarChannelEventAssociation.fields.calendarChannel
|
||||
.universalIdentifier,
|
||||
],
|
||||
calendarEventIdIndex: [
|
||||
STANDARD_OBJECTS.calendarChannelEventAssociation.fields.calendarEvent
|
||||
.universalIdentifier,
|
||||
],
|
||||
},
|
||||
calendarChannel: {
|
||||
connectedAccountIdIndex: [
|
||||
STANDARD_OBJECTS.calendarChannel.fields.connectedAccount
|
||||
.universalIdentifier,
|
||||
],
|
||||
},
|
||||
calendarEventParticipant: {
|
||||
calendarEventIdIndex: [
|
||||
STANDARD_OBJECTS.calendarEventParticipant.fields.calendarEvent
|
||||
.universalIdentifier,
|
||||
],
|
||||
personIdIndex: [
|
||||
STANDARD_OBJECTS.calendarEventParticipant.fields.person
|
||||
.universalIdentifier,
|
||||
],
|
||||
workspaceMemberIdIndex: [
|
||||
STANDARD_OBJECTS.calendarEventParticipant.fields.workspaceMember
|
||||
.universalIdentifier,
|
||||
],
|
||||
},
|
||||
company: {
|
||||
accountOwnerIdIndex: [
|
||||
STANDARD_OBJECTS.company.fields.accountOwner.universalIdentifier,
|
||||
],
|
||||
domainNameUniqueIndex: [
|
||||
STANDARD_OBJECTS.company.fields.domainName.universalIdentifier,
|
||||
],
|
||||
searchVectorGinIndex: [
|
||||
STANDARD_OBJECTS.company.fields.searchVector.universalIdentifier,
|
||||
],
|
||||
},
|
||||
connectedAccount: {
|
||||
accountOwnerIdIndex: [
|
||||
STANDARD_OBJECTS.connectedAccount.fields.accountOwner.universalIdentifier,
|
||||
],
|
||||
},
|
||||
dashboard: {
|
||||
searchVectorGinIndex: [
|
||||
STANDARD_OBJECTS.dashboard.fields.searchVector.universalIdentifier,
|
||||
],
|
||||
},
|
||||
favorite: {
|
||||
forWorkspaceMemberIdIndex: [
|
||||
STANDARD_OBJECTS.favorite.fields.forWorkspaceMember.universalIdentifier,
|
||||
],
|
||||
personIdIndex: [
|
||||
STANDARD_OBJECTS.favorite.fields.person.universalIdentifier,
|
||||
],
|
||||
companyIdIndex: [
|
||||
STANDARD_OBJECTS.favorite.fields.company.universalIdentifier,
|
||||
],
|
||||
favoriteFolderIdIndex: [
|
||||
STANDARD_OBJECTS.favorite.fields.favoriteFolder.universalIdentifier,
|
||||
],
|
||||
opportunityIdIndex: [
|
||||
STANDARD_OBJECTS.favorite.fields.opportunity.universalIdentifier,
|
||||
],
|
||||
workflowIdIndex: [
|
||||
STANDARD_OBJECTS.favorite.fields.workflow.universalIdentifier,
|
||||
],
|
||||
workflowVersionIdIndex: [
|
||||
STANDARD_OBJECTS.favorite.fields.workflowVersion.universalIdentifier,
|
||||
],
|
||||
workflowRunIdIndex: [
|
||||
STANDARD_OBJECTS.favorite.fields.workflowRun.universalIdentifier,
|
||||
],
|
||||
taskIdIndex: [STANDARD_OBJECTS.favorite.fields.task.universalIdentifier],
|
||||
noteIdIndex: [STANDARD_OBJECTS.favorite.fields.note.universalIdentifier],
|
||||
dashboardIdIndex: [
|
||||
STANDARD_OBJECTS.favorite.fields.dashboard.universalIdentifier,
|
||||
],
|
||||
},
|
||||
messageChannelMessageAssociation: {
|
||||
messageChannelIdIndex: [
|
||||
STANDARD_OBJECTS.messageChannelMessageAssociation.fields.messageChannel
|
||||
.universalIdentifier,
|
||||
],
|
||||
messageIdIndex: [
|
||||
STANDARD_OBJECTS.messageChannelMessageAssociation.fields.message
|
||||
.universalIdentifier,
|
||||
],
|
||||
messageChannelIdMessageIdUniqueIndex: [
|
||||
STANDARD_OBJECTS.messageChannelMessageAssociation.fields.messageChannel
|
||||
.universalIdentifier,
|
||||
STANDARD_OBJECTS.messageChannelMessageAssociation.fields.message
|
||||
.universalIdentifier,
|
||||
],
|
||||
},
|
||||
messageChannel: {
|
||||
connectedAccountIdIndex: [
|
||||
STANDARD_OBJECTS.messageChannel.fields.connectedAccount
|
||||
.universalIdentifier,
|
||||
],
|
||||
},
|
||||
messageFolder: {
|
||||
messageChannelIdIndex: [
|
||||
STANDARD_OBJECTS.messageFolder.fields.messageChannel.universalIdentifier,
|
||||
],
|
||||
},
|
||||
messageParticipant: {
|
||||
messageIdIndex: [
|
||||
STANDARD_OBJECTS.messageParticipant.fields.message.universalIdentifier,
|
||||
],
|
||||
personIdIndex: [
|
||||
STANDARD_OBJECTS.messageParticipant.fields.person.universalIdentifier,
|
||||
],
|
||||
workspaceMemberIdIndex: [
|
||||
STANDARD_OBJECTS.messageParticipant.fields.workspaceMember
|
||||
.universalIdentifier,
|
||||
],
|
||||
},
|
||||
message: {
|
||||
messageThreadIdIndex: [
|
||||
STANDARD_OBJECTS.message.fields.messageThread.universalIdentifier,
|
||||
],
|
||||
},
|
||||
note: {
|
||||
searchVectorGinIndex: [
|
||||
STANDARD_OBJECTS.note.fields.searchVector.universalIdentifier,
|
||||
],
|
||||
},
|
||||
noteTarget: {
|
||||
noteIdIndex: [STANDARD_OBJECTS.noteTarget.fields.note.universalIdentifier],
|
||||
personIdIndex: [
|
||||
STANDARD_OBJECTS.noteTarget.fields.targetPerson.universalIdentifier,
|
||||
],
|
||||
companyIdIndex: [
|
||||
STANDARD_OBJECTS.noteTarget.fields.targetCompany.universalIdentifier,
|
||||
],
|
||||
opportunityIdIndex: [
|
||||
STANDARD_OBJECTS.noteTarget.fields.targetOpportunity.universalIdentifier,
|
||||
],
|
||||
},
|
||||
opportunity: {
|
||||
pointOfContactIdIndex: [
|
||||
STANDARD_OBJECTS.opportunity.fields.pointOfContact.universalIdentifier,
|
||||
],
|
||||
companyIdIndex: [
|
||||
STANDARD_OBJECTS.opportunity.fields.company.universalIdentifier,
|
||||
],
|
||||
stageIndex: [STANDARD_OBJECTS.opportunity.fields.stage.universalIdentifier],
|
||||
searchVectorGinIndex: [
|
||||
STANDARD_OBJECTS.opportunity.fields.searchVector.universalIdentifier,
|
||||
],
|
||||
},
|
||||
person: {
|
||||
companyIdIndex: [
|
||||
STANDARD_OBJECTS.person.fields.company.universalIdentifier,
|
||||
],
|
||||
emailsUniqueIndex: [
|
||||
STANDARD_OBJECTS.person.fields.emails.universalIdentifier,
|
||||
],
|
||||
searchVectorGinIndex: [
|
||||
STANDARD_OBJECTS.person.fields.searchVector.universalIdentifier,
|
||||
],
|
||||
},
|
||||
task: {
|
||||
assigneeIdIndex: [
|
||||
STANDARD_OBJECTS.task.fields.assignee.universalIdentifier,
|
||||
],
|
||||
searchVectorGinIndex: [
|
||||
STANDARD_OBJECTS.task.fields.searchVector.universalIdentifier,
|
||||
],
|
||||
},
|
||||
taskTarget: {
|
||||
taskIdIndex: [STANDARD_OBJECTS.taskTarget.fields.task.universalIdentifier],
|
||||
personIdIndex: [
|
||||
STANDARD_OBJECTS.taskTarget.fields.targetPerson.universalIdentifier,
|
||||
],
|
||||
companyIdIndex: [
|
||||
STANDARD_OBJECTS.taskTarget.fields.targetCompany.universalIdentifier,
|
||||
],
|
||||
opportunityIdIndex: [
|
||||
STANDARD_OBJECTS.taskTarget.fields.targetOpportunity.universalIdentifier,
|
||||
],
|
||||
},
|
||||
timelineActivity: {
|
||||
workspaceMemberIdIndex: [
|
||||
STANDARD_OBJECTS.timelineActivity.fields.workspaceMember
|
||||
.universalIdentifier,
|
||||
],
|
||||
personIdIndex: [
|
||||
STANDARD_OBJECTS.timelineActivity.fields.targetPerson.universalIdentifier,
|
||||
],
|
||||
companyIdIndex: [
|
||||
STANDARD_OBJECTS.timelineActivity.fields.targetCompany
|
||||
.universalIdentifier,
|
||||
],
|
||||
opportunityIdIndex: [
|
||||
STANDARD_OBJECTS.timelineActivity.fields.targetOpportunity
|
||||
.universalIdentifier,
|
||||
],
|
||||
noteIdIndex: [
|
||||
STANDARD_OBJECTS.timelineActivity.fields.targetNote.universalIdentifier,
|
||||
],
|
||||
taskIdIndex: [
|
||||
STANDARD_OBJECTS.timelineActivity.fields.targetTask.universalIdentifier,
|
||||
],
|
||||
workflowIdIndex: [
|
||||
STANDARD_OBJECTS.timelineActivity.fields.targetWorkflow
|
||||
.universalIdentifier,
|
||||
],
|
||||
workflowVersionIdIndex: [
|
||||
STANDARD_OBJECTS.timelineActivity.fields.targetWorkflowVersion
|
||||
.universalIdentifier,
|
||||
],
|
||||
workflowRunIdIndex: [
|
||||
STANDARD_OBJECTS.timelineActivity.fields.targetWorkflowRun
|
||||
.universalIdentifier,
|
||||
],
|
||||
dashboardIdIndex: [
|
||||
STANDARD_OBJECTS.timelineActivity.fields.targetDashboard
|
||||
.universalIdentifier,
|
||||
],
|
||||
},
|
||||
workflow: {
|
||||
searchVectorGinIndex: [
|
||||
STANDARD_OBJECTS.workflow.fields.searchVector.universalIdentifier,
|
||||
],
|
||||
},
|
||||
workflowAutomatedTrigger: {
|
||||
workflowIdIndex: [
|
||||
STANDARD_OBJECTS.workflowAutomatedTrigger.fields.workflow
|
||||
.universalIdentifier,
|
||||
],
|
||||
},
|
||||
workflowRun: {
|
||||
workflowVersionIdIndex: [
|
||||
STANDARD_OBJECTS.workflowRun.fields.workflowVersion.universalIdentifier,
|
||||
],
|
||||
workflowIdIndex: [
|
||||
STANDARD_OBJECTS.workflowRun.fields.workflow.universalIdentifier,
|
||||
],
|
||||
searchVectorGinIndex: [
|
||||
STANDARD_OBJECTS.workflowRun.fields.searchVector.universalIdentifier,
|
||||
],
|
||||
},
|
||||
workflowVersion: {
|
||||
workflowIdIndex: [
|
||||
STANDARD_OBJECTS.workflowVersion.fields.workflow.universalIdentifier,
|
||||
],
|
||||
searchVectorGinIndex: [
|
||||
STANDARD_OBJECTS.workflowVersion.fields.searchVector.universalIdentifier,
|
||||
],
|
||||
},
|
||||
workspaceMember: {
|
||||
userEmailUniqueIndex: [
|
||||
STANDARD_OBJECTS.workspaceMember.fields.userEmail.universalIdentifier,
|
||||
],
|
||||
searchVectorGinIndex: [
|
||||
STANDARD_OBJECTS.workspaceMember.fields.searchVector.universalIdentifier,
|
||||
],
|
||||
},
|
||||
};
|
||||
+12
-5
@@ -289,11 +289,18 @@ export class MigrateAttachmentToMorphRelationsCommand extends ActiveOrSuspendedW
|
||||
this.logger.log(`Flush cache for workspace ${workspaceId}`);
|
||||
await this.workspaceCacheStorageService.flush(workspaceId);
|
||||
} catch (error) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
this.logger.error(
|
||||
`Error migrating attachment to morph relations (rolled transaction back on ${workspaceId})`,
|
||||
error,
|
||||
);
|
||||
if (queryRunner.isTransactionActive) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
this.logger.error(
|
||||
`Error migrating attachment to morph relations (rolled transaction back on ${workspaceId})`,
|
||||
error,
|
||||
);
|
||||
} else {
|
||||
this.logger.error(
|
||||
`Error migrating attachment to morph relations after commit on ${workspaceId}`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
|
||||
+327
@@ -0,0 +1,327 @@
|
||||
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command } from 'nest-commander';
|
||||
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
type FieldMetadataSettings,
|
||||
} from 'twenty-shared/types';
|
||||
import { capitalize } from 'twenty-shared/utils';
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { getFlatFieldsFromFlatObjectMetadata } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-flat-fields-for-flat-object-metadata.util';
|
||||
import { type FlatApplicationCacheMaps } from 'src/engine/core-modules/application/types/flat-application-cache-maps.type';
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util';
|
||||
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
||||
import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util';
|
||||
import { getMetadataRelatedMetadataNames } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-related-metadata-names.util';
|
||||
import { isMorphOrRelationFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-morph-or-relation-flat-field-metadata.util';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
||||
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
|
||||
import { type WorkspaceCacheKeyName } from 'src/engine/workspace-cache/types/workspace-cache-key.type';
|
||||
import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util';
|
||||
import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications';
|
||||
|
||||
type RelationFieldMetadataSettings =
|
||||
FieldMetadataSettings<FieldMetadataType.RELATION>;
|
||||
|
||||
@Command({
|
||||
name: 'upgrade:1-17:migrate-note-target-to-morph-relations',
|
||||
description:
|
||||
'Migrate noteTarget relations to morph relation fields and set feature flag',
|
||||
})
|
||||
export class MigrateNoteTargetToMorphRelationsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
constructor(
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
@InjectDataSource()
|
||||
private readonly coreDataSource: DataSource,
|
||||
private readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
|
||||
private readonly workspaceCacheService: WorkspaceCacheService,
|
||||
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
workspaceId,
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
const isMigrated = await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_NOTE_TARGET_MIGRATED,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
this.logger.log(`Migrating noteTarget for workspace ${workspaceId}`);
|
||||
|
||||
if (isMigrated) {
|
||||
this.logger.log(`NoteTarget migration already completed. Skipping...`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.dryRun) {
|
||||
this.logger.log(
|
||||
`Would have migrated noteTarget for workspace ${workspaceId}. Skipping...`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const queryRunner = this.coreDataSource.createQueryRunner();
|
||||
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
const schemaName = getWorkspaceSchemaName(workspaceId);
|
||||
const tableName = 'noteTarget';
|
||||
|
||||
const {
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
flatApplicationMaps,
|
||||
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatObjectMetadataMaps',
|
||||
'flatFieldMetadataMaps',
|
||||
'flatApplicationMaps',
|
||||
]);
|
||||
|
||||
const noteTargetFieldUniversalIdentifiers = new Set<string>([
|
||||
STANDARD_OBJECTS.noteTarget.fields.targetPerson.universalIdentifier,
|
||||
STANDARD_OBJECTS.noteTarget.fields.targetCompany.universalIdentifier,
|
||||
STANDARD_OBJECTS.noteTarget.fields.targetOpportunity
|
||||
.universalIdentifier,
|
||||
]);
|
||||
|
||||
const noteTargetObjectMetadata =
|
||||
findFlatEntityByUniversalIdentifier<FlatObjectMetadata>({
|
||||
flatEntityMaps: flatObjectMetadataMaps,
|
||||
universalIdentifier: STANDARD_OBJECTS.noteTarget.universalIdentifier,
|
||||
});
|
||||
|
||||
if (!noteTargetObjectMetadata) {
|
||||
this.logger.error(
|
||||
`🟥 ${tableName} object metadata not found for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const noteTargetFieldMetadatas = getFlatFieldsFromFlatObjectMetadata(
|
||||
noteTargetObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
);
|
||||
|
||||
const noteTargetRelationFields = noteTargetFieldMetadatas
|
||||
.filter(isMorphOrRelationFlatFieldMetadata)
|
||||
.filter((field) => field.type === FieldMetadataType.RELATION)
|
||||
.filter((field) => {
|
||||
const isStandardAppField = this.isTwentyStandardApplicationField({
|
||||
field,
|
||||
flatApplicationMaps,
|
||||
workspaceId,
|
||||
});
|
||||
const isStandardTarget =
|
||||
isStandardAppField &&
|
||||
noteTargetFieldUniversalIdentifiers.has(field.universalIdentifier);
|
||||
const targetObjectMetadata = field.relationTargetObjectMetadataId
|
||||
? findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityMaps: flatObjectMetadataMaps,
|
||||
flatEntityId: field.relationTargetObjectMetadataId,
|
||||
})
|
||||
: undefined;
|
||||
const isCustomTarget =
|
||||
!isStandardAppField && targetObjectMetadata?.isCustom === true;
|
||||
|
||||
return isStandardTarget || isCustomTarget;
|
||||
});
|
||||
|
||||
const fieldMigrations = noteTargetRelationFields.map((field) => {
|
||||
const newFieldName = `target${capitalize(field.name)}`;
|
||||
const relationSettings: RelationFieldMetadataSettings = field.settings;
|
||||
const oldJoinColumnName =
|
||||
relationSettings?.joinColumnName ??
|
||||
computeMorphOrRelationFieldJoinColumnName({ name: field.name });
|
||||
const newJoinColumnName = computeMorphOrRelationFieldJoinColumnName({
|
||||
name: newFieldName,
|
||||
});
|
||||
|
||||
return {
|
||||
field,
|
||||
newFieldName,
|
||||
oldJoinColumnName,
|
||||
newJoinColumnName,
|
||||
};
|
||||
});
|
||||
|
||||
// Rename columns
|
||||
for (const { oldJoinColumnName, newJoinColumnName } of fieldMigrations) {
|
||||
if (oldJoinColumnName === newJoinColumnName) {
|
||||
this.logger.log(
|
||||
`Column "${oldJoinColumnName}" already renamed. Skipping...`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "${schemaName}"."${tableName}"
|
||||
RENAME COLUMN "${oldJoinColumnName}" TO "${newJoinColumnName}"`,
|
||||
);
|
||||
this.logger.log(
|
||||
`Renamed column "${oldJoinColumnName}" to "${newJoinColumnName}" for "${tableName}"`,
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Error renaming column "${oldJoinColumnName}" to "${newJoinColumnName}" for "${tableName}" in workspace ${workspaceId}`,
|
||||
error,
|
||||
);
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.log(`✅ Successfully migrated ${tableName} records`);
|
||||
|
||||
const morphId =
|
||||
STANDARD_OBJECTS.noteTarget.morphIds.targetMorphId.morphId;
|
||||
|
||||
// Update field metadata
|
||||
for (const {
|
||||
field: fieldToMigrate,
|
||||
newFieldName,
|
||||
newJoinColumnName,
|
||||
} of fieldMigrations) {
|
||||
const settings = {
|
||||
...fieldToMigrate.settings,
|
||||
joinColumnName: newJoinColumnName,
|
||||
};
|
||||
|
||||
try {
|
||||
const result = await queryRunner.query(
|
||||
`UPDATE core."fieldMetadata"
|
||||
SET name = $1, type = $5, "morphId" = $3, settings = $4
|
||||
WHERE id = $2`,
|
||||
[
|
||||
newFieldName,
|
||||
fieldToMigrate.id,
|
||||
morphId,
|
||||
settings,
|
||||
FieldMetadataType.MORPH_RELATION,
|
||||
],
|
||||
);
|
||||
|
||||
const rowsUpdated = result[1] || 0;
|
||||
|
||||
if (rowsUpdated > 0) {
|
||||
this.logger.log(
|
||||
`Updated fieldMetadata: ${fieldToMigrate.name} → ${newFieldName} (type: MORPH_RELATION)`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Error updating fieldMetadata for field "${fieldToMigrate.name}" in workspace ${workspaceId}`,
|
||||
error,
|
||||
);
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.log(`✅ Successfully migrated ${tableName} fieldmetadata`);
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
await this.featureFlagService.enableFeatureFlags(
|
||||
[FeatureFlagKey.IS_NOTE_TARGET_MIGRATED],
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const relatedMetadataNames =
|
||||
getMetadataRelatedMetadataNames('fieldMetadata');
|
||||
const relatedCacheKeysToInvalidate: WorkspaceCacheKeyName[] =
|
||||
relatedMetadataNames.map(getMetadataFlatEntityMapsKey);
|
||||
const cacheKeysToInvalidate: WorkspaceCacheKeyName[] = [
|
||||
'flatFieldMetadataMaps',
|
||||
...relatedCacheKeysToInvalidate,
|
||||
'featureFlagsMap',
|
||||
];
|
||||
|
||||
this.logger.log(
|
||||
`Invalidating caches: ${cacheKeysToInvalidate.join(' ')}`,
|
||||
);
|
||||
|
||||
await this.workspaceCacheService.invalidateAndRecompute(
|
||||
workspaceId,
|
||||
cacheKeysToInvalidate,
|
||||
);
|
||||
|
||||
await this.workspaceMetadataVersionService.incrementMetadataVersion(
|
||||
workspaceId,
|
||||
);
|
||||
this.logger.log(`Cache flushed`);
|
||||
|
||||
this.logger.log(
|
||||
`Set IS_NOTE_TARGET_MIGRATED feature flag for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
this.logger.log(`Flush cache for workspace ${workspaceId}`);
|
||||
await this.workspaceCacheStorageService.flush(workspaceId);
|
||||
} catch (error) {
|
||||
if (queryRunner.isTransactionActive) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
this.logger.error(
|
||||
`Error migrating noteTarget to morph relations (rolled transaction back on ${workspaceId})`,
|
||||
error,
|
||||
);
|
||||
} else {
|
||||
this.logger.error(
|
||||
`Error migrating noteTarget to morph relations after commit on ${workspaceId}`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
|
||||
private isTwentyStandardApplicationField({
|
||||
field,
|
||||
flatApplicationMaps,
|
||||
workspaceId,
|
||||
}: {
|
||||
field: { id: string; name: string; applicationId: string };
|
||||
flatApplicationMaps: FlatApplicationCacheMaps;
|
||||
workspaceId: string;
|
||||
}): boolean {
|
||||
const application = flatApplicationMaps.byId[field.applicationId];
|
||||
|
||||
if (!application) {
|
||||
this.logger.error(
|
||||
`🟥 Application not found for field "${field.name}" (${field.id}) in workspace ${workspaceId} (applicationId: ${field.applicationId})`,
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
application.universalIdentifier ===
|
||||
TWENTY_STANDARD_APPLICATION.universalIdentifier
|
||||
);
|
||||
}
|
||||
}
|
||||
+327
@@ -0,0 +1,327 @@
|
||||
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command } from 'nest-commander';
|
||||
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
type FieldMetadataSettings,
|
||||
} from 'twenty-shared/types';
|
||||
import { capitalize } from 'twenty-shared/utils';
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { getFlatFieldsFromFlatObjectMetadata } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-flat-fields-for-flat-object-metadata.util';
|
||||
import { type FlatApplicationCacheMaps } from 'src/engine/core-modules/application/types/flat-application-cache-maps.type';
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util';
|
||||
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
||||
import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util';
|
||||
import { getMetadataRelatedMetadataNames } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-related-metadata-names.util';
|
||||
import { isMorphOrRelationFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-morph-or-relation-flat-field-metadata.util';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
||||
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
|
||||
import { type WorkspaceCacheKeyName } from 'src/engine/workspace-cache/types/workspace-cache-key.type';
|
||||
import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util';
|
||||
import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications';
|
||||
|
||||
type RelationFieldMetadataSettings =
|
||||
FieldMetadataSettings<FieldMetadataType.RELATION>;
|
||||
|
||||
@Command({
|
||||
name: 'upgrade:1-17:migrate-task-target-to-morph-relations',
|
||||
description:
|
||||
'Migrate taskTarget relations to morph relation fields and set feature flag',
|
||||
})
|
||||
export class MigrateTaskTargetToMorphRelationsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
constructor(
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
@InjectDataSource()
|
||||
private readonly coreDataSource: DataSource,
|
||||
private readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
|
||||
private readonly workspaceCacheService: WorkspaceCacheService,
|
||||
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
workspaceId,
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
const isMigrated = await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_TASK_TARGET_MIGRATED,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
this.logger.log(`Migrating taskTarget for workspace ${workspaceId}`);
|
||||
|
||||
if (isMigrated) {
|
||||
this.logger.log(`TaskTarget migration already completed. Skipping...`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.dryRun) {
|
||||
this.logger.log(
|
||||
`Would have migrated taskTarget for workspace ${workspaceId}. Skipping...`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const queryRunner = this.coreDataSource.createQueryRunner();
|
||||
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
const schemaName = getWorkspaceSchemaName(workspaceId);
|
||||
const tableName = 'taskTarget';
|
||||
|
||||
const {
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
flatApplicationMaps,
|
||||
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatObjectMetadataMaps',
|
||||
'flatFieldMetadataMaps',
|
||||
'flatApplicationMaps',
|
||||
]);
|
||||
|
||||
const taskTargetFieldUniversalIdentifiers = new Set<string>([
|
||||
STANDARD_OBJECTS.taskTarget.fields.targetPerson.universalIdentifier,
|
||||
STANDARD_OBJECTS.taskTarget.fields.targetCompany.universalIdentifier,
|
||||
STANDARD_OBJECTS.taskTarget.fields.targetOpportunity
|
||||
.universalIdentifier,
|
||||
]);
|
||||
|
||||
const taskTargetObjectMetadata =
|
||||
findFlatEntityByUniversalIdentifier<FlatObjectMetadata>({
|
||||
flatEntityMaps: flatObjectMetadataMaps,
|
||||
universalIdentifier: STANDARD_OBJECTS.taskTarget.universalIdentifier,
|
||||
});
|
||||
|
||||
if (!taskTargetObjectMetadata) {
|
||||
this.logger.error(
|
||||
`🟥 ${tableName} object metadata not found for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const taskTargetFieldMetadatas = getFlatFieldsFromFlatObjectMetadata(
|
||||
taskTargetObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
);
|
||||
|
||||
const taskTargetRelationFields = taskTargetFieldMetadatas
|
||||
.filter(isMorphOrRelationFlatFieldMetadata)
|
||||
.filter((field) => field.type === FieldMetadataType.RELATION)
|
||||
.filter((field) => {
|
||||
const isStandardAppField = this.isTwentyStandardApplicationField({
|
||||
field,
|
||||
flatApplicationMaps,
|
||||
workspaceId,
|
||||
});
|
||||
const isStandardTarget =
|
||||
isStandardAppField &&
|
||||
taskTargetFieldUniversalIdentifiers.has(field.universalIdentifier);
|
||||
const targetObjectMetadata = field.relationTargetObjectMetadataId
|
||||
? findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityMaps: flatObjectMetadataMaps,
|
||||
flatEntityId: field.relationTargetObjectMetadataId,
|
||||
})
|
||||
: undefined;
|
||||
const isCustomTarget =
|
||||
!isStandardAppField && targetObjectMetadata?.isCustom === true;
|
||||
|
||||
return isStandardTarget || isCustomTarget;
|
||||
});
|
||||
|
||||
const fieldMigrations = taskTargetRelationFields.map((field) => {
|
||||
const newFieldName = `target${capitalize(field.name)}`;
|
||||
const relationSettings: RelationFieldMetadataSettings = field.settings;
|
||||
const oldJoinColumnName =
|
||||
relationSettings?.joinColumnName ??
|
||||
computeMorphOrRelationFieldJoinColumnName({ name: field.name });
|
||||
const newJoinColumnName = computeMorphOrRelationFieldJoinColumnName({
|
||||
name: newFieldName,
|
||||
});
|
||||
|
||||
return {
|
||||
field,
|
||||
newFieldName,
|
||||
oldJoinColumnName,
|
||||
newJoinColumnName,
|
||||
};
|
||||
});
|
||||
|
||||
// Rename columns
|
||||
for (const { oldJoinColumnName, newJoinColumnName } of fieldMigrations) {
|
||||
if (oldJoinColumnName === newJoinColumnName) {
|
||||
this.logger.log(
|
||||
`Column "${oldJoinColumnName}" already renamed. Skipping...`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "${schemaName}"."${tableName}"
|
||||
RENAME COLUMN "${oldJoinColumnName}" TO "${newJoinColumnName}"`,
|
||||
);
|
||||
this.logger.log(
|
||||
`Renamed column "${oldJoinColumnName}" to "${newJoinColumnName}" for "${tableName}"`,
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Error renaming column "${oldJoinColumnName}" to "${newJoinColumnName}" for "${tableName}" in workspace ${workspaceId}`,
|
||||
error,
|
||||
);
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.log(`✅ Successfully migrated ${tableName} records`);
|
||||
|
||||
const morphId =
|
||||
STANDARD_OBJECTS.taskTarget.morphIds.targetMorphId.morphId;
|
||||
|
||||
// Update field metadata
|
||||
for (const {
|
||||
field: fieldToMigrate,
|
||||
newFieldName,
|
||||
newJoinColumnName,
|
||||
} of fieldMigrations) {
|
||||
const settings = {
|
||||
...fieldToMigrate.settings,
|
||||
joinColumnName: newJoinColumnName,
|
||||
};
|
||||
|
||||
try {
|
||||
const result = await queryRunner.query(
|
||||
`UPDATE core."fieldMetadata"
|
||||
SET name = $1, type = $5, "morphId" = $3, settings = $4
|
||||
WHERE id = $2`,
|
||||
[
|
||||
newFieldName,
|
||||
fieldToMigrate.id,
|
||||
morphId,
|
||||
settings,
|
||||
FieldMetadataType.MORPH_RELATION,
|
||||
],
|
||||
);
|
||||
|
||||
const rowsUpdated = result[1] || 0;
|
||||
|
||||
if (rowsUpdated > 0) {
|
||||
this.logger.log(
|
||||
`Updated fieldMetadata: ${fieldToMigrate.name} → ${newFieldName} (type: MORPH_RELATION)`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Error updating fieldMetadata for field "${fieldToMigrate.name}" in workspace ${workspaceId}`,
|
||||
error,
|
||||
);
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.log(`✅ Successfully migrated ${tableName} fieldmetadata`);
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
await this.featureFlagService.enableFeatureFlags(
|
||||
[FeatureFlagKey.IS_TASK_TARGET_MIGRATED],
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const relatedMetadataNames =
|
||||
getMetadataRelatedMetadataNames('fieldMetadata');
|
||||
const relatedCacheKeysToInvalidate: WorkspaceCacheKeyName[] =
|
||||
relatedMetadataNames.map(getMetadataFlatEntityMapsKey);
|
||||
const cacheKeysToInvalidate: WorkspaceCacheKeyName[] = [
|
||||
'flatFieldMetadataMaps',
|
||||
...relatedCacheKeysToInvalidate,
|
||||
'featureFlagsMap',
|
||||
];
|
||||
|
||||
this.logger.log(
|
||||
`Invalidating caches: ${cacheKeysToInvalidate.join(' ')}`,
|
||||
);
|
||||
|
||||
await this.workspaceCacheService.invalidateAndRecompute(
|
||||
workspaceId,
|
||||
cacheKeysToInvalidate,
|
||||
);
|
||||
|
||||
await this.workspaceMetadataVersionService.incrementMetadataVersion(
|
||||
workspaceId,
|
||||
);
|
||||
this.logger.log(`Cache flushed`);
|
||||
|
||||
this.logger.log(
|
||||
`Set IS_TASK_TARGET_MIGRATED feature flag for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
this.logger.log(`Flush cache for workspace ${workspaceId}`);
|
||||
await this.workspaceCacheStorageService.flush(workspaceId);
|
||||
} catch (error) {
|
||||
if (queryRunner.isTransactionActive) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
this.logger.error(
|
||||
`Error migrating taskTarget to morph relations (rolled transaction back on ${workspaceId})`,
|
||||
error,
|
||||
);
|
||||
} else {
|
||||
this.logger.error(
|
||||
`Error migrating taskTarget to morph relations after commit on ${workspaceId}`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
|
||||
private isTwentyStandardApplicationField({
|
||||
field,
|
||||
flatApplicationMaps,
|
||||
workspaceId,
|
||||
}: {
|
||||
field: { id: string; name: string; applicationId: string };
|
||||
flatApplicationMaps: FlatApplicationCacheMaps;
|
||||
workspaceId: string;
|
||||
}): boolean {
|
||||
const application = flatApplicationMaps.byId[field.applicationId];
|
||||
|
||||
if (!application) {
|
||||
this.logger.error(
|
||||
`🟥 Application not found for field "${field.name}" (${field.id}) in workspace ${workspaceId} (applicationId: ${field.applicationId})`,
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
application.universalIdentifier ===
|
||||
TWENTY_STANDARD_APPLICATION.universalIdentifier
|
||||
);
|
||||
}
|
||||
}
|
||||
+10
@@ -6,7 +6,9 @@ import { DeleteFileRecordsCommand } from 'src/database/commands/upgrade-version-
|
||||
import { IdentifyWebhookMetadataCommand } from 'src/database/commands/upgrade-version-command/1-17/1-17-identify-webhook-metadata.command';
|
||||
import { MakeWebhookUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-17/1-17-make-webhook-universal-identifier-and-application-id-not-nullable-migration.command';
|
||||
import { MigrateAttachmentToMorphRelationsCommand } from 'src/database/commands/upgrade-version-command/1-17/1-17-migrate-attachment-to-morph-relations.command';
|
||||
import { MigrateNoteTargetToMorphRelationsCommand } from 'src/database/commands/upgrade-version-command/1-17/1-17-migrate-note-target-to-morph-relations.command';
|
||||
import { MigrateSendEmailRecipientsCommand } from 'src/database/commands/upgrade-version-command/1-17/1-17-migrate-send-email-recipients.command';
|
||||
import { MigrateTaskTargetToMorphRelationsCommand } from 'src/database/commands/upgrade-version-command/1-17/1-17-migrate-task-target-to-morph-relations.command';
|
||||
import { MigrateWorkflowCodeStepsCommand } from 'src/database/commands/upgrade-version-command/1-17/1-17-migrate-workflow-code-steps.command';
|
||||
import { SeedWorkflowV1_16Command } from 'src/database/commands/upgrade-version-command/1-17/1-17-seed-workflow-v1-16.command';
|
||||
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
|
||||
@@ -30,6 +32,8 @@ import { GlobalWorkspaceDataSourceModule } from 'src/engine/twenty-orm/global-wo
|
||||
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
|
||||
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
|
||||
import { AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objects/attachment.workspace-entity';
|
||||
import { NoteTargetWorkspaceEntity } from 'src/modules/note/standard-objects/note-target.workspace-entity';
|
||||
import { TaskTargetWorkspaceEntity } from 'src/modules/task/standard-objects/task-target.workspace-entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -40,6 +44,8 @@ import { AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objec
|
||||
FeatureFlagEntity,
|
||||
AttachmentWorkspaceEntity,
|
||||
WebhookEntity,
|
||||
NoteTargetWorkspaceEntity,
|
||||
TaskTargetWorkspaceEntity,
|
||||
FileEntity,
|
||||
LogicFunctionEntity,
|
||||
]),
|
||||
@@ -59,6 +65,8 @@ import { AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objec
|
||||
],
|
||||
providers: [
|
||||
MigrateAttachmentToMorphRelationsCommand,
|
||||
MigrateNoteTargetToMorphRelationsCommand,
|
||||
MigrateTaskTargetToMorphRelationsCommand,
|
||||
IdentifyWebhookMetadataCommand,
|
||||
MakeWebhookUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
|
||||
DeleteFileRecordsCommand,
|
||||
@@ -69,6 +77,8 @@ import { AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objec
|
||||
],
|
||||
exports: [
|
||||
MigrateAttachmentToMorphRelationsCommand,
|
||||
MigrateNoteTargetToMorphRelationsCommand,
|
||||
MigrateTaskTargetToMorphRelationsCommand,
|
||||
IdentifyWebhookMetadataCommand,
|
||||
MakeWebhookUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
|
||||
DeleteFileRecordsCommand,
|
||||
|
||||
+6
@@ -14,6 +14,8 @@ import { DeleteFileRecordsCommand } from 'src/database/commands/upgrade-version-
|
||||
import { IdentifyWebhookMetadataCommand } from 'src/database/commands/upgrade-version-command/1-17/1-17-identify-webhook-metadata.command';
|
||||
import { MakeWebhookUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-17/1-17-make-webhook-universal-identifier-and-application-id-not-nullable-migration.command';
|
||||
import { MigrateAttachmentToMorphRelationsCommand } from 'src/database/commands/upgrade-version-command/1-17/1-17-migrate-attachment-to-morph-relations.command';
|
||||
import { MigrateNoteTargetToMorphRelationsCommand } from 'src/database/commands/upgrade-version-command/1-17/1-17-migrate-note-target-to-morph-relations.command';
|
||||
import { MigrateTaskTargetToMorphRelationsCommand } from 'src/database/commands/upgrade-version-command/1-17/1-17-migrate-task-target-to-morph-relations.command';
|
||||
import { MigrateWorkflowCodeStepsCommand } from 'src/database/commands/upgrade-version-command/1-17/1-17-migrate-workflow-code-steps.command';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
@@ -38,6 +40,8 @@ export class UpgradeCommand extends UpgradeCommandRunner {
|
||||
protected readonly backfillApplicationPackageFilesCommand: BackfillApplicationPackageFilesCommand,
|
||||
protected readonly deleteFileRecordsCommand: DeleteFileRecordsCommand,
|
||||
protected readonly migrateAttachmentToMorphRelationsCommand: MigrateAttachmentToMorphRelationsCommand,
|
||||
protected readonly migrateNoteTargetToMorphRelationsCommand: MigrateNoteTargetToMorphRelationsCommand,
|
||||
protected readonly migrateTaskTargetToMorphRelationsCommand: MigrateTaskTargetToMorphRelationsCommand,
|
||||
protected readonly identifyWebhookMetadataCommand: IdentifyWebhookMetadataCommand,
|
||||
protected readonly makeWebhookUniversalIdentifierAndApplicationIdNotNullableMigrationCommand: MakeWebhookUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
|
||||
protected readonly migrateWorkflowCodeStepsCommand: MigrateWorkflowCodeStepsCommand,
|
||||
@@ -54,6 +58,8 @@ export class UpgradeCommand extends UpgradeCommandRunner {
|
||||
|
||||
const commands_1170: VersionCommands = [
|
||||
this.migrateAttachmentToMorphRelationsCommand,
|
||||
this.migrateNoteTargetToMorphRelationsCommand,
|
||||
this.migrateTaskTargetToMorphRelationsCommand,
|
||||
this.identifyWebhookMetadataCommand,
|
||||
this
|
||||
.makeWebhookUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
|
||||
|
||||
Reference in New Issue
Block a user