Add email thread widget and message thread record page layout (#19351)
## Summary - Move email thread display from side panel to a dedicated record page with a new `EMAIL_THREAD` widget type - Add message thread as a standard object with page layout, subject field, and backfill command - Add reply-to-email command menu item for message thread records - Remove old side panel message thread components in favor of the new widget-based approach ## Type fixes - Add `EMAIL_THREAD` to `WidgetConfigurationType`, `WidgetType`, and all configuration/validator maps - Create `EmailThreadConfigurationDTO` and shared `EmailThreadConfiguration` type - Register EMAIL_THREAD in widget type validators, configuration resolvers, and standard widget mappings ## Test plan - [ ] Verify message thread record pages render with the email thread widget - [ ] Verify email thread preview navigates to the record page instead of opening side panel - [ ] Verify reply-to-email command appears for message thread records - [ ] Verify typecheck passes for both twenty-front and twenty-server - [ ] Run existing test suites to check for regressions 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+73
@@ -0,0 +1,73 @@
|
||||
import { Command } from 'nest-commander';
|
||||
|
||||
import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-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 { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util';
|
||||
|
||||
@Command({
|
||||
name: 'upgrade:1-21:backfill-message-thread-subject',
|
||||
description:
|
||||
'Backfill messageThread.subject from the most recently received message in each thread',
|
||||
})
|
||||
export class BackfillMessageThreadSubjectCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
|
||||
constructor(
|
||||
protected readonly workspaceIteratorService: WorkspaceIteratorService,
|
||||
) {
|
||||
super(workspaceIteratorService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
workspaceId,
|
||||
dataSource,
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
if (!dataSource) {
|
||||
this.logger.log(`No data source for workspace ${workspaceId}, skipping`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const schemaName = getWorkspaceSchemaName(workspaceId);
|
||||
|
||||
if (options.dryRun) {
|
||||
this.logger.log(
|
||||
`[DRY RUN] Would backfill messageThread.subject for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const columnExists = await dataSource.query(
|
||||
`SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = $1
|
||||
AND table_name = 'messageThread'
|
||||
AND column_name = 'subject'`,
|
||||
[schemaName],
|
||||
);
|
||||
|
||||
if (columnExists.length === 0) {
|
||||
this.logger.log(
|
||||
`Column "subject" does not exist yet on messageThread for workspace ${workspaceId}, skipping (will be created by sync-metadata)`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await dataSource.query(
|
||||
`UPDATE "${schemaName}"."messageThread" mt
|
||||
SET "subject" = sub.subject
|
||||
FROM (
|
||||
SELECT DISTINCT ON ("messageThreadId") "messageThreadId", "subject"
|
||||
FROM "${schemaName}"."message"
|
||||
ORDER BY "messageThreadId", "receivedAt" DESC NULLS LAST
|
||||
) sub
|
||||
WHERE mt.id = sub."messageThreadId"
|
||||
AND mt."subject" IS NULL`,
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
`Backfilled subject for ${result?.[1] ?? 0} message threads in workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
+3
@@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { WorkspaceIteratorModule } from 'src/database/commands/command-runners/workspace-iterator.module';
|
||||
import { BackfillMessageThreadSubjectCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-backfill-message-thread-subject.command';
|
||||
import { AddGlobalKeyValuePairUniqueIndexCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-add-global-key-value-pair-unique-index.command';
|
||||
import { BackfillDatasourceToWorkspaceCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-backfill-datasource-to-workspace.command';
|
||||
import { BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-backfill-page-layouts-and-fields-widget-view-fields.command';
|
||||
@@ -39,6 +40,7 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace
|
||||
providers: [
|
||||
AddGlobalKeyValuePairUniqueIndexCommand,
|
||||
BackfillDatasourceToWorkspaceCommand,
|
||||
BackfillMessageThreadSubjectCommand,
|
||||
BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand,
|
||||
DeduplicateEngineCommandsCommand,
|
||||
FixSelectAllCommandMenuItemsCommand,
|
||||
@@ -50,6 +52,7 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace
|
||||
exports: [
|
||||
AddGlobalKeyValuePairUniqueIndexCommand,
|
||||
BackfillDatasourceToWorkspaceCommand,
|
||||
BackfillMessageThreadSubjectCommand,
|
||||
BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand,
|
||||
DeduplicateEngineCommandsCommand,
|
||||
FixSelectAllCommandMenuItemsCommand,
|
||||
|
||||
+3
@@ -28,6 +28,7 @@ import { SeedCliApplicationRegistrationCommand } from 'src/database/commands/upg
|
||||
import { UpdateStandardIndexViewNamesCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-update-standard-index-view-names.command';
|
||||
import { AddGlobalKeyValuePairUniqueIndexCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-add-global-key-value-pair-unique-index.command';
|
||||
import { BackfillDatasourceToWorkspaceCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-backfill-datasource-to-workspace.command';
|
||||
import { BackfillMessageThreadSubjectCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-backfill-message-thread-subject.command';
|
||||
import { BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-backfill-page-layouts-and-fields-widget-view-fields.command';
|
||||
import { DeduplicateEngineCommandsCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-deduplicate-engine-commands.command';
|
||||
import { FixSelectAllCommandMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-fix-select-all-command-menu-items.command';
|
||||
@@ -75,6 +76,7 @@ export class UpgradeCommand extends UpgradeCommandRunner {
|
||||
// 1.21 Commands
|
||||
private readonly addGlobalKeyValuePairUniqueIndexCommand: AddGlobalKeyValuePairUniqueIndexCommand,
|
||||
private readonly backfillDatasourceToWorkspaceCommand: BackfillDatasourceToWorkspaceCommand,
|
||||
private readonly backfillMessageThreadSubjectCommand: BackfillMessageThreadSubjectCommand,
|
||||
private readonly backfillPageLayoutsAndFieldsWidgetViewFieldsCommand: BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand,
|
||||
private readonly deduplicateEngineCommandsCommand: DeduplicateEngineCommandsCommand,
|
||||
private readonly fixSelectAllCommandMenuItemsCommand: FixSelectAllCommandMenuItemsCommand,
|
||||
@@ -116,6 +118,7 @@ export class UpgradeCommand extends UpgradeCommandRunner {
|
||||
const commands_1210: VersionCommands = [
|
||||
this.addGlobalKeyValuePairUniqueIndexCommand,
|
||||
this.backfillDatasourceToWorkspaceCommand,
|
||||
this.backfillMessageThreadSubjectCommand,
|
||||
this.backfillPageLayoutsAndFieldsWidgetViewFieldsCommand,
|
||||
this.deduplicateEngineCommandsCommand,
|
||||
this.fixSelectAllCommandMenuItemsCommand,
|
||||
|
||||
Reference in New Issue
Block a user