From 1fb1232a1743c3d8148875f68ba87d470f770f20 Mon Sep 17 00:00:00 2001
From: Paul Rastoin <45004772+prastoin@users.noreply.github.com>
Date: Tue, 28 Jul 2026 16:07:52 +0200
Subject: [PATCH] Message campaign backfill method sort view field (#23433)
---
...add-message-campaign-name-field.command.ts | 92 +++++++++++++++----
1 file changed, 75 insertions(+), 17 deletions(-)
diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/2-25/2-25-workspace-command-1785229970000-add-message-campaign-name-field.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/2-25/2-25-workspace-command-1785229970000-add-message-campaign-name-field.command.ts
index 7c8dc7b837..8f9481b387 100644
--- a/packages/twenty-server/src/database/commands/upgrade-version-command/2-25/2-25-workspace-command-1785229970000-add-message-campaign-name-field.command.ts
+++ b/packages/twenty-server/src/database/commands/upgrade-version-command/2-25/2-25-workspace-command-1785229970000-add-message-campaign-name-field.command.ts
@@ -13,6 +13,7 @@ import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { type FlatSearchFieldMetadata } from 'src/engine/metadata-modules/flat-search-field-metadata/types/flat-search-field-metadata.type';
+import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
import { type FlatViewField } from 'src/engine/metadata-modules/flat-view-field/types/flat-view-field.type';
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
import { computeTwentyStandardApplicationAllFlatEntityMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/twenty-standard-application-all-flat-entity-maps.constant';
@@ -56,11 +57,13 @@ export class AddMessageCampaignNameFieldCommand extends ProvisionedWorkspaceComm
const {
flatObjectMetadataMaps,
flatFieldMetadataMaps,
+ flatViewMaps,
flatViewFieldMaps,
flatSearchFieldMetadataMaps,
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
'flatObjectMetadataMaps',
'flatFieldMetadataMaps',
+ 'flatViewMaps',
'flatViewFieldMaps',
'flatSearchFieldMetadataMaps',
]);
@@ -115,6 +118,26 @@ export class AddMessageCampaignNameFieldCommand extends ProvisionedWorkspaceComm
fieldsToCreate.push(standardNameField);
}
+ // Move the label identifier from subject to name, but never clobber a
+ // user customization pointing at another field.
+ const currentLabelIdentifier =
+ campaignObjectMetadata.labelIdentifierFieldMetadataUniversalIdentifier;
+ const isNameLabelIdentifier =
+ !isDefined(currentLabelIdentifier) ||
+ currentLabelIdentifier === SUBJECT_FIELD_UNIVERSAL_IDENTIFIER ||
+ currentLabelIdentifier === NAME_FIELD_UNIVERSAL_IDENTIFIER;
+ const flatObjectMetadataToUpdate: FlatObjectMetadata[] =
+ isNameLabelIdentifier &&
+ currentLabelIdentifier !== NAME_FIELD_UNIVERSAL_IDENTIFIER
+ ? [
+ {
+ ...campaignObjectMetadata,
+ labelIdentifierFieldMetadataUniversalIdentifier:
+ NAME_FIELD_UNIVERSAL_IDENTIFIER,
+ },
+ ]
+ : [];
+
const viewFieldsToCreate: FlatViewField[] = [];
if (
@@ -136,7 +159,15 @@ export class AddMessageCampaignNameFieldCommand extends ProvisionedWorkspaceComm
);
}
- viewFieldsToCreate.push(standardNameViewField);
+ viewFieldsToCreate.push({
+ ...standardNameViewField,
+ position: this.computeNameViewFieldPosition({
+ flatViewMaps,
+ flatViewFieldMaps,
+ standardNameViewField,
+ isNameLabelIdentifier,
+ }),
+ });
}
const searchFieldMetadatasToCreate =
@@ -148,22 +179,6 @@ export class AddMessageCampaignNameFieldCommand extends ProvisionedWorkspaceComm
twentyStandardFlatApplication.universalIdentifier,
});
- // Move the label identifier from subject to name, but never clobber a
- // user customization pointing at another field.
- const currentLabelIdentifier =
- campaignObjectMetadata.labelIdentifierFieldMetadataUniversalIdentifier;
- const flatObjectMetadataToUpdate: FlatObjectMetadata[] =
- !isDefined(currentLabelIdentifier) ||
- currentLabelIdentifier === SUBJECT_FIELD_UNIVERSAL_IDENTIFIER
- ? [
- {
- ...campaignObjectMetadata,
- labelIdentifierFieldMetadataUniversalIdentifier:
- NAME_FIELD_UNIVERSAL_IDENTIFIER,
- },
- ]
- : [];
-
const totalOperationCount =
fieldsToCreate.length +
viewFieldsToCreate.length +
@@ -233,6 +248,49 @@ export class AddMessageCampaignNameFieldCommand extends ProvisionedWorkspaceComm
);
}
+ // The standard positions assume a freshly provisioned view, where name comes
+ // first and everything else is shifted by one. Existing views keep subject at
+ // position 0, so name is placed relative to the columns already there: below
+ // all of them when it becomes the label identifier (the validator requires
+ // it), above all of them otherwise (the validator forbids anything below the
+ // label identifier).
+ private computeNameViewFieldPosition({
+ flatViewMaps,
+ flatViewFieldMaps,
+ standardNameViewField,
+ isNameLabelIdentifier,
+ }: {
+ flatViewMaps: FlatEntityMaps;
+ flatViewFieldMaps: FlatEntityMaps;
+ standardNameViewField: FlatViewField;
+ isNameLabelIdentifier: boolean;
+ }): number {
+ const flatView = findFlatEntityByUniversalIdentifier({
+ flatEntityMaps: flatViewMaps,
+ universalIdentifier: standardNameViewField.viewUniversalIdentifier,
+ });
+
+ if (!isDefined(flatView)) {
+ return standardNameViewField.position;
+ }
+
+ const otherViewFieldPositions = flatView.viewFieldUniversalIdentifiers
+ .map(
+ (viewFieldUniversalIdentifier) =>
+ flatViewFieldMaps.byUniversalIdentifier[viewFieldUniversalIdentifier],
+ )
+ .filter(isDefined)
+ .map(({ position }) => position);
+
+ if (otherViewFieldPositions.length === 0) {
+ return standardNameViewField.position;
+ }
+
+ return isNameLabelIdentifier
+ ? Math.min(...otherViewFieldPositions) - 1
+ : Math.max(...otherViewFieldPositions) + 1;
+ }
+
private computeSearchFieldMetadatasToCreate({
flatSearchFieldMetadataMaps,
standardFlatSearchFieldMetadataMaps,