refactor(emailing): rename email* objects to message* prefix
Channel-agnostic naming ahead of SMS/text support: emailList->messageTopic, emailListSubscription->messageSubscription, emailCampaign->messageBroadcast. Regenerate client-sdk and front metadata graphql types.
This commit is contained in:
@@ -398,6 +398,7 @@ enum EngineComponentKey {
|
||||
FRONT_COMPONENT_RENDERER
|
||||
REPLY_TO_EMAIL_THREAD
|
||||
COMPOSE_EMAIL
|
||||
COMPOSE_CAMPAIGN
|
||||
GO_TO_PEOPLE
|
||||
GO_TO_COMPANIES
|
||||
GO_TO_DASHBOARDS
|
||||
@@ -1463,7 +1464,7 @@ enum EmailingDomainStatus {
|
||||
}
|
||||
|
||||
type SendMessageBroadcastOutputDTO {
|
||||
campaignId: String!
|
||||
broadcastId: String!
|
||||
sentCount: Int!
|
||||
failedCount: Int!
|
||||
}
|
||||
@@ -3782,6 +3783,7 @@ input SendEmailViaDomainInput {
|
||||
|
||||
input SendMessageBroadcastInput {
|
||||
messageTopicId: String!
|
||||
recipientViewId: String
|
||||
subject: String!
|
||||
body: String!
|
||||
fromAddress: String!
|
||||
|
||||
@@ -304,7 +304,7 @@ export interface CommandMenuItem {
|
||||
__typename: 'CommandMenuItem'
|
||||
}
|
||||
|
||||
export type EngineComponentKey = 'NAVIGATE_TO_NEXT_RECORD' | 'NAVIGATE_TO_PREVIOUS_RECORD' | 'CREATE_NEW_RECORD' | 'DELETE_RECORDS' | 'RESTORE_RECORDS' | 'DESTROY_RECORDS' | 'ADD_TO_FAVORITES' | 'REMOVE_FROM_FAVORITES' | 'EXPORT_NOTE_TO_PDF' | 'EXPORT_RECORDS' | 'UPDATE_MULTIPLE_RECORDS' | 'MERGE_MULTIPLE_RECORDS' | 'IMPORT_RECORDS' | 'EXPORT_VIEW' | 'SEE_DELETED_RECORDS' | 'CREATE_NEW_VIEW' | 'HIDE_DELETED_RECORDS' | 'EDIT_RECORD_PAGE_LAYOUT' | 'EDIT_DASHBOARD_LAYOUT' | 'SAVE_DASHBOARD_LAYOUT' | 'CANCEL_DASHBOARD_LAYOUT' | 'DUPLICATE_DASHBOARD' | 'ACTIVATE_WORKFLOW' | 'DEACTIVATE_WORKFLOW' | 'DISCARD_DRAFT_WORKFLOW' | 'TEST_WORKFLOW' | 'SEE_ACTIVE_VERSION_WORKFLOW' | 'SEE_RUNS_WORKFLOW' | 'SEE_VERSIONS_WORKFLOW' | 'ADD_NODE_WORKFLOW' | 'TIDY_UP_WORKFLOW' | 'DUPLICATE_WORKFLOW' | 'SEE_VERSION_WORKFLOW_RUN' | 'SEE_WORKFLOW_WORKFLOW_RUN' | 'STOP_WORKFLOW_RUN' | 'SEE_RUNS_WORKFLOW_VERSION' | 'SEE_WORKFLOW_WORKFLOW_VERSION' | 'USE_AS_DRAFT_WORKFLOW_VERSION' | 'SEE_VERSIONS_WORKFLOW_VERSION' | 'SEARCH_RECORDS' | 'SEARCH_RECORDS_FALLBACK' | 'ASK_AI' | 'VIEW_PREVIOUS_AI_CHATS' | 'NAVIGATION' | 'TRIGGER_WORKFLOW_VERSION' | 'FRONT_COMPONENT_RENDERER' | 'REPLY_TO_EMAIL_THREAD' | 'COMPOSE_EMAIL' | 'GO_TO_PEOPLE' | 'GO_TO_COMPANIES' | 'GO_TO_DASHBOARDS' | 'GO_TO_OPPORTUNITIES' | 'GO_TO_SETTINGS' | 'GO_TO_TASKS' | 'GO_TO_NOTES' | 'GO_TO_WORKFLOWS' | 'GO_TO_RUNS' | 'DELETE_SINGLE_RECORD' | 'DELETE_MULTIPLE_RECORDS' | 'RESTORE_SINGLE_RECORD' | 'RESTORE_MULTIPLE_RECORDS' | 'DESTROY_SINGLE_RECORD' | 'DESTROY_MULTIPLE_RECORDS' | 'EXPORT_FROM_RECORD_INDEX' | 'EXPORT_FROM_RECORD_SHOW' | 'EXPORT_MULTIPLE_RECORDS'
|
||||
export type EngineComponentKey = 'NAVIGATE_TO_NEXT_RECORD' | 'NAVIGATE_TO_PREVIOUS_RECORD' | 'CREATE_NEW_RECORD' | 'DELETE_RECORDS' | 'RESTORE_RECORDS' | 'DESTROY_RECORDS' | 'ADD_TO_FAVORITES' | 'REMOVE_FROM_FAVORITES' | 'EXPORT_NOTE_TO_PDF' | 'EXPORT_RECORDS' | 'UPDATE_MULTIPLE_RECORDS' | 'MERGE_MULTIPLE_RECORDS' | 'IMPORT_RECORDS' | 'EXPORT_VIEW' | 'SEE_DELETED_RECORDS' | 'CREATE_NEW_VIEW' | 'HIDE_DELETED_RECORDS' | 'EDIT_RECORD_PAGE_LAYOUT' | 'EDIT_DASHBOARD_LAYOUT' | 'SAVE_DASHBOARD_LAYOUT' | 'CANCEL_DASHBOARD_LAYOUT' | 'DUPLICATE_DASHBOARD' | 'ACTIVATE_WORKFLOW' | 'DEACTIVATE_WORKFLOW' | 'DISCARD_DRAFT_WORKFLOW' | 'TEST_WORKFLOW' | 'SEE_ACTIVE_VERSION_WORKFLOW' | 'SEE_RUNS_WORKFLOW' | 'SEE_VERSIONS_WORKFLOW' | 'ADD_NODE_WORKFLOW' | 'TIDY_UP_WORKFLOW' | 'DUPLICATE_WORKFLOW' | 'SEE_VERSION_WORKFLOW_RUN' | 'SEE_WORKFLOW_WORKFLOW_RUN' | 'STOP_WORKFLOW_RUN' | 'SEE_RUNS_WORKFLOW_VERSION' | 'SEE_WORKFLOW_WORKFLOW_VERSION' | 'USE_AS_DRAFT_WORKFLOW_VERSION' | 'SEE_VERSIONS_WORKFLOW_VERSION' | 'SEARCH_RECORDS' | 'SEARCH_RECORDS_FALLBACK' | 'ASK_AI' | 'VIEW_PREVIOUS_AI_CHATS' | 'NAVIGATION' | 'TRIGGER_WORKFLOW_VERSION' | 'FRONT_COMPONENT_RENDERER' | 'REPLY_TO_EMAIL_THREAD' | 'COMPOSE_EMAIL' | 'COMPOSE_CAMPAIGN' | 'GO_TO_PEOPLE' | 'GO_TO_COMPANIES' | 'GO_TO_DASHBOARDS' | 'GO_TO_OPPORTUNITIES' | 'GO_TO_SETTINGS' | 'GO_TO_TASKS' | 'GO_TO_NOTES' | 'GO_TO_WORKFLOWS' | 'GO_TO_RUNS' | 'DELETE_SINGLE_RECORD' | 'DELETE_MULTIPLE_RECORDS' | 'RESTORE_SINGLE_RECORD' | 'RESTORE_MULTIPLE_RECORDS' | 'DESTROY_SINGLE_RECORD' | 'DESTROY_MULTIPLE_RECORDS' | 'EXPORT_FROM_RECORD_INDEX' | 'EXPORT_FROM_RECORD_SHOW' | 'EXPORT_MULTIPLE_RECORDS'
|
||||
|
||||
export type CommandMenuItemAvailabilityType = 'GLOBAL' | 'GLOBAL_OBJECT_CONTEXT' | 'RECORD_SELECTION' | 'FALLBACK'
|
||||
|
||||
@@ -1119,7 +1119,7 @@ export type EmailingDomainDriver = 'AWS_SES'
|
||||
export type EmailingDomainStatus = 'PENDING' | 'VERIFIED' | 'FAILED' | 'TEMPORARY_FAILURE'
|
||||
|
||||
export interface SendMessageBroadcastOutputDTO {
|
||||
campaignId: Scalars['String']
|
||||
broadcastId: Scalars['String']
|
||||
sentCount: Scalars['Int']
|
||||
failedCount: Scalars['Int']
|
||||
__typename: 'SendMessageBroadcastOutputDTO'
|
||||
@@ -4060,7 +4060,7 @@ export interface EmailingDomainGenqlSelection{
|
||||
}
|
||||
|
||||
export interface SendMessageBroadcastOutputDTOGenqlSelection{
|
||||
campaignId?: boolean | number
|
||||
broadcastId?: boolean | number
|
||||
sentCount?: boolean | number
|
||||
failedCount?: boolean | number
|
||||
__typename?: boolean | number
|
||||
@@ -6107,7 +6107,7 @@ export interface GridPositionInput {row: Scalars['Float'],column: Scalars['Float
|
||||
|
||||
export interface SendEmailViaDomainInput {emailingDomainId: Scalars['String'],to: Scalars['String'][],cc?: (Scalars['String'][] | null),bcc?: (Scalars['String'][] | null),subject: Scalars['String'],text: Scalars['String'],html?: (Scalars['String'] | null),from: Scalars['String'],replyTo?: (Scalars['String'][] | null)}
|
||||
|
||||
export interface SendMessageBroadcastInput {messageTopicId: Scalars['String'],subject: Scalars['String'],body: Scalars['String'],fromAddress: Scalars['String']}
|
||||
export interface SendMessageBroadcastInput {messageTopicId: Scalars['String'],recipientViewId?: (Scalars['String'] | null),subject: Scalars['String'],body: Scalars['String'],fromAddress: Scalars['String']}
|
||||
|
||||
export interface CreatePageLayoutWidgetInput {pageLayoutTabId: Scalars['UUID'],title: Scalars['String'],type: WidgetType,objectMetadataId?: (Scalars['UUID'] | null),gridPosition: GridPositionInput,position?: (Scalars['JSON'] | null),configuration: Scalars['JSON']}
|
||||
|
||||
@@ -8422,6 +8422,7 @@ export const enumEngineComponentKey = {
|
||||
FRONT_COMPONENT_RENDERER: 'FRONT_COMPONENT_RENDERER' as const,
|
||||
REPLY_TO_EMAIL_THREAD: 'REPLY_TO_EMAIL_THREAD' as const,
|
||||
COMPOSE_EMAIL: 'COMPOSE_EMAIL' as const,
|
||||
COMPOSE_CAMPAIGN: 'COMPOSE_CAMPAIGN' as const,
|
||||
GO_TO_PEOPLE: 'GO_TO_PEOPLE' as const,
|
||||
GO_TO_COMPANIES: 'GO_TO_COMPANIES' as const,
|
||||
GO_TO_DASHBOARDS: 'GO_TO_DASHBOARDS' as const,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
+1
-1
@@ -3,7 +3,7 @@ import { Field, Int, ObjectType } from '@nestjs/graphql';
|
||||
@ObjectType()
|
||||
export class SendMessageBroadcastOutputDTO {
|
||||
@Field(() => String)
|
||||
campaignId: string;
|
||||
broadcastId: string;
|
||||
|
||||
@Field(() => Int)
|
||||
sentCount: number;
|
||||
|
||||
+15
-1
@@ -1,6 +1,13 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { IsEmail, IsString, IsUUID, Length, MinLength } from 'class-validator';
|
||||
import {
|
||||
IsEmail,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUUID,
|
||||
Length,
|
||||
MinLength,
|
||||
} from 'class-validator';
|
||||
|
||||
@InputType()
|
||||
export class SendMessageBroadcastInput {
|
||||
@@ -8,6 +15,13 @@ export class SendMessageBroadcastInput {
|
||||
@IsUUID('4')
|
||||
messageTopicId: string;
|
||||
|
||||
// Optional Person view (segment) whose filters resolve the recipients.
|
||||
// When omitted, recipients are the people subscribed to the topic.
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsOptional()
|
||||
@IsUUID('4')
|
||||
recipientViewId?: string;
|
||||
|
||||
@Field(() => String)
|
||||
@IsString()
|
||||
@Length(1, 998)
|
||||
|
||||
+2
-1
@@ -103,9 +103,10 @@ export class EmailingDomainResolver {
|
||||
@Args('input') input: SendMessageBroadcastInput,
|
||||
@AuthWorkspace() currentWorkspace: WorkspaceEntity,
|
||||
): Promise<SendMessageBroadcastOutputDTO> {
|
||||
return this.messageBroadcastService.sendToList({
|
||||
return this.messageBroadcastService.send({
|
||||
workspaceId: currentWorkspace.id,
|
||||
messageTopicId: input.messageTopicId,
|
||||
recipientViewId: input.recipientViewId,
|
||||
subject: input.subject,
|
||||
html: input.body,
|
||||
fromAddress: input.fromAddress,
|
||||
|
||||
+75
-18
@@ -1,4 +1,5 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { ModuleRef } from '@nestjs/core';
|
||||
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { In } from 'typeorm';
|
||||
@@ -7,6 +8,8 @@ import { EmailingDomainStatus } from 'src/engine/core-modules/emailing-domain/dr
|
||||
import { EmailingDomainEntity } from 'src/engine/core-modules/emailing-domain/emailing-domain.entity';
|
||||
import { EmailingDomainSenderService } from 'src/engine/core-modules/emailing-domain/services/emailing-domain-sender.service';
|
||||
import { EmailGroupMessageCategory } from 'src/engine/core-modules/emailing-domain/types/email-group-message-category.type';
|
||||
import { FindRecordsService } from 'src/engine/core-modules/record-crud/services/find-records.service';
|
||||
import { ViewQueryParamsService } from 'src/engine/metadata-modules/view/services/view-query-params.service';
|
||||
import { InjectWorkspaceScopedRepository } from 'src/engine/twenty-orm/workspace-scoped-repository/inject-workspace-scoped-repository.decorator';
|
||||
import { WorkspaceScopedRepository } from 'src/engine/twenty-orm/workspace-scoped-repository/workspace-scoped-repository';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
@@ -16,21 +19,25 @@ import { MessageSubscriptionWorkspaceEntity } from 'src/modules/emailing/standar
|
||||
import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
|
||||
import { createHtmlToTextConverter } from 'src/modules/messaging/message-import-manager/utils/create-html-to-text-converter.util';
|
||||
|
||||
type SendCampaignArgs = {
|
||||
type SendBroadcastArgs = {
|
||||
workspaceId: string;
|
||||
messageTopicId: string;
|
||||
subject: string;
|
||||
html: string;
|
||||
fromAddress: string;
|
||||
// When set, recipients come from this Person view's filters (a segment).
|
||||
// Otherwise recipients are the people subscribed to the topic.
|
||||
recipientViewId?: string;
|
||||
};
|
||||
|
||||
type SendCampaignResult = {
|
||||
campaignId: string;
|
||||
type SendBroadcastResult = {
|
||||
broadcastId: string;
|
||||
sentCount: number;
|
||||
failedCount: number;
|
||||
};
|
||||
|
||||
const SUBSCRIBED_STATUS = 'SUBSCRIBED';
|
||||
const PERSON_OBJECT_NAME = 'person';
|
||||
|
||||
@Injectable()
|
||||
export class MessageBroadcastService {
|
||||
@@ -42,15 +49,20 @@ export class MessageBroadcastService {
|
||||
private readonly emailingDomainRepository: WorkspaceScopedRepository<EmailingDomainEntity>,
|
||||
private readonly emailingDomainSenderService: EmailingDomainSenderService,
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
// Resolved lazily to avoid a module-load cycle: the record-crud/view
|
||||
// graph (CoreCommonApiModule) cannot be eagerly imported from this
|
||||
// early-loaded core module without a require-time TDZ.
|
||||
private readonly moduleRef: ModuleRef,
|
||||
) {}
|
||||
|
||||
async sendToList({
|
||||
async send({
|
||||
workspaceId,
|
||||
messageTopicId,
|
||||
subject,
|
||||
html,
|
||||
fromAddress,
|
||||
}: SendCampaignArgs): Promise<SendCampaignResult> {
|
||||
recipientViewId,
|
||||
}: SendBroadcastArgs): Promise<SendBroadcastResult> {
|
||||
const fromDomain = fromAddress.split('@')[1]?.toLowerCase();
|
||||
|
||||
const emailingDomain = await this.emailingDomainRepository.findOne(
|
||||
@@ -68,28 +80,30 @@ export class MessageBroadcastService {
|
||||
|
||||
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
async () => {
|
||||
const recipientEmails = await this.resolveSubscribedEmails(
|
||||
workspaceId,
|
||||
messageTopicId,
|
||||
);
|
||||
const recipientEmails = isNonEmptyString(recipientViewId)
|
||||
? await this.resolveRecipientsFromView(workspaceId, recipientViewId)
|
||||
: await this.resolveSubscribedEmails(workspaceId, messageTopicId);
|
||||
|
||||
const campaignRepository =
|
||||
const broadcastRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository(
|
||||
workspaceId,
|
||||
MessageBroadcastWorkspaceEntity,
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
const { identifiers } = await campaignRepository.insert({
|
||||
const { identifiers } = await broadcastRepository.insert({
|
||||
name: subject,
|
||||
subject,
|
||||
bodyTemplate: html,
|
||||
fromAddress,
|
||||
status: 'SENDING',
|
||||
recipientSource: 'LIST',
|
||||
listId: messageTopicId,
|
||||
recipientSource: isNonEmptyString(recipientViewId)
|
||||
? 'FILTER'
|
||||
: 'LIST',
|
||||
recipientViewId: recipientViewId ?? null,
|
||||
topicId: messageTopicId,
|
||||
});
|
||||
const campaignId = identifiers[0].id;
|
||||
const broadcastId = identifiers[0].id;
|
||||
|
||||
let sentCount = 0;
|
||||
let failedCount = 0;
|
||||
@@ -113,26 +127,69 @@ export class MessageBroadcastService {
|
||||
} catch (error) {
|
||||
failedCount += 1;
|
||||
this.logger.warn(
|
||||
`Campaign ${campaignId} skipped ${recipientEmail}: ${
|
||||
`Broadcast ${broadcastId} skipped ${recipientEmail}: ${
|
||||
error instanceof Error ? error.message : String(error)
|
||||
}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await campaignRepository.update(campaignId, {
|
||||
await broadcastRepository.update(broadcastId, {
|
||||
status: 'SENT',
|
||||
sentAt: new Date(),
|
||||
sentCount,
|
||||
failedCount,
|
||||
});
|
||||
|
||||
return { campaignId, sentCount, failedCount };
|
||||
return { broadcastId, sentCount, failedCount };
|
||||
},
|
||||
buildSystemAuthContext(workspaceId),
|
||||
);
|
||||
}
|
||||
|
||||
// Resolves the recipients of a broadcast from a saved Person view (segment):
|
||||
// the view's filters are run server-side to produce the list of people.
|
||||
private async resolveRecipientsFromView(
|
||||
workspaceId: string,
|
||||
recipientViewId: string,
|
||||
): Promise<string[]> {
|
||||
const viewQueryParamsService = this.moduleRef.get(ViewQueryParamsService, {
|
||||
strict: false,
|
||||
});
|
||||
const findRecordsService = this.moduleRef.get(FindRecordsService, {
|
||||
strict: false,
|
||||
});
|
||||
|
||||
const viewParams = await viewQueryParamsService.resolveViewToQueryParams(
|
||||
recipientViewId,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (viewParams.objectNameSingular !== PERSON_OBJECT_NAME) {
|
||||
throw new Error(
|
||||
`Recipient view must target ${PERSON_OBJECT_NAME} records, got ${viewParams.objectNameSingular}`,
|
||||
);
|
||||
}
|
||||
|
||||
const output = await findRecordsService.execute({
|
||||
objectName: PERSON_OBJECT_NAME,
|
||||
filter: viewParams.filter,
|
||||
authContext: buildSystemAuthContext(workspaceId),
|
||||
});
|
||||
|
||||
if (!output.success || !output.result) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const records = output.result.records as Array<{
|
||||
emails?: { primaryEmail?: string | null };
|
||||
}>;
|
||||
|
||||
return records
|
||||
.map((record) => record.emails?.primaryEmail)
|
||||
.filter((email): email is string => isNonEmptyString(email));
|
||||
}
|
||||
|
||||
private async resolveSubscribedEmails(
|
||||
workspaceId: string,
|
||||
messageTopicId: string,
|
||||
@@ -145,7 +202,7 @@ export class MessageBroadcastService {
|
||||
);
|
||||
|
||||
const subscriptions = await subscriptionRepository.find({
|
||||
where: { listId: messageTopicId, status: SUBSCRIBED_STATUS },
|
||||
where: { topicId: messageTopicId, status: SUBSCRIBED_STATUS },
|
||||
});
|
||||
|
||||
const personIds = subscriptions.map(
|
||||
|
||||
+19
-6
@@ -286,7 +286,7 @@ export const buildMessageBroadcastStandardFlatFieldMetadatas = ({
|
||||
{
|
||||
id: '89f0301d-b168-4da3-b435-4bd5e969b604',
|
||||
value: 'LIST',
|
||||
label: i18nLabel(msg`List`),
|
||||
label: i18nLabel(msg`Topic`),
|
||||
position: 0,
|
||||
color: 'blue',
|
||||
},
|
||||
@@ -300,6 +300,19 @@ export const buildMessageBroadcastStandardFlatFieldMetadatas = ({
|
||||
],
|
||||
},
|
||||
}),
|
||||
recipientViewId: createStandardFieldFlatMetadata({
|
||||
...base,
|
||||
context: {
|
||||
fieldName: 'recipientViewId',
|
||||
type: FieldMetadataType.UUID,
|
||||
label: i18nLabel(msg`Recipient view`),
|
||||
description: i18nLabel(
|
||||
msg`The Person view whose filters resolve the recipients`,
|
||||
),
|
||||
icon: 'IconFilter',
|
||||
isNullable: true,
|
||||
},
|
||||
}),
|
||||
scheduledAt: createStandardFieldFlatMetadata({
|
||||
...base,
|
||||
context: {
|
||||
@@ -360,22 +373,22 @@ export const buildMessageBroadcastStandardFlatFieldMetadatas = ({
|
||||
defaultValue: 0,
|
||||
},
|
||||
}),
|
||||
list: createStandardRelationFieldFlatMetadata({
|
||||
topic: createStandardRelationFieldFlatMetadata({
|
||||
...base,
|
||||
context: {
|
||||
type: FieldMetadataType.RELATION,
|
||||
morphId: null,
|
||||
fieldName: 'list',
|
||||
label: i18nLabel(msg`List`),
|
||||
fieldName: 'topic',
|
||||
label: i18nLabel(msg`Topic`),
|
||||
description: i18nLabel(msg`The audience this campaign was sent to`),
|
||||
icon: 'IconMailbox',
|
||||
isNullable: true,
|
||||
targetObjectName: 'messageTopic',
|
||||
targetFieldName: 'campaigns',
|
||||
targetFieldName: 'broadcasts',
|
||||
settings: {
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
joinColumnName: 'listId',
|
||||
joinColumnName: 'topicId',
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
+22
-5
@@ -174,6 +174,23 @@ export const buildMessageSubscriptionStandardFlatFieldMetadatas = ({
|
||||
twentyStandardApplicationId,
|
||||
now,
|
||||
}),
|
||||
topicName: createStandardFieldFlatMetadata({
|
||||
objectName,
|
||||
workspaceId,
|
||||
context: {
|
||||
fieldName: 'topicName',
|
||||
type: FieldMetadataType.TEXT,
|
||||
label: i18nLabel(msg`Topic`),
|
||||
description: i18nLabel(msg`The topic this subscription is for`),
|
||||
icon: 'IconMailbox',
|
||||
isNullable: true,
|
||||
isUIReadOnly: true,
|
||||
},
|
||||
standardObjectMetadataRelatedEntityIds,
|
||||
dependencyFlatEntityMaps,
|
||||
twentyStandardApplicationId,
|
||||
now,
|
||||
}),
|
||||
status: createStandardFieldFlatMetadata({
|
||||
objectName,
|
||||
workspaceId,
|
||||
@@ -181,7 +198,7 @@ export const buildMessageSubscriptionStandardFlatFieldMetadatas = ({
|
||||
fieldName: 'status',
|
||||
type: FieldMetadataType.SELECT,
|
||||
label: i18nLabel(msg`Status`),
|
||||
description: i18nLabel(msg`Subscription status for the list`),
|
||||
description: i18nLabel(msg`Subscription status for the topic`),
|
||||
icon: 'IconMailShare',
|
||||
isNullable: false,
|
||||
isUIReadOnly: true,
|
||||
@@ -292,14 +309,14 @@ export const buildMessageSubscriptionStandardFlatFieldMetadatas = ({
|
||||
twentyStandardApplicationId,
|
||||
now,
|
||||
}),
|
||||
list: createStandardRelationFieldFlatMetadata({
|
||||
topic: createStandardRelationFieldFlatMetadata({
|
||||
objectName,
|
||||
workspaceId,
|
||||
context: {
|
||||
type: FieldMetadataType.RELATION,
|
||||
morphId: null,
|
||||
fieldName: 'list',
|
||||
label: i18nLabel(msg`List`),
|
||||
fieldName: 'topic',
|
||||
label: i18nLabel(msg`Topic`),
|
||||
description: i18nLabel(msg`The list the person is subscribed to`),
|
||||
icon: 'IconMailbox',
|
||||
isNullable: false,
|
||||
@@ -308,7 +325,7 @@ export const buildMessageSubscriptionStandardFlatFieldMetadatas = ({
|
||||
settings: {
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
onDelete: RelationOnDeleteAction.CASCADE,
|
||||
joinColumnName: 'listId',
|
||||
joinColumnName: 'topicId',
|
||||
},
|
||||
},
|
||||
standardObjectMetadataRelatedEntityIds,
|
||||
|
||||
+93
-5
@@ -201,7 +201,7 @@ export const buildMessageTopicStandardFlatFieldMetadatas = ({
|
||||
fieldName: 'name',
|
||||
type: FieldMetadataType.TEXT,
|
||||
label: i18nLabel(msg`Name`),
|
||||
description: i18nLabel(msg`The email list name`),
|
||||
description: i18nLabel(msg`The topic public display name`),
|
||||
icon: 'IconMailbox',
|
||||
isNullable: true,
|
||||
},
|
||||
@@ -210,6 +210,94 @@ export const buildMessageTopicStandardFlatFieldMetadatas = ({
|
||||
twentyStandardApplicationId,
|
||||
now,
|
||||
}),
|
||||
description: createStandardFieldFlatMetadata({
|
||||
objectName,
|
||||
workspaceId,
|
||||
context: {
|
||||
fieldName: 'description',
|
||||
type: FieldMetadataType.TEXT,
|
||||
label: i18nLabel(msg`Description`),
|
||||
description: i18nLabel(
|
||||
msg`Public description shown on the preferences page`,
|
||||
),
|
||||
icon: 'IconFileDescription',
|
||||
isNullable: true,
|
||||
},
|
||||
standardObjectMetadataRelatedEntityIds,
|
||||
dependencyFlatEntityMaps,
|
||||
twentyStandardApplicationId,
|
||||
now,
|
||||
}),
|
||||
subscriptionDefault: createStandardFieldFlatMetadata({
|
||||
objectName,
|
||||
workspaceId,
|
||||
context: {
|
||||
fieldName: 'subscriptionDefault',
|
||||
type: FieldMetadataType.SELECT,
|
||||
label: i18nLabel(msg`Defaults to`),
|
||||
description: i18nLabel(
|
||||
msg`Whether contacts are subscribed to this topic by default`,
|
||||
),
|
||||
icon: 'IconToggleRight',
|
||||
isNullable: false,
|
||||
defaultValue: "'OPT_IN'",
|
||||
options: [
|
||||
{
|
||||
id: '9c23720f-c250-44e3-bf35-d6f5a1fd2d47',
|
||||
value: 'OPT_IN',
|
||||
label: i18nLabel(msg`Opt-in`),
|
||||
position: 0,
|
||||
color: 'green',
|
||||
},
|
||||
{
|
||||
id: 'cae4baf5-b81f-4a6d-bcc3-8015ddaeed12',
|
||||
value: 'OPT_OUT',
|
||||
label: i18nLabel(msg`Opt-out`),
|
||||
position: 1,
|
||||
color: 'orange',
|
||||
},
|
||||
],
|
||||
},
|
||||
standardObjectMetadataRelatedEntityIds,
|
||||
dependencyFlatEntityMaps,
|
||||
twentyStandardApplicationId,
|
||||
now,
|
||||
}),
|
||||
visibility: createStandardFieldFlatMetadata({
|
||||
objectName,
|
||||
workspaceId,
|
||||
context: {
|
||||
fieldName: 'visibility',
|
||||
type: FieldMetadataType.SELECT,
|
||||
label: i18nLabel(msg`Visibility`),
|
||||
description: i18nLabel(
|
||||
msg`Whether the topic is shown on the public preferences page`,
|
||||
),
|
||||
icon: 'IconEye',
|
||||
isNullable: false,
|
||||
defaultValue: "'PRIVATE'",
|
||||
options: [
|
||||
{
|
||||
id: 'c919e1ad-75a9-4e3b-a8ad-05fde89e6146',
|
||||
value: 'PUBLIC',
|
||||
label: i18nLabel(msg`Public`),
|
||||
position: 0,
|
||||
color: 'blue',
|
||||
},
|
||||
{
|
||||
id: 'f8568bfb-93da-40c2-b70e-e1fbc9d503f1',
|
||||
value: 'PRIVATE',
|
||||
label: i18nLabel(msg`Private`),
|
||||
position: 1,
|
||||
color: 'gray',
|
||||
},
|
||||
],
|
||||
},
|
||||
standardObjectMetadataRelatedEntityIds,
|
||||
dependencyFlatEntityMaps,
|
||||
twentyStandardApplicationId,
|
||||
now,
|
||||
}),
|
||||
subscriptions: createStandardRelationFieldFlatMetadata({
|
||||
objectName,
|
||||
workspaceId,
|
||||
@@ -222,7 +310,7 @@ export const buildMessageTopicStandardFlatFieldMetadatas = ({
|
||||
icon: 'IconMailShare',
|
||||
isNullable: true,
|
||||
targetObjectName: 'messageSubscription',
|
||||
targetFieldName: 'list',
|
||||
targetFieldName: 'topic',
|
||||
settings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
@@ -232,20 +320,20 @@ export const buildMessageTopicStandardFlatFieldMetadatas = ({
|
||||
twentyStandardApplicationId,
|
||||
now,
|
||||
}),
|
||||
campaigns: createStandardRelationFieldFlatMetadata({
|
||||
broadcasts: createStandardRelationFieldFlatMetadata({
|
||||
objectName,
|
||||
workspaceId,
|
||||
context: {
|
||||
type: FieldMetadataType.RELATION,
|
||||
morphId: null,
|
||||
fieldName: 'campaigns',
|
||||
fieldName: 'broadcasts',
|
||||
label: i18nLabel(msg`Campaigns`),
|
||||
description: i18nLabel(msg`Campaigns sent to this list`),
|
||||
icon: 'IconSend',
|
||||
isUIReadOnly: true,
|
||||
isNullable: true,
|
||||
targetObjectName: 'messageBroadcast',
|
||||
targetFieldName: 'list',
|
||||
targetFieldName: 'topic',
|
||||
settings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
|
||||
+3
-3
@@ -17,12 +17,12 @@ export const buildMessageBroadcastStandardFlatIndexMetadatas = ({
|
||||
AllStandardObjectIndexName<'messageBroadcast'>,
|
||||
FlatIndexMetadata
|
||||
> => ({
|
||||
listIdIndex: createStandardIndexFlatMetadata({
|
||||
topicIdIndex: createStandardIndexFlatMetadata({
|
||||
objectName,
|
||||
workspaceId,
|
||||
context: {
|
||||
indexName: 'listIdIndex',
|
||||
relatedFieldNames: ['list'],
|
||||
indexName: 'topicIdIndex',
|
||||
relatedFieldNames: ['topic'],
|
||||
},
|
||||
standardObjectMetadataRelatedEntityIds,
|
||||
dependencyFlatEntityMaps,
|
||||
|
||||
+6
-6
@@ -16,24 +16,24 @@ export const buildMessageSubscriptionStandardFlatIndexMetadatas = ({
|
||||
AllStandardObjectIndexName<'messageSubscription'>,
|
||||
FlatIndexMetadata
|
||||
> => ({
|
||||
listIdIndex: createStandardIndexFlatMetadata({
|
||||
topicIdIndex: createStandardIndexFlatMetadata({
|
||||
objectName,
|
||||
workspaceId,
|
||||
context: {
|
||||
indexName: 'listIdIndex',
|
||||
relatedFieldNames: ['list'],
|
||||
indexName: 'topicIdIndex',
|
||||
relatedFieldNames: ['topic'],
|
||||
},
|
||||
standardObjectMetadataRelatedEntityIds,
|
||||
dependencyFlatEntityMaps,
|
||||
twentyStandardApplicationId,
|
||||
now,
|
||||
}),
|
||||
personListUniqueIndex: createStandardIndexFlatMetadata({
|
||||
personTopicUniqueIndex: createStandardIndexFlatMetadata({
|
||||
objectName,
|
||||
workspaceId,
|
||||
context: {
|
||||
indexName: 'personListUniqueIndex',
|
||||
relatedFieldNames: ['person', 'list'],
|
||||
indexName: 'personTopicUniqueIndex',
|
||||
relatedFieldNames: ['person', 'topic'],
|
||||
isUnique: true,
|
||||
},
|
||||
standardObjectMetadataRelatedEntityIds,
|
||||
|
||||
+3
-2
@@ -21,12 +21,13 @@ export class MessageBroadcastWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
replyTo: string | null;
|
||||
status: string;
|
||||
recipientSource: string;
|
||||
recipientViewId: string | null;
|
||||
scheduledAt: Date | null;
|
||||
sentAt: Date | null;
|
||||
sentCount: number;
|
||||
bouncedCount: number;
|
||||
failedCount: number;
|
||||
list: EntityRelation<MessageTopicWorkspaceEntity> | null;
|
||||
listId: string | null;
|
||||
topic: EntityRelation<MessageTopicWorkspaceEntity> | null;
|
||||
topicId: string | null;
|
||||
searchVector: string;
|
||||
}
|
||||
|
||||
+5
-2
@@ -4,12 +4,15 @@ import { type MessageTopicWorkspaceEntity } from 'src/modules/emailing/standard-
|
||||
import { type PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
|
||||
|
||||
export class MessageSubscriptionWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
// Denormalized copy of the topic name so the subscription is identifiable
|
||||
// (its labelIdentifier) — a relation can't be a labelIdentifier in Twenty.
|
||||
topicName: string | null;
|
||||
status: string;
|
||||
subscribedAt: Date | null;
|
||||
unsubscribedAt: Date | null;
|
||||
source: string;
|
||||
list: EntityRelation<MessageTopicWorkspaceEntity>;
|
||||
listId: string;
|
||||
topic: EntityRelation<MessageTopicWorkspaceEntity>;
|
||||
topicId: string;
|
||||
person: EntityRelation<PersonWorkspaceEntity>;
|
||||
personId: string;
|
||||
searchVector: string;
|
||||
|
||||
+4
-1
@@ -14,7 +14,10 @@ export const SEARCH_FIELDS_FOR_MESSAGE_TOPIC: FieldTypeAndNameMetadata[] = [
|
||||
|
||||
export class MessageTopicWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
name: string | null;
|
||||
description: string | null;
|
||||
subscriptionDefault: string;
|
||||
visibility: string;
|
||||
subscriptions: EntityRelation<MessageSubscriptionWorkspaceEntity[]>;
|
||||
campaigns: EntityRelation<MessageBroadcastWorkspaceEntity[]>;
|
||||
broadcasts: EntityRelation<MessageBroadcastWorkspaceEntity[]>;
|
||||
searchVector: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user