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) Review in cubic --- ...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 {}