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,
|
||||
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
import { type MigrationInterface, type QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddEmailThreadWidgetType1775200000000
|
||||
implements MigrationInterface
|
||||
{
|
||||
name = 'AddEmailThreadWidgetType1775200000000';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TYPE "core"."pageLayoutWidget_type_enum" RENAME TO "pageLayoutWidget_type_enum_old"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE TYPE "core"."pageLayoutWidget_type_enum" AS ENUM('VIEW', 'IFRAME', 'FIELD', 'FIELDS', 'GRAPH', 'STANDALONE_RICH_TEXT', 'TIMELINE', 'TASKS', 'NOTES', 'FILES', 'EMAILS', 'CALENDAR', 'FIELD_RICH_TEXT', 'WORKFLOW', 'WORKFLOW_VERSION', 'WORKFLOW_RUN', 'FRONT_COMPONENT', 'RECORD_TABLE', 'EMAIL_THREAD')`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."pageLayoutWidget" ALTER COLUMN "type" DROP DEFAULT`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."pageLayoutWidget" ALTER COLUMN "type" TYPE "core"."pageLayoutWidget_type_enum" USING "type"::"text"::"core"."pageLayoutWidget_type_enum"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."pageLayoutWidget" ALTER COLUMN "type" SET DEFAULT 'VIEW'`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`DROP TYPE "core"."pageLayoutWidget_type_enum_old"`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`CREATE TYPE "core"."pageLayoutWidget_type_enum_old" AS ENUM('VIEW', 'IFRAME', 'FIELD', 'FIELDS', 'GRAPH', 'STANDALONE_RICH_TEXT', 'TIMELINE', 'TASKS', 'NOTES', 'FILES', 'EMAILS', 'CALENDAR', 'FIELD_RICH_TEXT', 'WORKFLOW', 'WORKFLOW_VERSION', 'WORKFLOW_RUN', 'FRONT_COMPONENT', 'RECORD_TABLE')`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."pageLayoutWidget" ALTER COLUMN "type" DROP DEFAULT`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."pageLayoutWidget" ALTER COLUMN "type" TYPE "core"."pageLayoutWidget_type_enum_old" USING "type"::"text"::"core"."pageLayoutWidget_type_enum_old"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."pageLayoutWidget" ALTER COLUMN "type" SET DEFAULT 'VIEW'`,
|
||||
);
|
||||
await queryRunner.query(`DROP TYPE "core"."pageLayoutWidget_type_enum"`);
|
||||
await queryRunner.query(
|
||||
`ALTER TYPE "core"."pageLayoutWidget_type_enum_old" RENAME TO "pageLayoutWidget_type_enum"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
+1
@@ -55,6 +55,7 @@ export enum EngineComponentKey {
|
||||
VIEW_PREVIOUS_AI_CHATS = 'VIEW_PREVIOUS_AI_CHATS',
|
||||
TRIGGER_WORKFLOW_VERSION = 'TRIGGER_WORKFLOW_VERSION',
|
||||
FRONT_COMPONENT_RENDERER = 'FRONT_COMPONENT_RENDERER',
|
||||
REPLY_TO_EMAIL_THREAD = 'REPLY_TO_EMAIL_THREAD',
|
||||
|
||||
// Deprecated keys kept for backward compatibility until migration runs
|
||||
DELETE_SINGLE_RECORD = 'DELETE_SINGLE_RECORD',
|
||||
|
||||
+6
@@ -92,6 +92,9 @@ export class FlatPageLayoutWidgetTypeValidatorService {
|
||||
RECORD_TABLE: validateSimpleRecordPageWidgetForCreation(
|
||||
WidgetConfigurationType.RECORD_TABLE,
|
||||
),
|
||||
EMAIL_THREAD: validateSimpleRecordPageWidgetForCreation(
|
||||
WidgetConfigurationType.EMAIL_THREAD,
|
||||
),
|
||||
};
|
||||
|
||||
private readonly PAGE_LAYOUT_WIDGET_TYPE_VALIDATOR_FOR_UPDATE_HASHMAP: FlatPageLayoutWidgetTypeValidatorForUpdate =
|
||||
@@ -143,6 +146,9 @@ export class FlatPageLayoutWidgetTypeValidatorService {
|
||||
RECORD_TABLE: validateSimpleRecordPageWidgetForUpdate(
|
||||
WidgetConfigurationType.RECORD_TABLE,
|
||||
),
|
||||
EMAIL_THREAD: validateSimpleRecordPageWidgetForUpdate(
|
||||
WidgetConfigurationType.EMAIL_THREAD,
|
||||
),
|
||||
};
|
||||
|
||||
public validateFlatPageLayoutWidgetTypeSpecificitiesForCreation(
|
||||
|
||||
+1
@@ -378,6 +378,7 @@ export const fromPageLayoutWidgetConfigurationToUniversalConfiguration = ({
|
||||
case WidgetConfigurationType.WORKFLOW_RUN:
|
||||
case WidgetConfigurationType.IFRAME:
|
||||
case WidgetConfigurationType.STANDALONE_RICH_TEXT:
|
||||
case WidgetConfigurationType.EMAIL_THREAD:
|
||||
return configuration;
|
||||
}
|
||||
};
|
||||
|
||||
+2
@@ -1,4 +1,5 @@
|
||||
import { AggregateChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/aggregate-chart-configuration.dto';
|
||||
import { EmailThreadConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/email-thread-configuration.dto';
|
||||
import { BarChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/bar-chart-configuration.dto';
|
||||
import { CalendarConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/calendar-configuration.dto';
|
||||
import { EmailsConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/emails-configuration.dto';
|
||||
@@ -35,6 +36,7 @@ export const ALL_WIDGET_CONFIGURATION_TYPE_VALIDATOR_BY_WIDGET_CONFIGURATION_TYP
|
||||
CALENDAR: CalendarConfigurationDTO,
|
||||
FRONT_COMPONENT: FrontComponentConfigurationDTO,
|
||||
EMAILS: EmailsConfigurationDTO,
|
||||
EMAIL_THREAD: EmailThreadConfigurationDTO,
|
||||
FIELD: FieldConfigurationDTO,
|
||||
FIELD_RICH_TEXT: FieldRichTextConfigurationDTO,
|
||||
FIELDS: FieldsConfigurationDTO,
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { IsIn, IsNotEmpty } from 'class-validator';
|
||||
import { type EmailThreadConfiguration } from 'twenty-shared/types';
|
||||
|
||||
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
|
||||
|
||||
@ObjectType('EmailThreadConfiguration')
|
||||
export class EmailThreadConfigurationDTO implements EmailThreadConfiguration {
|
||||
@Field(() => WidgetConfigurationType)
|
||||
@IsIn([WidgetConfigurationType.EMAIL_THREAD])
|
||||
@IsNotEmpty()
|
||||
configurationType: WidgetConfigurationType.EMAIL_THREAD;
|
||||
}
|
||||
+1
@@ -27,6 +27,7 @@ export enum WidgetConfigurationType {
|
||||
WORKFLOW_RUN = 'WORKFLOW_RUN',
|
||||
FRONT_COMPONENT = 'FRONT_COMPONENT',
|
||||
RECORD_TABLE = 'RECORD_TABLE',
|
||||
EMAIL_THREAD = 'EMAIL_THREAD',
|
||||
}
|
||||
export type AllGraphWidgetConfigurationType =
|
||||
| WidgetConfigurationType.AGGREGATE_CHART
|
||||
|
||||
+1
@@ -17,4 +17,5 @@ export enum WidgetType {
|
||||
WORKFLOW_RUN = 'WORKFLOW_RUN',
|
||||
FRONT_COMPONENT = 'FRONT_COMPONENT',
|
||||
RECORD_TABLE = 'RECORD_TABLE',
|
||||
EMAIL_THREAD = 'EMAIL_THREAD',
|
||||
}
|
||||
|
||||
+20
-1
@@ -3,10 +3,26 @@ type MessageThreadDataSeed = {
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
deletedAt: Date | null;
|
||||
subject: string;
|
||||
};
|
||||
|
||||
export const MESSAGE_THREAD_DATA_SEED_COLUMNS: (keyof MessageThreadDataSeed)[] =
|
||||
['id', 'createdAt', 'updatedAt', 'deletedAt'];
|
||||
['id', 'createdAt', 'updatedAt', 'deletedAt', 'subject'];
|
||||
|
||||
const EMAIL_SUBJECTS = [
|
||||
'Meeting Request',
|
||||
'Project Update',
|
||||
'Invoice for Services',
|
||||
'Thank You for the Meeting',
|
||||
'Proposal Submission',
|
||||
'Follow-up on Discussion',
|
||||
'Customer Feedback',
|
||||
'Training Session Reminder',
|
||||
'Contract Renewal',
|
||||
'Quarterly Report',
|
||||
'Partnership Opportunity',
|
||||
'Event Invitation',
|
||||
];
|
||||
|
||||
const GENERATE_MESSAGE_THREAD_IDS = (): Record<string, string> => {
|
||||
const THREAD_IDS: Record<string, string> = {};
|
||||
@@ -41,11 +57,14 @@ const GENERATE_MESSAGE_THREAD_SEEDS = (): MessageThreadDataSeed[] => {
|
||||
CREATED_DATE.getTime() + UPDATE_OFFSET * 24 * 60 * 60 * 1000,
|
||||
);
|
||||
|
||||
const TEMPLATE_INDEX = ((INDEX - 1) * 2) % EMAIL_SUBJECTS.length;
|
||||
|
||||
THREAD_SEEDS.push({
|
||||
id: MESSAGE_THREAD_DATA_SEED_IDS[`ID_${INDEX}`],
|
||||
createdAt: CREATED_DATE,
|
||||
updatedAt: UPDATED_DATE,
|
||||
deletedAt: null,
|
||||
subject: EMAIL_SUBJECTS[TEMPLATE_INDEX],
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+15
@@ -787,4 +787,19 @@ export const STANDARD_COMMAND_MENU_ITEMS = {
|
||||
engineComponentKey: EngineComponentKey.VIEW_PREVIOUS_AI_CHATS,
|
||||
hotKeys: null,
|
||||
},
|
||||
replyToEmailThread: {
|
||||
universalIdentifier: '8f015cbd-c764-434e-a6c6-bb7581b4be44',
|
||||
label: 'Reply',
|
||||
icon: 'IconArrowBackUp',
|
||||
isPinned: true,
|
||||
position: 70,
|
||||
shortLabel: 'Reply',
|
||||
availabilityType: CommandMenuItemAvailabilityType.RECORD_SELECTION,
|
||||
conditionalAvailabilityExpression: 'numberOfSelectedRecords == 1',
|
||||
availabilityObjectMetadataUniversalIdentifier:
|
||||
STANDARD_OBJECTS.messageThread.universalIdentifier,
|
||||
frontComponentUniversalIdentifier: null,
|
||||
engineComponentKey: EngineComponentKey.REPLY_TO_EMAIL_THREAD,
|
||||
hotKeys: null,
|
||||
},
|
||||
} as const;
|
||||
|
||||
+6
@@ -223,4 +223,10 @@ export const WIDGET_PROPS = {
|
||||
gridPosition: GRID_POSITIONS.FULL_WIDTH,
|
||||
position: CANVAS_LAYOUT_POSITIONS.DEFAULT,
|
||||
},
|
||||
emailThread: {
|
||||
title: 'Thread',
|
||||
type: WidgetType.EMAIL_THREAD,
|
||||
gridPosition: GRID_POSITIONS.FULL_WIDTH,
|
||||
position: VERTICAL_LIST_LAYOUT_POSITIONS.SECOND,
|
||||
},
|
||||
} as const;
|
||||
|
||||
+2
@@ -13,6 +13,7 @@ import {
|
||||
STANDARD_MESSAGE_CHANNEL_PAGE_LAYOUT_CONFIG,
|
||||
STANDARD_MESSAGE_FOLDER_PAGE_LAYOUT_CONFIG,
|
||||
STANDARD_MESSAGE_PARTICIPANT_PAGE_LAYOUT_CONFIG,
|
||||
STANDARD_MESSAGE_THREAD_PAGE_LAYOUT_CONFIG,
|
||||
STANDARD_NOTE_PAGE_LAYOUT_CONFIG,
|
||||
STANDARD_OPPORTUNITY_PAGE_LAYOUT_CONFIG,
|
||||
STANDARD_PERSON_PAGE_LAYOUT_CONFIG,
|
||||
@@ -43,6 +44,7 @@ export const STANDARD_PAGE_LAYOUTS = {
|
||||
STANDARD_MESSAGE_CHANNEL_MESSAGE_ASSOCIATION_MESSAGE_FOLDER_PAGE_LAYOUT_CONFIG,
|
||||
messageFolderRecordPage: STANDARD_MESSAGE_FOLDER_PAGE_LAYOUT_CONFIG,
|
||||
messageParticipantRecordPage: STANDARD_MESSAGE_PARTICIPANT_PAGE_LAYOUT_CONFIG,
|
||||
messageThreadRecordPage: STANDARD_MESSAGE_THREAD_PAGE_LAYOUT_CONFIG,
|
||||
noteRecordPage: STANDARD_NOTE_PAGE_LAYOUT_CONFIG,
|
||||
opportunityRecordPage: STANDARD_OPPORTUNITY_PAGE_LAYOUT_CONFIG,
|
||||
personRecordPage: STANDARD_PERSON_PAGE_LAYOUT_CONFIG,
|
||||
|
||||
+1002
-993
File diff suppressed because it is too large
Load Diff
+90
-77
@@ -1,4 +1,4 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`getStandardPageLayoutMetadataRelatedEntityIds should return standard page layout metadata related entity ids 1`] = `
|
||||
{
|
||||
@@ -321,6 +321,19 @@ exports[`getStandardPageLayoutMetadataRelatedEntityIds should return standard pa
|
||||
},
|
||||
},
|
||||
},
|
||||
"messageThreadRecordPage": {
|
||||
"id": "00000000-0000-0000-0000-000000000088",
|
||||
"tabs": {
|
||||
"home": {
|
||||
"id": "00000000-0000-0000-0000-000000000089",
|
||||
"widgets": {
|
||||
"emailThread": {
|
||||
"id": "00000000-0000-0000-0000-000000000090",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"myFirstDashboard": {
|
||||
"id": "00000000-0000-0000-0000-000000000001",
|
||||
"tabs": {
|
||||
@@ -356,299 +369,299 @@ exports[`getStandardPageLayoutMetadataRelatedEntityIds should return standard pa
|
||||
},
|
||||
},
|
||||
"noteRecordPage": {
|
||||
"id": "00000000-0000-0000-0000-000000000088",
|
||||
"id": "00000000-0000-0000-0000-000000000091",
|
||||
"tabs": {
|
||||
"files": {
|
||||
"id": "00000000-0000-0000-0000-000000000096",
|
||||
"id": "00000000-0000-0000-0000-000000000099",
|
||||
"widgets": {
|
||||
"files": {
|
||||
"id": "00000000-0000-0000-0000-000000000097",
|
||||
"id": "00000000-0000-0000-0000-000000000100",
|
||||
},
|
||||
},
|
||||
},
|
||||
"home": {
|
||||
"id": "00000000-0000-0000-0000-000000000089",
|
||||
"id": "00000000-0000-0000-0000-000000000092",
|
||||
"widgets": {
|
||||
"fields": {
|
||||
"id": "00000000-0000-0000-0000-000000000090",
|
||||
"id": "00000000-0000-0000-0000-000000000093",
|
||||
},
|
||||
"noteRichText": {
|
||||
"id": "00000000-0000-0000-0000-000000000091",
|
||||
"id": "00000000-0000-0000-0000-000000000094",
|
||||
},
|
||||
},
|
||||
},
|
||||
"note": {
|
||||
"id": "00000000-0000-0000-0000-000000000092",
|
||||
"id": "00000000-0000-0000-0000-000000000095",
|
||||
"widgets": {
|
||||
"noteRichText": {
|
||||
"id": "00000000-0000-0000-0000-000000000093",
|
||||
"id": "00000000-0000-0000-0000-000000000096",
|
||||
},
|
||||
},
|
||||
},
|
||||
"timeline": {
|
||||
"id": "00000000-0000-0000-0000-000000000094",
|
||||
"id": "00000000-0000-0000-0000-000000000097",
|
||||
"widgets": {
|
||||
"timeline": {
|
||||
"id": "00000000-0000-0000-0000-000000000095",
|
||||
"id": "00000000-0000-0000-0000-000000000098",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"opportunityRecordPage": {
|
||||
"id": "00000000-0000-0000-0000-000000000098",
|
||||
"id": "00000000-0000-0000-0000-000000000101",
|
||||
"tabs": {
|
||||
"calendar": {
|
||||
"id": "00000000-0000-0000-0000-000000000114",
|
||||
"id": "00000000-0000-0000-0000-000000000117",
|
||||
"widgets": {
|
||||
"calendar": {
|
||||
"id": "00000000-0000-0000-0000-000000000115",
|
||||
"id": "00000000-0000-0000-0000-000000000118",
|
||||
},
|
||||
},
|
||||
},
|
||||
"emails": {
|
||||
"id": "00000000-0000-0000-0000-000000000112",
|
||||
"id": "00000000-0000-0000-0000-000000000115",
|
||||
"widgets": {
|
||||
"emails": {
|
||||
"id": "00000000-0000-0000-0000-000000000113",
|
||||
"id": "00000000-0000-0000-0000-000000000116",
|
||||
},
|
||||
},
|
||||
},
|
||||
"files": {
|
||||
"id": "00000000-0000-0000-0000-000000000110",
|
||||
"id": "00000000-0000-0000-0000-000000000113",
|
||||
"widgets": {
|
||||
"files": {
|
||||
"id": "00000000-0000-0000-0000-000000000111",
|
||||
"id": "00000000-0000-0000-0000-000000000114",
|
||||
},
|
||||
},
|
||||
},
|
||||
"home": {
|
||||
"id": "00000000-0000-0000-0000-000000000099",
|
||||
"id": "00000000-0000-0000-0000-000000000102",
|
||||
"widgets": {
|
||||
"company": {
|
||||
"id": "00000000-0000-0000-0000-000000000102",
|
||||
"id": "00000000-0000-0000-0000-000000000105",
|
||||
},
|
||||
"fields": {
|
||||
"id": "00000000-0000-0000-0000-000000000100",
|
||||
},
|
||||
"owner": {
|
||||
"id": "00000000-0000-0000-0000-000000000103",
|
||||
},
|
||||
"owner": {
|
||||
"id": "00000000-0000-0000-0000-000000000106",
|
||||
},
|
||||
"pointOfContact": {
|
||||
"id": "00000000-0000-0000-0000-000000000101",
|
||||
"id": "00000000-0000-0000-0000-000000000104",
|
||||
},
|
||||
},
|
||||
},
|
||||
"notes": {
|
||||
"id": "00000000-0000-0000-0000-000000000108",
|
||||
"id": "00000000-0000-0000-0000-000000000111",
|
||||
"widgets": {
|
||||
"notes": {
|
||||
"id": "00000000-0000-0000-0000-000000000109",
|
||||
"id": "00000000-0000-0000-0000-000000000112",
|
||||
},
|
||||
},
|
||||
},
|
||||
"tasks": {
|
||||
"id": "00000000-0000-0000-0000-000000000106",
|
||||
"id": "00000000-0000-0000-0000-000000000109",
|
||||
"widgets": {
|
||||
"tasks": {
|
||||
"id": "00000000-0000-0000-0000-000000000107",
|
||||
"id": "00000000-0000-0000-0000-000000000110",
|
||||
},
|
||||
},
|
||||
},
|
||||
"timeline": {
|
||||
"id": "00000000-0000-0000-0000-000000000104",
|
||||
"id": "00000000-0000-0000-0000-000000000107",
|
||||
"widgets": {
|
||||
"timeline": {
|
||||
"id": "00000000-0000-0000-0000-000000000105",
|
||||
"id": "00000000-0000-0000-0000-000000000108",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"personRecordPage": {
|
||||
"id": "00000000-0000-0000-0000-000000000116",
|
||||
"id": "00000000-0000-0000-0000-000000000119",
|
||||
"tabs": {
|
||||
"calendar": {
|
||||
"id": "00000000-0000-0000-0000-000000000131",
|
||||
"id": "00000000-0000-0000-0000-000000000134",
|
||||
"widgets": {
|
||||
"calendar": {
|
||||
"id": "00000000-0000-0000-0000-000000000132",
|
||||
"id": "00000000-0000-0000-0000-000000000135",
|
||||
},
|
||||
},
|
||||
},
|
||||
"emails": {
|
||||
"id": "00000000-0000-0000-0000-000000000129",
|
||||
"id": "00000000-0000-0000-0000-000000000132",
|
||||
"widgets": {
|
||||
"emails": {
|
||||
"id": "00000000-0000-0000-0000-000000000130",
|
||||
"id": "00000000-0000-0000-0000-000000000133",
|
||||
},
|
||||
},
|
||||
},
|
||||
"files": {
|
||||
"id": "00000000-0000-0000-0000-000000000127",
|
||||
"id": "00000000-0000-0000-0000-000000000130",
|
||||
"widgets": {
|
||||
"files": {
|
||||
"id": "00000000-0000-0000-0000-000000000128",
|
||||
"id": "00000000-0000-0000-0000-000000000131",
|
||||
},
|
||||
},
|
||||
},
|
||||
"home": {
|
||||
"id": "00000000-0000-0000-0000-000000000117",
|
||||
"id": "00000000-0000-0000-0000-000000000120",
|
||||
"widgets": {
|
||||
"company": {
|
||||
"id": "00000000-0000-0000-0000-000000000119",
|
||||
"id": "00000000-0000-0000-0000-000000000122",
|
||||
},
|
||||
"fields": {
|
||||
"id": "00000000-0000-0000-0000-000000000118",
|
||||
"id": "00000000-0000-0000-0000-000000000121",
|
||||
},
|
||||
"pointOfContactForOpportunities": {
|
||||
"id": "00000000-0000-0000-0000-000000000120",
|
||||
"id": "00000000-0000-0000-0000-000000000123",
|
||||
},
|
||||
},
|
||||
},
|
||||
"notes": {
|
||||
"id": "00000000-0000-0000-0000-000000000125",
|
||||
"id": "00000000-0000-0000-0000-000000000128",
|
||||
"widgets": {
|
||||
"notes": {
|
||||
"id": "00000000-0000-0000-0000-000000000126",
|
||||
"id": "00000000-0000-0000-0000-000000000129",
|
||||
},
|
||||
},
|
||||
},
|
||||
"tasks": {
|
||||
"id": "00000000-0000-0000-0000-000000000123",
|
||||
"id": "00000000-0000-0000-0000-000000000126",
|
||||
"widgets": {
|
||||
"tasks": {
|
||||
"id": "00000000-0000-0000-0000-000000000124",
|
||||
"id": "00000000-0000-0000-0000-000000000127",
|
||||
},
|
||||
},
|
||||
},
|
||||
"timeline": {
|
||||
"id": "00000000-0000-0000-0000-000000000121",
|
||||
"id": "00000000-0000-0000-0000-000000000124",
|
||||
"widgets": {
|
||||
"timeline": {
|
||||
"id": "00000000-0000-0000-0000-000000000122",
|
||||
"id": "00000000-0000-0000-0000-000000000125",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"taskRecordPage": {
|
||||
"id": "00000000-0000-0000-0000-000000000133",
|
||||
"id": "00000000-0000-0000-0000-000000000136",
|
||||
"tabs": {
|
||||
"files": {
|
||||
"id": "00000000-0000-0000-0000-000000000141",
|
||||
"id": "00000000-0000-0000-0000-000000000144",
|
||||
"widgets": {
|
||||
"files": {
|
||||
"id": "00000000-0000-0000-0000-000000000142",
|
||||
"id": "00000000-0000-0000-0000-000000000145",
|
||||
},
|
||||
},
|
||||
},
|
||||
"home": {
|
||||
"id": "00000000-0000-0000-0000-000000000134",
|
||||
"id": "00000000-0000-0000-0000-000000000137",
|
||||
"widgets": {
|
||||
"fields": {
|
||||
"id": "00000000-0000-0000-0000-000000000135",
|
||||
"id": "00000000-0000-0000-0000-000000000138",
|
||||
},
|
||||
"taskRichText": {
|
||||
"id": "00000000-0000-0000-0000-000000000136",
|
||||
"id": "00000000-0000-0000-0000-000000000139",
|
||||
},
|
||||
},
|
||||
},
|
||||
"note": {
|
||||
"id": "00000000-0000-0000-0000-000000000137",
|
||||
"id": "00000000-0000-0000-0000-000000000140",
|
||||
"widgets": {
|
||||
"taskRichText": {
|
||||
"id": "00000000-0000-0000-0000-000000000138",
|
||||
"id": "00000000-0000-0000-0000-000000000141",
|
||||
},
|
||||
},
|
||||
},
|
||||
"timeline": {
|
||||
"id": "00000000-0000-0000-0000-000000000139",
|
||||
"id": "00000000-0000-0000-0000-000000000142",
|
||||
"widgets": {
|
||||
"timeline": {
|
||||
"id": "00000000-0000-0000-0000-000000000140",
|
||||
"id": "00000000-0000-0000-0000-000000000143",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"workflowAutomatedTriggerRecordPage": {
|
||||
"id": "00000000-0000-0000-0000-000000000146",
|
||||
"id": "00000000-0000-0000-0000-000000000149",
|
||||
"tabs": {
|
||||
"home": {
|
||||
"id": "00000000-0000-0000-0000-000000000147",
|
||||
"id": "00000000-0000-0000-0000-000000000150",
|
||||
"widgets": {
|
||||
"fields": {
|
||||
"id": "00000000-0000-0000-0000-000000000148",
|
||||
"id": "00000000-0000-0000-0000-000000000151",
|
||||
},
|
||||
},
|
||||
},
|
||||
"timeline": {
|
||||
"id": "00000000-0000-0000-0000-000000000149",
|
||||
"id": "00000000-0000-0000-0000-000000000152",
|
||||
"widgets": {
|
||||
"timeline": {
|
||||
"id": "00000000-0000-0000-0000-000000000150",
|
||||
"id": "00000000-0000-0000-0000-000000000153",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"workflowRecordPage": {
|
||||
"id": "00000000-0000-0000-0000-000000000143",
|
||||
"id": "00000000-0000-0000-0000-000000000146",
|
||||
"tabs": {
|
||||
"flow": {
|
||||
"id": "00000000-0000-0000-0000-000000000144",
|
||||
"id": "00000000-0000-0000-0000-000000000147",
|
||||
"widgets": {
|
||||
"workflow": {
|
||||
"id": "00000000-0000-0000-0000-000000000145",
|
||||
"id": "00000000-0000-0000-0000-000000000148",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"workflowRunRecordPage": {
|
||||
"id": "00000000-0000-0000-0000-000000000157",
|
||||
"id": "00000000-0000-0000-0000-000000000160",
|
||||
"tabs": {
|
||||
"flow": {
|
||||
"id": "00000000-0000-0000-0000-000000000161",
|
||||
"id": "00000000-0000-0000-0000-000000000164",
|
||||
"widgets": {
|
||||
"workflowRun": {
|
||||
"id": "00000000-0000-0000-0000-000000000162",
|
||||
"id": "00000000-0000-0000-0000-000000000165",
|
||||
},
|
||||
},
|
||||
},
|
||||
"home": {
|
||||
"id": "00000000-0000-0000-0000-000000000158",
|
||||
"id": "00000000-0000-0000-0000-000000000161",
|
||||
"widgets": {
|
||||
"fields": {
|
||||
"id": "00000000-0000-0000-0000-000000000159",
|
||||
"id": "00000000-0000-0000-0000-000000000162",
|
||||
},
|
||||
"workflow": {
|
||||
"id": "00000000-0000-0000-0000-000000000160",
|
||||
"id": "00000000-0000-0000-0000-000000000163",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"workflowVersionRecordPage": {
|
||||
"id": "00000000-0000-0000-0000-000000000151",
|
||||
"id": "00000000-0000-0000-0000-000000000154",
|
||||
"tabs": {
|
||||
"flow": {
|
||||
"id": "00000000-0000-0000-0000-000000000155",
|
||||
"id": "00000000-0000-0000-0000-000000000158",
|
||||
"widgets": {
|
||||
"workflowVersion": {
|
||||
"id": "00000000-0000-0000-0000-000000000156",
|
||||
"id": "00000000-0000-0000-0000-000000000159",
|
||||
},
|
||||
},
|
||||
},
|
||||
"home": {
|
||||
"id": "00000000-0000-0000-0000-000000000152",
|
||||
"id": "00000000-0000-0000-0000-000000000155",
|
||||
"widgets": {
|
||||
"fields": {
|
||||
"id": "00000000-0000-0000-0000-000000000153",
|
||||
"id": "00000000-0000-0000-0000-000000000156",
|
||||
},
|
||||
"workflow": {
|
||||
"id": "00000000-0000-0000-0000-000000000154",
|
||||
"id": "00000000-0000-0000-0000-000000000157",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
+21
-3
@@ -14,6 +14,7 @@ import {
|
||||
} from 'src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/create-standard-field-flat-metadata.util';
|
||||
import { createStandardRelationFieldFlatMetadata } from 'src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/create-standard-relation-field-flat-metadata.util';
|
||||
import { getTsVectorColumnExpressionFromFields } from 'src/engine/workspace-manager/utils/get-ts-vector-column-expression.util';
|
||||
import { SEARCH_FIELDS_FOR_MESSAGE_THREAD } from 'src/modules/messaging/common/standard-objects/message-thread.workspace-entity';
|
||||
|
||||
export const buildMessageThreadStandardFlatFieldMetadatas = ({
|
||||
now,
|
||||
@@ -183,9 +184,9 @@ export const buildMessageThreadStandardFlatFieldMetadatas = ({
|
||||
isNullable: true,
|
||||
settings: {
|
||||
generatedType: 'STORED',
|
||||
asExpression: getTsVectorColumnExpressionFromFields([
|
||||
{ name: 'id', type: FieldMetadataType.UUID },
|
||||
]),
|
||||
asExpression: getTsVectorColumnExpressionFromFields(
|
||||
SEARCH_FIELDS_FOR_MESSAGE_THREAD,
|
||||
),
|
||||
},
|
||||
},
|
||||
standardObjectMetadataRelatedEntityIds,
|
||||
@@ -193,6 +194,23 @@ export const buildMessageThreadStandardFlatFieldMetadatas = ({
|
||||
twentyStandardApplicationId,
|
||||
now,
|
||||
}),
|
||||
subject: createStandardFieldFlatMetadata({
|
||||
objectName,
|
||||
workspaceId,
|
||||
context: {
|
||||
fieldName: 'subject',
|
||||
type: FieldMetadataType.TEXT,
|
||||
label: i18nLabel(msg`Subject`),
|
||||
description: i18nLabel(msg`Subject`),
|
||||
icon: 'IconMessage',
|
||||
isNullable: true,
|
||||
isUIReadOnly: true,
|
||||
},
|
||||
standardObjectMetadataRelatedEntityIds,
|
||||
dependencyFlatEntityMaps,
|
||||
twentyStandardApplicationId,
|
||||
now,
|
||||
}),
|
||||
messages: createStandardRelationFieldFlatMetadata({
|
||||
objectName,
|
||||
workspaceId,
|
||||
|
||||
+1
-1
@@ -510,7 +510,7 @@ export const STANDARD_FLAT_OBJECT_METADATA_BUILDERS_BY_OBJECT_NAME = {
|
||||
icon: 'IconMessage',
|
||||
isSystem: true,
|
||||
isAuditLogged: false,
|
||||
labelIdentifierFieldMetadataName: 'id',
|
||||
labelIdentifierFieldMetadataName: 'subject',
|
||||
},
|
||||
workspaceId,
|
||||
standardObjectMetadataRelatedEntityIds,
|
||||
|
||||
+1
@@ -12,6 +12,7 @@ export { STANDARD_MESSAGE_CHANNEL_MESSAGE_ASSOCIATION_PAGE_LAYOUT_CONFIG } from
|
||||
export { STANDARD_MESSAGE_CHANNEL_PAGE_LAYOUT_CONFIG } from './standard-message-channel-page-layout.config';
|
||||
export { STANDARD_MESSAGE_FOLDER_PAGE_LAYOUT_CONFIG } from './standard-message-folder-page-layout.config';
|
||||
export { STANDARD_MESSAGE_PARTICIPANT_PAGE_LAYOUT_CONFIG } from './standard-message-participant-page-layout.config';
|
||||
export { STANDARD_MESSAGE_THREAD_PAGE_LAYOUT_CONFIG } from './standard-message-thread-page-layout.config';
|
||||
export { STANDARD_NOTE_PAGE_LAYOUT_CONFIG } from './standard-note-page-layout.config';
|
||||
export { STANDARD_OPPORTUNITY_PAGE_LAYOUT_CONFIG } from './standard-opportunity-page-layout.config';
|
||||
export {
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
|
||||
|
||||
import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
|
||||
import {
|
||||
TAB_PROPS,
|
||||
WIDGET_PROPS,
|
||||
} from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout-tabs.template';
|
||||
import {
|
||||
type StandardPageLayoutConfig,
|
||||
type StandardPageLayoutTabConfig,
|
||||
} from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout-config/standard-page-layout-config.type';
|
||||
|
||||
const MESSAGE_THREAD_PAGE_TABS = {
|
||||
home: {
|
||||
universalIdentifier: '20202020-f639-48a0-9a44-027cf4e3cd15',
|
||||
...TAB_PROPS.home,
|
||||
widgets: {
|
||||
emailThread: {
|
||||
universalIdentifier: '20202020-d57e-44cb-b220-69a881feb9c3',
|
||||
...WIDGET_PROPS.emailThread,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const satisfies Record<string, StandardPageLayoutTabConfig>;
|
||||
|
||||
export const STANDARD_MESSAGE_THREAD_PAGE_LAYOUT_CONFIG = {
|
||||
name: 'Default Message Thread Layout',
|
||||
type: PageLayoutType.RECORD_PAGE,
|
||||
objectUniversalIdentifier: STANDARD_OBJECTS.messageThread.universalIdentifier,
|
||||
universalIdentifier: '20202020-95bb-40eb-a699-70e7ea02a79e',
|
||||
defaultTabUniversalIdentifier: null,
|
||||
tabs: MESSAGE_THREAD_PAGE_TABS,
|
||||
} as const satisfies StandardPageLayoutConfig;
|
||||
+4
-4
@@ -61,6 +61,7 @@ const WIDGET_TYPE_TO_CONFIGURATION_TYPE: Partial<
|
||||
[WidgetType.WORKFLOW_VERSION]: WidgetConfigurationType.WORKFLOW_VERSION,
|
||||
[WidgetType.WORKFLOW_RUN]: WidgetConfigurationType.WORKFLOW_RUN,
|
||||
[WidgetType.RECORD_TABLE]: WidgetConfigurationType.RECORD_TABLE,
|
||||
[WidgetType.EMAIL_THREAD]: WidgetConfigurationType.EMAIL_THREAD,
|
||||
};
|
||||
|
||||
const RECORD_PAGE_FIELDS_VIEW_NAME_BY_OBJECT: Partial<
|
||||
@@ -135,10 +136,9 @@ const buildRecordPageWidgetConfigurations = ({
|
||||
const baseConfig = { configurationType };
|
||||
|
||||
return {
|
||||
// @ts-expect-error ignore - configurationType is validated but TS can't match to discriminated union
|
||||
configuration: baseConfig,
|
||||
// @ts-expect-error ignore - we'd need to implement for each widget type (including unused GRAPH type) to be able to match to the discriminated union
|
||||
universalConfiguration: baseConfig,
|
||||
configuration: baseConfig as AllPageLayoutWidgetConfiguration,
|
||||
universalConfiguration:
|
||||
baseConfig as CreateStandardPageLayoutWidgetContext['universalConfiguration'],
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
+27
-3
@@ -8,6 +8,18 @@ export const computeStandardMessageThreadViewFields = (
|
||||
args: Omit<CreateStandardViewFieldArgs<'messageThread'>, 'context'>,
|
||||
): Record<string, FlatViewField> => {
|
||||
return {
|
||||
allMessageThreadsSubject: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'messageThread',
|
||||
context: {
|
||||
viewName: 'allMessageThreads',
|
||||
viewFieldName: 'subject',
|
||||
fieldName: 'subject',
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 300,
|
||||
},
|
||||
}),
|
||||
allMessageThreadsMessages: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'messageThread',
|
||||
@@ -15,9 +27,21 @@ export const computeStandardMessageThreadViewFields = (
|
||||
viewName: 'allMessageThreads',
|
||||
viewFieldName: 'messages',
|
||||
fieldName: 'messages',
|
||||
position: 0,
|
||||
position: 1,
|
||||
isVisible: true,
|
||||
size: 180,
|
||||
size: 150,
|
||||
},
|
||||
}),
|
||||
allMessageThreadsUpdatedAt: createStandardViewFieldFlatMetadata({
|
||||
...args,
|
||||
objectName: 'messageThread',
|
||||
context: {
|
||||
viewName: 'allMessageThreads',
|
||||
viewFieldName: 'updatedAt',
|
||||
fieldName: 'updatedAt',
|
||||
position: 2,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
}),
|
||||
allMessageThreadsCreatedAt: createStandardViewFieldFlatMetadata({
|
||||
@@ -27,7 +51,7 @@ export const computeStandardMessageThreadViewFields = (
|
||||
viewName: 'allMessageThreads',
|
||||
viewFieldName: 'createdAt',
|
||||
fieldName: 'createdAt',
|
||||
position: 1,
|
||||
position: 3,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`ALL_UNIVERSAL_FLAT_ENTITY_FOREIGN_KEY_AGGREGATOR_PROPERTIES should match snapshot 1`] = `
|
||||
{
|
||||
|
||||
+1
@@ -361,6 +361,7 @@ export const fromUniversalConfigurationToFlatPageLayoutWidgetConfiguration = ({
|
||||
case WidgetConfigurationType.WORKFLOW_RUN:
|
||||
case WidgetConfigurationType.IFRAME:
|
||||
case WidgetConfigurationType.STANDALONE_RICH_TEXT:
|
||||
case WidgetConfigurationType.EMAIL_THREAD:
|
||||
return universalConfiguration;
|
||||
}
|
||||
};
|
||||
|
||||
+10
@@ -1,7 +1,17 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
|
||||
import { type FieldTypeAndNameMetadata } from 'src/engine/workspace-manager/utils/get-ts-vector-column-expression.util';
|
||||
import { type EntityRelation } from 'src/engine/workspace-manager/workspace-migration/types/entity-relation.interface';
|
||||
import { type MessageWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message.workspace-entity';
|
||||
|
||||
const SUBJECT_FIELD_NAME = 'subject';
|
||||
|
||||
export const SEARCH_FIELDS_FOR_MESSAGE_THREAD: FieldTypeAndNameMetadata[] = [
|
||||
{ name: SUBJECT_FIELD_NAME, type: FieldMetadataType.TEXT },
|
||||
];
|
||||
|
||||
export class MessageThreadWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
subject: string | null;
|
||||
messages: EntityRelation<MessageWorkspaceEntity[]>;
|
||||
}
|
||||
|
||||
+48
-5
@@ -25,7 +25,7 @@ type MessageAccumulator = {
|
||||
| 'text'
|
||||
| 'messageThreadId'
|
||||
>;
|
||||
threadToCreate?: Pick<MessageThreadWorkspaceEntity, 'id'>;
|
||||
threadToCreate?: Pick<MessageThreadWorkspaceEntity, 'id' | 'subject'>;
|
||||
messageChannelMessageAssociationToCreate?: Pick<
|
||||
MessageChannelMessageAssociationWorkspaceEntity,
|
||||
| 'id'
|
||||
@@ -199,10 +199,52 @@ export class MessagingMessageService {
|
||||
.map((accumulator) => accumulator.threadToCreate)
|
||||
.filter(isDefined);
|
||||
|
||||
await messageThreadRepository.insert(
|
||||
messageThreadsToCreate,
|
||||
transactionManager,
|
||||
);
|
||||
const threadSubjectUpdates = new Map<
|
||||
string,
|
||||
{ subject: string; receivedAt: number }
|
||||
>();
|
||||
|
||||
for (const message of messages) {
|
||||
const messageAccumulator = messageAccumulatorMap.get(
|
||||
message.externalId,
|
||||
);
|
||||
|
||||
if (!isDefined(messageAccumulator)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
isDefined(messageAccumulator.existingThreadInDB) &&
|
||||
isDefined(messageAccumulator.messageToCreate) &&
|
||||
isDefined(message.subject)
|
||||
) {
|
||||
const threadId = messageAccumulator.existingThreadInDB.id;
|
||||
const existing = threadSubjectUpdates.get(threadId);
|
||||
const receivedAt = message.receivedAt?.getTime() ?? 0;
|
||||
|
||||
if (!isDefined(existing) || receivedAt > existing.receivedAt) {
|
||||
threadSubjectUpdates.set(threadId, {
|
||||
subject: message.subject,
|
||||
receivedAt,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const threadsToUpsert = [
|
||||
...messageThreadsToCreate,
|
||||
...Array.from(threadSubjectUpdates.entries()).map(
|
||||
([id, { subject }]) => ({ id, subject }),
|
||||
),
|
||||
];
|
||||
|
||||
if (threadsToUpsert.length > 0) {
|
||||
await messageThreadRepository.upsert(
|
||||
threadsToUpsert,
|
||||
['id'],
|
||||
transactionManager,
|
||||
);
|
||||
}
|
||||
|
||||
const messagesToCreate = Array.from(messageAccumulatorMap.values())
|
||||
.map((accumulator) => accumulator.messageToCreate)
|
||||
@@ -451,6 +493,7 @@ export class MessagingMessageService {
|
||||
|
||||
messageAccumulator.threadToCreate = {
|
||||
id: newOrExistingMessageThreadId,
|
||||
subject: message.subject,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user