From 59eead238ddecd2be34094cd25686a71cbefa0ff Mon Sep 17 00:00:00 2001
From: neo773 <62795688+neo773@users.noreply.github.com>
Date: Fri, 24 Jul 2026 00:55:46 +0530
Subject: [PATCH] message list member backfill (#23176)
---
...ttingsWorkspaceUnsubscribeTopicSection.tsx | 5 +-
.../SettingsWorkspaceEmailGroupSection.tsx | 3 -
.../2-24-upgrade-version-command.module.ts | 19 +++
...ge-list-members-junction-target.command.ts | 138 ++++++++++++++++++
.../workspace-command-provider.module.ts | 2 +
5 files changed, 160 insertions(+), 7 deletions(-)
create mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/2-24/2-24-upgrade-version-command.module.ts
create mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/2-24/2-24-workspace-command-1784567000000-backfill-message-list-members-junction-target.command.ts
diff --git a/packages/twenty-front/src/modules/settings/unsubscribe-topics/components/SettingsWorkspaceUnsubscribeTopicSection.tsx b/packages/twenty-front/src/modules/settings/unsubscribe-topics/components/SettingsWorkspaceUnsubscribeTopicSection.tsx
index cdffd6b53c..6bd2fa3aa9 100644
--- a/packages/twenty-front/src/modules/settings/unsubscribe-topics/components/SettingsWorkspaceUnsubscribeTopicSection.tsx
+++ b/packages/twenty-front/src/modules/settings/unsubscribe-topics/components/SettingsWorkspaceUnsubscribeTopicSection.tsx
@@ -7,8 +7,7 @@ import {
type UnsubscribeTopicsQuery,
UnsubscribeTopicVisibility,
} from '~/generated-metadata/graphql';
-import { Pill, Status } from 'twenty-ui/data-display';
-import { IconLock } from 'twenty-ui/icon';
+import { Status } from 'twenty-ui/data-display';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
type UnsubscribeTopic = UnsubscribeTopicsQuery['unsubscribeTopics'][number];
@@ -20,13 +19,11 @@ export const SettingsWorkspaceUnsubscribeTopicSection = () => {
const title = t`Unsubscribe topics`;
const description = t`Email categories recipients can opt out of`;
- const organizationPill = ;
return (
title={title}
description={description}
- headerAdornment={organizationPill}
items={unsubscribeTopics}
columns={[
{
diff --git a/packages/twenty-front/src/modules/settings/workspace/components/SettingsWorkspaceEmailGroupSection.tsx b/packages/twenty-front/src/modules/settings/workspace/components/SettingsWorkspaceEmailGroupSection.tsx
index 2fd31749cc..76675ee4f8 100644
--- a/packages/twenty-front/src/modules/settings/workspace/components/SettingsWorkspaceEmailGroupSection.tsx
+++ b/packages/twenty-front/src/modules/settings/workspace/components/SettingsWorkspaceEmailGroupSection.tsx
@@ -6,8 +6,6 @@ import { SettingsTableListSection } from '@/settings/components/SettingsTableLis
import { SettingsWorkspaceEmailChannelDomainStatusCell } from '@/settings/workspace/components/SettingsWorkspaceEmailChannelDomainStatusCell';
import { SettingsWorkspaceEmailGroupSourceCell } from '@/settings/workspace/components/SettingsWorkspaceEmailGroupSourceCell';
import { MessageChannelType, SettingsPath } from 'twenty-shared/types';
-import { Pill } from 'twenty-ui/data-display';
-import { IconLock } from 'twenty-ui/icon';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
export const SettingsWorkspaceEmailGroupSection = () => {
@@ -23,7 +21,6 @@ export const SettingsWorkspaceEmailGroupSection = () => {
title={t`Channels`}
description={t`Addresses your workspace uses to send and receive email from shared inboxes`}
- headerAdornment={}
items={emailGroupChannels}
columns={[
{ label: t`Email`, Cell: SettingsWorkspaceEmailGroupSourceCell },
diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/2-24/2-24-upgrade-version-command.module.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/2-24/2-24-upgrade-version-command.module.ts
new file mode 100644
index 0000000000..37ea509c80
--- /dev/null
+++ b/packages/twenty-server/src/database/commands/upgrade-version-command/2-24/2-24-upgrade-version-command.module.ts
@@ -0,0 +1,19 @@
+import { Module } from '@nestjs/common';
+import { TypeOrmModule } from '@nestjs/typeorm';
+
+import { WorkspaceIteratorModule } from 'src/database/commands/command-runners/workspace-iterator.module';
+import { BackfillMessageListMembersJunctionTargetCommand } from 'src/database/commands/upgrade-version-command/2-24/2-24-workspace-command-1784567000000-backfill-message-list-members-junction-target.command';
+import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
+import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
+import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/workspace-migration-runner.module';
+
+@Module({
+ imports: [
+ TypeOrmModule.forFeature([FieldMetadataEntity]),
+ WorkspaceCacheModule,
+ WorkspaceMigrationRunnerModule,
+ WorkspaceIteratorModule,
+ ],
+ providers: [BackfillMessageListMembersJunctionTargetCommand],
+})
+export class V2_24_UpgradeVersionCommandModule {}
diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/2-24/2-24-workspace-command-1784567000000-backfill-message-list-members-junction-target.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/2-24/2-24-workspace-command-1784567000000-backfill-message-list-members-junction-target.command.ts
new file mode 100644
index 0000000000..24b46fbb52
--- /dev/null
+++ b/packages/twenty-server/src/database/commands/upgrade-version-command/2-24/2-24-workspace-command-1784567000000-backfill-message-list-members-junction-target.command.ts
@@ -0,0 +1,138 @@
+import { InjectRepository } from '@nestjs/typeorm';
+
+import { Command } from 'nest-commander';
+import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
+import { FieldMetadataType } from 'twenty-shared/types';
+import { isDefined } from 'twenty-shared/utils';
+import { Repository } from 'typeorm';
+
+import { ProvisionedWorkspaceCommandRunner } from 'src/database/commands/command-runners/provisioned-workspace.command-runner';
+import { WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service';
+import { type RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspace.command-runner';
+import { RegisteredWorkspaceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-workspace-command.decorator';
+import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
+import { isFieldMetadataSettingsOfType } from 'src/engine/metadata-modules/field-metadata/utils/is-field-metadata-settings-of-type.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 { getMetadataSerializedRelationNames } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-serialized-relation-names.util';
+import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
+import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/services/workspace-migration-runner.service';
+
+const MEMBERS_FIELD_UNIVERSAL_IDENTIFIER =
+ STANDARD_OBJECTS.messageList.fields.members.universalIdentifier;
+
+const PERSON_FIELD_UNIVERSAL_IDENTIFIER =
+ STANDARD_OBJECTS.messageListMember.fields.person.universalIdentifier;
+
+@RegisteredWorkspaceCommand('2.24.0', 1784567000000)
+@Command({
+ name: 'upgrade:2-24:backfill-message-list-members-junction-target',
+ description:
+ 'Backfill the junction target field id on messageList.members for workspaces provisioned before it was declared, so the record page renders the members junction picker.',
+})
+export class BackfillMessageListMembersJunctionTargetCommand extends ProvisionedWorkspaceCommandRunner {
+ constructor(
+ protected readonly workspaceIteratorService: WorkspaceIteratorService,
+ private readonly workspaceCacheService: WorkspaceCacheService,
+ private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
+ @InjectRepository(FieldMetadataEntity)
+ private readonly fieldMetadataRepository: Repository,
+ ) {
+ super(workspaceIteratorService);
+ }
+
+ override async runOnWorkspace({
+ workspaceId,
+ options,
+ }: RunOnWorkspaceArgs): Promise {
+ const isDryRun = options.dryRun ?? false;
+
+ const { flatFieldMetadataMaps } =
+ await this.workspaceCacheService.getOrRecompute(workspaceId, [
+ 'flatFieldMetadataMaps',
+ ]);
+
+ const membersFlatFieldMetadata =
+ flatFieldMetadataMaps.byUniversalIdentifier[
+ MEMBERS_FIELD_UNIVERSAL_IDENTIFIER
+ ];
+
+ if (!isDefined(membersFlatFieldMetadata)) {
+ this.logger.log(
+ `No messageList.members field for workspace ${workspaceId}, skipping`,
+ );
+
+ return;
+ }
+
+ const currentSettings = membersFlatFieldMetadata.settings;
+
+ if (
+ !isFieldMetadataSettingsOfType(currentSettings, FieldMetadataType.RELATION)
+ ) {
+ this.logger.warn(
+ `messageList.members has no relation settings for workspace ${workspaceId}, skipping`,
+ );
+
+ return;
+ }
+
+ if (isDefined(currentSettings.junctionTargetFieldId)) {
+ this.logger.log(
+ `messageList.members junction target already set for workspace ${workspaceId}, skipping`,
+ );
+
+ return;
+ }
+
+ const personFlatFieldMetadata =
+ flatFieldMetadataMaps.byUniversalIdentifier[
+ PERSON_FIELD_UNIVERSAL_IDENTIFIER
+ ];
+
+ if (!isDefined(personFlatFieldMetadata)) {
+ this.logger.warn(
+ `No messageListMember.person field for workspace ${workspaceId}, skipping`,
+ );
+
+ return;
+ }
+
+ const updatedSettings = {
+ ...currentSettings,
+ junctionTargetFieldId: personFlatFieldMetadata.id,
+ };
+
+ this.logger.log(
+ `${isDryRun ? '[DRY RUN] ' : ''}Backfilling messageList.members junction target for workspace ${workspaceId}`,
+ );
+
+ if (isDryRun) {
+ return;
+ }
+
+ await this.fieldMetadataRepository.update(
+ { id: membersFlatFieldMetadata.id, workspaceId },
+ { settings: updatedSettings },
+ );
+
+ const fieldMetadataRelatedNames = [
+ 'fieldMetadata',
+ ...getMetadataRelatedMetadataNames('fieldMetadata'),
+ ...getMetadataSerializedRelationNames('fieldMetadata'),
+ 'index',
+ ] as const;
+ const allFlatEntityMapsKeys = [
+ ...new Set(fieldMetadataRelatedNames.map(getMetadataFlatEntityMapsKey)),
+ ];
+
+ await this.workspaceMigrationRunnerService.invalidateCache({
+ allFlatEntityMapsKeys,
+ workspaceId,
+ });
+
+ this.logger.log(
+ `Backfilled messageList.members junction target for workspace ${workspaceId}`,
+ );
+ }
+}
diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/workspace-command-provider.module.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/workspace-command-provider.module.ts
index e374b69fff..e791b51ee8 100644
--- a/packages/twenty-server/src/database/commands/upgrade-version-command/workspace-command-provider.module.ts
+++ b/packages/twenty-server/src/database/commands/upgrade-version-command/workspace-command-provider.module.ts
@@ -24,6 +24,7 @@ import { V2_20_UpgradeVersionCommandModule } from 'src/database/commands/upgrade
import { V2_21_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/2-21/2-21-upgrade-version-command.module';
import { V2_22_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/2-22/2-22-upgrade-version-command.module';
import { V2_23_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/2-23/2-23-upgrade-version-command.module';
+import { V2_24_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/2-24/2-24-upgrade-version-command.module';
@Module({
imports: [
@@ -51,6 +52,7 @@ import { V2_23_UpgradeVersionCommandModule } from 'src/database/commands/upgrade
V2_21_UpgradeVersionCommandModule,
V2_22_UpgradeVersionCommandModule,
V2_23_UpgradeVersionCommandModule,
+ V2_24_UpgradeVersionCommandModule,
],
})
export class WorkspaceCommandProviderModule {}