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,