diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-update-task-on-delete-action.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-update-task-on-delete-action.command.ts index af5541f71b..7c9fe3602c 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-update-task-on-delete-action.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/1-16-update-task-on-delete-action.command.ts @@ -47,24 +47,52 @@ export class UpdateTaskOnDeleteActionCommand extends ActiveOrSuspendedWorkspaces const isDryRun = options.dryRun ?? false; this.logger.log( - `Running UpdateTaskOnDeleteActionCommand for workspace ${workspaceId}`, + `Running UpdateTaskOnDeleteActionCommand for workspace ${workspaceId} (dryRun: ${isDryRun})`, + ); + + this.logger.log( + `[Phase 1] Starting task relation onDelete action update for workspace ${workspaceId}`, ); await this.updateTaskRelationOnDeleteAction(workspaceId, isDryRun); + this.logger.log( + `[Phase 1] Completed task relation onDelete action update for workspace ${workspaceId}`, + ); + + this.logger.log( + `[Phase 2] Starting orphaned taskTarget cleanup for workspace ${workspaceId}`, + ); + await this.deleteOrphanedTaskTargets(workspaceId, isDryRun); + + this.logger.log( + `[Phase 2] Completed orphaned taskTarget cleanup for workspace ${workspaceId}`, + ); + + this.logger.log( + `UpdateTaskOnDeleteActionCommand completed successfully for workspace ${workspaceId}`, + ); } private async updateTaskRelationOnDeleteAction( workspaceId: string, isDryRun: boolean, ): Promise { + this.logger.log( + `[Step 1/4] Fetching workspace cache for workspace ${workspaceId}`, + ); + const { flatFieldMetadataMaps, flatObjectMetadataMaps } = await this.workspaceCacheService.getOrRecompute(workspaceId, [ 'flatFieldMetadataMaps', 'flatObjectMetadataMaps', ]); + this.logger.log( + `[Step 2/4] Looking for taskTarget object metadata in workspace ${workspaceId}`, + ); + const taskTargetObjectMetadata = Object.values( flatObjectMetadataMaps.byId, ).find( @@ -80,6 +108,14 @@ export class UpdateTaskOnDeleteActionCommand extends ActiveOrSuspendedWorkspaces return; } + this.logger.log( + `[Step 2/4] Found taskTarget object metadata with id ${taskTargetObjectMetadata.id} that has ${taskTargetObjectMetadata.fieldIds.length} field(s) in workspace ${workspaceId}`, + ); + + this.logger.log( + `[Step 3/4] Looking for task field on taskTarget object in workspace ${workspaceId}`, + ); + const taskTargetFields = findManyFlatEntityByIdInFlatEntityMapsOrThrow({ flatEntityIds: taskTargetObjectMetadata.fieldIds, flatEntityMaps: flatFieldMetadataMaps, @@ -97,9 +133,13 @@ export class UpdateTaskOnDeleteActionCommand extends ActiveOrSuspendedWorkspaces return; } + this.logger.log( + `[Step 3/4] Found task field with id ${taskField.id} in workspace ${workspaceId}`, + ); + if (taskField.type !== FieldMetadataType.RELATION) { this.logger.warn( - `Task field is not a relation field in workspace ${workspaceId}`, + `Task field is not a relation field (type: ${taskField.type}) in workspace ${workspaceId}`, ); return; @@ -110,14 +150,14 @@ export class UpdateTaskOnDeleteActionCommand extends ActiveOrSuspendedWorkspaces if (taskFieldSettings?.onDelete === RelationOnDeleteAction.CASCADE) { this.logger.log( - `Task relation already has CASCADE onDelete in workspace ${workspaceId}`, + `[Step 4/4] Task relation already has CASCADE onDelete in workspace ${workspaceId}, skipping update`, ); return; } this.logger.log( - `Updating task relation onDelete from ${taskFieldSettings?.onDelete} to CASCADE in workspace ${workspaceId}`, + `[Step 4/4] Updating task relation onDelete from ${taskFieldSettings?.onDelete ?? 'undefined'} to CASCADE in workspace ${workspaceId}`, ); if (!isDryRun) { @@ -126,6 +166,10 @@ export class UpdateTaskOnDeleteActionCommand extends ActiveOrSuspendedWorkspaces onDelete: RelationOnDeleteAction.CASCADE, }; + this.logger.log( + `[Step 4/4] Calling fieldMetadataService.updateOneField for field ${taskField.id} in workspace ${workspaceId}`, + ); + try { await this.fieldMetadataService.updateOneField({ updateFieldInput: { @@ -135,18 +179,34 @@ export class UpdateTaskOnDeleteActionCommand extends ActiveOrSuspendedWorkspaces workspaceId, isSystemBuild: true, }); + + this.logger.log( + `[Step 4/4] Successfully updated task relation onDelete to CASCADE in workspace ${workspaceId}`, + ); } catch (error) { - this.logger.debug(`Error details: ${JSON.stringify(error)}`); + this.logger.error( + `[Step 4/4] Failed to update task relation onDelete in workspace ${workspaceId}`, + ); + this.logger.error( + `[Step 4/4] Field id: ${taskField.id}, current settings: ${JSON.stringify(taskFieldSettings)}`, + ); + this.logger.error( + `[Step 4/4] Target settings: ${JSON.stringify(updatedSettings)}`, + ); + this.logger.error( + `[Step 4/4] Error message: ${error instanceof Error ? error.message : String(error)}`, + ); + this.logger.error( + `[Step 4/4] Error stack: ${error instanceof Error ? error.stack : 'No stack available'}`, + ); + + this.logger.error(JSON.stringify(error, null, 2)); throw error; } - - this.logger.log( - `Successfully updated task relation onDelete to CASCADE in workspace ${workspaceId}`, - ); } else { this.logger.log( - `DRY RUN: Would update task relation onDelete to CASCADE in workspace ${workspaceId}`, + `[Step 4/4] DRY RUN: Would update task relation onDelete to CASCADE in workspace ${workspaceId}`, ); } } @@ -155,6 +215,10 @@ export class UpdateTaskOnDeleteActionCommand extends ActiveOrSuspendedWorkspaces workspaceId: string, isDryRun: boolean, ): Promise { + this.logger.log( + `[Orphan cleanup 1/3] Getting taskTarget repository for workspace ${workspaceId}`, + ); + const taskTargetRepository = await this.globalWorkspaceOrmManager.getRepository( workspaceId, @@ -162,6 +226,10 @@ export class UpdateTaskOnDeleteActionCommand extends ActiveOrSuspendedWorkspaces { shouldBypassPermissionChecks: true }, ); + this.logger.log( + `[Orphan cleanup 2/3] Searching for orphaned taskTarget records (taskId is null) in workspace ${workspaceId}`, + ); + const orphanedTaskTargets = await taskTargetRepository.find({ withDeleted: true, select: ['id'], @@ -173,10 +241,14 @@ export class UpdateTaskOnDeleteActionCommand extends ActiveOrSuspendedWorkspaces const orphanedCount = orphanedTaskTargets.length; this.logger.log( - `Found ${orphanedCount} orphaned taskTarget record(s) in workspace ${workspaceId}`, + `[Orphan cleanup 2/3] Found ${orphanedCount} orphaned taskTarget record(s) in workspace ${workspaceId}`, ); if (orphanedCount === 0) { + this.logger.log( + `[Orphan cleanup 3/3] No orphaned records to delete in workspace ${workspaceId}`, + ); + return; } @@ -184,23 +256,50 @@ export class UpdateTaskOnDeleteActionCommand extends ActiveOrSuspendedWorkspaces const orphanedIds = orphanedTaskTargets.map((record) => record.id); const batchSize = 100; + const totalBatches = Math.ceil(orphanedIds.length / batchSize); + + this.logger.log( + `[Orphan cleanup 3/3] Deleting ${orphanedCount} orphaned taskTarget record(s) in ${totalBatches} batch(es) in workspace ${workspaceId}`, + ); for (let i = 0; i < orphanedIds.length; i += batchSize) { const batch = orphanedIds.slice(i, i + batchSize); + const batchNumber = Math.floor(i / batchSize) + 1; - await taskTargetRepository - .createQueryBuilder() - .delete() - .whereInIds(batch) - .execute(); + this.logger.log( + `[Orphan cleanup 3/3] Deleting batch ${batchNumber}/${totalBatches} (${batch.length} records) in workspace ${workspaceId}`, + ); + + try { + await taskTargetRepository + .createQueryBuilder() + .delete() + .whereInIds(batch) + .execute(); + } catch (error) { + this.logger.error( + `[Orphan cleanup 3/3] Failed to delete batch ${batchNumber}/${totalBatches} in workspace ${workspaceId}`, + ); + this.logger.error( + `[Orphan cleanup 3/3] Batch record ids: ${JSON.stringify(batch)}`, + ); + this.logger.error( + `[Orphan cleanup 3/3] Error message: ${error instanceof Error ? error.message : String(error)}`, + ); + this.logger.error( + `[Orphan cleanup 3/3] Error stack: ${error instanceof Error ? error.stack : 'No stack available'}`, + ); + + throw error; + } } this.logger.log( - `Deleted ${orphanedCount} orphaned taskTarget record(s) in workspace ${workspaceId}`, + `[Orphan cleanup 3/3] Successfully deleted ${orphanedCount} orphaned taskTarget record(s) in workspace ${workspaceId}`, ); } else { this.logger.log( - `DRY RUN: Would delete ${orphanedCount} orphaned taskTarget record(s) in workspace ${workspaceId}`, + `[Orphan cleanup 3/3] DRY RUN: Would delete ${orphanedCount} orphaned taskTarget record(s) in workspace ${workspaceId}`, ); } }