Revert "feat(messaging): message campaign delivery stats + views" (#22452) (#22627)

Revert "feat(messaging): message campaign delivery stats + views
(#22452)"

This reverts commit 2e1117d442.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22627?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
This commit is contained in:
Paul Rastoin
2026-07-07 14:17:13 +02:00
committed by GitHub
parent 54aa52d11c
commit bd8bf89653
32 changed files with 721 additions and 2323 deletions
@@ -4,13 +4,16 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { WorkspaceIteratorModule } from 'src/database/commands/command-runners/workspace-iterator.module';
import { BackfillWorkspaceCustomApplicationRegistrationCommand } from 'src/database/commands/upgrade-version-command/2-19/2-19-workspace-command-1782853718000-backfill-workspace-custom-application-registration.command';
import { BackfillSystemUniqueIndexUniversalIdentifierCommand } from 'src/database/commands/upgrade-version-command/2-19/2-19-workspace-command-1783093620000-backfill-system-unique-index-universal-identifier.command';
import { BackfillDeterministicFieldUniversalIdentifiersCommand } from 'src/database/commands/upgrade-version-command/2-19/2-19-workspace-command-1783100000000-backfill-deterministic-field-universal-identifiers.command';
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module';
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration.module';
import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/workspace-migration-runner.module';
@Module({
imports: [
@@ -18,16 +21,19 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace
TypeOrmModule.forFeature([
WorkspaceEntity,
ApplicationEntity,
FieldMetadataEntity,
IndexMetadataEntity,
]),
WorkspaceIteratorModule,
WorkspaceCacheModule,
WorkspaceMetadataVersionModule,
WorkspaceMigrationModule,
WorkspaceMigrationRunnerModule,
],
providers: [
BackfillWorkspaceCustomApplicationRegistrationCommand,
BackfillSystemUniqueIndexUniversalIdentifierCommand,
BackfillDeterministicFieldUniversalIdentifiersCommand,
],
})
export class V2_19_UpgradeVersionCommandModule {}
@@ -87,9 +87,9 @@ const isMorphOrRelationFieldMetadataType = (type: FieldMetadataType) =>
type === FieldMetadataType.RELATION ||
type === FieldMetadataType.MORPH_RELATION;
@RegisteredWorkspaceCommand('2.20.0', 1783100000000)
@RegisteredWorkspaceCommand('2.19.0', 1783100000000)
@Command({
name: 'upgrade:2-20:backfill-deterministic-field-universal-identifiers',
name: 'upgrade:2-19:backfill-deterministic-field-universal-identifiers',
description:
'Recompute the universal identifier of auto-provisioned field metadata (system fields and default relation fields) to the deterministic getFieldUniversalIdentifier derivation.',
})
@@ -8,7 +8,7 @@ import { FastInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/
// (the sequence only runs previous + current versions). down() restores it.
const TABLES = ['objectMetadata', 'fieldMetadata'] as const;
@RegisteredInstanceCommand('2.20.0', 1782991100000)
@RegisteredInstanceCommand('2.20.0', 1825000000000)
export class DropMetadataStandardOverridesColumnFastInstanceCommand
implements FastInstanceCommand
{
@@ -1,30 +0,0 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { WorkspaceIteratorModule } from 'src/database/commands/command-runners/workspace-iterator.module';
import { AddMessageCampaignStatFieldsCommand } from 'src/database/commands/upgrade-version-command/2-20/2-20-workspace-command-1783083423000-add-message-campaign-stat-fields.command';
import { CreateMessageListViewCommand } from 'src/database/commands/upgrade-version-command/2-20/2-20-workspace-command-1783083423001-create-message-list-view.command';
import { BackfillDeterministicFieldUniversalIdentifiersCommand } from 'src/database/commands/upgrade-version-command/2-20/2-20-workspace-command-1783100000000-backfill-deterministic-field-universal-identifiers.command';
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration.module';
import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/workspace-migration-runner.module';
@Module({
imports: [
ApplicationModule,
TypeOrmModule.forFeature([ApplicationEntity, FieldMetadataEntity]),
WorkspaceIteratorModule,
WorkspaceCacheModule,
WorkspaceMigrationModule,
WorkspaceMigrationRunnerModule,
],
providers: [
AddMessageCampaignStatFieldsCommand,
CreateMessageListViewCommand,
BackfillDeterministicFieldUniversalIdentifiersCommand,
],
})
export class V2_20_UpgradeVersionCommandModule {}
@@ -1,231 +0,0 @@
import { Command } from 'nest-commander';
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
import { isDefined } from 'twenty-shared/utils';
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 { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { RegisteredWorkspaceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-workspace-command.decorator';
import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { type FlatViewField } from 'src/engine/metadata-modules/flat-view-field/types/flat-view-field.type';
import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
import { type TwentyStandardAllFlatEntityMaps } from 'src/engine/workspace-manager/twenty-standard-application/types/twenty-standard-all-flat-entity-maps.type';
import { computeTwentyStandardApplicationAllFlatEntityMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/twenty-standard-application-all-flat-entity-maps.constant';
import { type WorkspaceCacheDataMap } from 'src/engine/workspace-cache/types/workspace-cache-key.type';
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service';
const CAMPAIGN = STANDARD_OBJECTS.messageCampaign;
const STAT_FIELD_UNIVERSAL_IDENTIFIERS = [
CAMPAIGN.fields.sentCount.universalIdentifier,
CAMPAIGN.fields.failedCount.universalIdentifier,
CAMPAIGN.fields.bouncedCount.universalIdentifier,
CAMPAIGN.fields.complainedCount.universalIdentifier,
];
// The allMessageCampaigns view is introduced by this feature, so existing
// workspaces have no messageCampaign view at all. Create the view and every one
// of its columns (not just the stat columns) so it materializes end to end.
const CAMPAIGN_VIEW_UNIVERSAL_IDENTIFIER =
CAMPAIGN.views.allMessageCampaigns.universalIdentifier;
const CAMPAIGN_VIEW_FIELD_UNIVERSAL_IDENTIFIERS = Object.values(
CAMPAIGN.views.allMessageCampaigns.viewFields,
).map((viewField) => viewField.universalIdentifier);
@RegisteredWorkspaceCommand('2.20.0', 1783083423000)
@Command({
name: 'upgrade:2-20:add-message-campaign-stat-fields',
description:
'Add the MessageCampaign delivery-stat fields (sent/failed/bounced/complained) and their view columns on existing workspaces',
})
export class AddMessageCampaignStatFieldsCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
constructor(
protected readonly workspaceIteratorService: WorkspaceIteratorService,
private readonly applicationService: ApplicationService,
private readonly workspaceCacheService: WorkspaceCacheService,
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
) {
super(workspaceIteratorService);
}
override async runOnWorkspace({
workspaceId,
options,
}: RunOnWorkspaceArgs): Promise<void> {
const isDryRun = options.dryRun ?? false;
const {
flatFieldMetadataMaps,
flatObjectMetadataMaps,
flatViewMaps,
flatViewFieldMaps,
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
'flatFieldMetadataMaps',
'flatObjectMetadataMaps',
'flatViewMaps',
'flatViewFieldMaps',
]);
const campaignObjectMetadata =
findFlatEntityByUniversalIdentifier<FlatObjectMetadata>({
flatEntityMaps: flatObjectMetadataMaps,
universalIdentifier: CAMPAIGN.universalIdentifier,
});
if (!isDefined(campaignObjectMetadata)) {
this.logger.log(
`messageCampaign object does not exist for workspace ${workspaceId}, skipping`,
);
return;
}
const { twentyStandardFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{ workspaceId },
);
const { allFlatEntityMaps: standardAllFlatEntityMaps } =
computeTwentyStandardApplicationAllFlatEntityMaps({
now: new Date().toISOString(),
workspaceId,
twentyStandardApplicationId: twentyStandardFlatApplication.id,
});
const fieldsToCreate = STAT_FIELD_UNIVERSAL_IDENTIFIERS.filter(
(universalIdentifier) =>
!isDefined(
flatFieldMetadataMaps.byUniversalIdentifier[universalIdentifier],
),
).map((universalIdentifier) => {
const standardField =
findFlatEntityByUniversalIdentifier<FlatFieldMetadata>({
flatEntityMaps: standardAllFlatEntityMaps.flatFieldMetadataMaps,
universalIdentifier,
});
if (!isDefined(standardField)) {
throw new Error(
`Standard application is missing messageCampaign field ${universalIdentifier}`,
);
}
return standardField;
});
const viewsToCreate = this.resolveViewsToCreate({
flatViewMaps,
standardAllFlatEntityMaps,
});
const viewFieldsToCreate = CAMPAIGN_VIEW_FIELD_UNIVERSAL_IDENTIFIERS.filter(
(universalIdentifier) =>
!isDefined(flatViewFieldMaps.byUniversalIdentifier[universalIdentifier]),
).map((universalIdentifier) => {
const standardViewField = findFlatEntityByUniversalIdentifier<FlatViewField>(
{
flatEntityMaps: standardAllFlatEntityMaps.flatViewFieldMaps,
universalIdentifier,
},
);
if (!isDefined(standardViewField)) {
throw new Error(
`Standard application is missing messageCampaign view column ${universalIdentifier}`,
);
}
return standardViewField;
});
const hasMetadataChanges =
fieldsToCreate.length > 0 ||
viewsToCreate.length > 0 ||
viewFieldsToCreate.length > 0;
if (isDryRun) {
this.logger.log(
`[DRY RUN] Workspace ${workspaceId}: ${fieldsToCreate.length} field(s), ${viewsToCreate.length} view(s), ${viewFieldsToCreate.length} view column(s)`,
);
return;
}
if (hasMetadataChanges) {
const result =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
isSystemBuild: true,
workspaceId,
applicationUniversalIdentifier:
twentyStandardFlatApplication.universalIdentifier,
allFlatEntityOperationByMetadataName: {
fieldMetadata: {
flatEntityToCreate: fieldsToCreate,
flatEntityToDelete: [],
flatEntityToUpdate: [],
},
view: {
flatEntityToCreate: viewsToCreate,
flatEntityToDelete: [],
flatEntityToUpdate: [],
},
viewField: {
flatEntityToCreate: viewFieldsToCreate,
flatEntityToDelete: [],
flatEntityToUpdate: [],
},
},
},
);
if (result.status === 'fail') {
this.logger.error(
`Failed to add messageCampaign stat fields:\n${JSON.stringify(result, null, 2)}`,
);
throw new Error(
`Failed to add messageCampaign stat fields for workspace ${workspaceId}`,
);
}
}
this.logger.log(
`Applied messageCampaign stat fields for workspace ${workspaceId}`,
);
}
private resolveViewsToCreate({
flatViewMaps,
standardAllFlatEntityMaps,
}: {
flatViewMaps: WorkspaceCacheDataMap['flatViewMaps'];
standardAllFlatEntityMaps: TwentyStandardAllFlatEntityMaps;
}): FlatView[] {
if (
isDefined(
flatViewMaps.byUniversalIdentifier[CAMPAIGN_VIEW_UNIVERSAL_IDENTIFIER],
)
) {
return [];
}
const standardView = findFlatEntityByUniversalIdentifier<FlatView>({
flatEntityMaps: standardAllFlatEntityMaps.flatViewMaps,
universalIdentifier: CAMPAIGN_VIEW_UNIVERSAL_IDENTIFIER,
});
if (!isDefined(standardView)) {
throw new Error(
`Standard application is missing messageCampaign view ${CAMPAIGN_VIEW_UNIVERSAL_IDENTIFIER}`,
);
}
return [standardView];
}
}
@@ -1,187 +0,0 @@
import { Command } from 'nest-commander';
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
import { isDefined } from 'twenty-shared/utils';
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 { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { RegisteredWorkspaceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-workspace-command.decorator';
import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { type FlatViewField } from 'src/engine/metadata-modules/flat-view-field/types/flat-view-field.type';
import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
import { type TwentyStandardAllFlatEntityMaps } from 'src/engine/workspace-manager/twenty-standard-application/types/twenty-standard-all-flat-entity-maps.type';
import { computeTwentyStandardApplicationAllFlatEntityMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/twenty-standard-application-all-flat-entity-maps.constant';
import { type WorkspaceCacheDataMap } from 'src/engine/workspace-cache/types/workspace-cache-key.type';
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service';
const LIST = STANDARD_OBJECTS.messageList;
const LIST_VIEW_UNIVERSAL_IDENTIFIER =
LIST.views.allMessageLists.universalIdentifier;
const LIST_VIEW_FIELD_UNIVERSAL_IDENTIFIERS = Object.values(
LIST.views.allMessageLists.viewFields,
).map((viewField) => viewField.universalIdentifier);
@RegisteredWorkspaceCommand('2.20.0', 1783083423001)
@Command({
name: 'upgrade:2-20:create-message-list-view',
description:
'Create the MessageList standard view and its columns on existing workspaces',
})
export class CreateMessageListViewCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
constructor(
protected readonly workspaceIteratorService: WorkspaceIteratorService,
private readonly applicationService: ApplicationService,
private readonly workspaceCacheService: WorkspaceCacheService,
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
) {
super(workspaceIteratorService);
}
override async runOnWorkspace({
workspaceId,
options,
}: RunOnWorkspaceArgs): Promise<void> {
const isDryRun = options.dryRun ?? false;
const { flatObjectMetadataMaps, flatViewMaps, flatViewFieldMaps } =
await this.workspaceCacheService.getOrRecompute(workspaceId, [
'flatObjectMetadataMaps',
'flatViewMaps',
'flatViewFieldMaps',
]);
const listObjectMetadata =
findFlatEntityByUniversalIdentifier<FlatObjectMetadata>({
flatEntityMaps: flatObjectMetadataMaps,
universalIdentifier: LIST.universalIdentifier,
});
if (!isDefined(listObjectMetadata)) {
this.logger.log(
`messageList object does not exist for workspace ${workspaceId}, skipping`,
);
return;
}
const { twentyStandardFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{ workspaceId },
);
const { allFlatEntityMaps: standardAllFlatEntityMaps } =
computeTwentyStandardApplicationAllFlatEntityMaps({
now: new Date().toISOString(),
workspaceId,
twentyStandardApplicationId: twentyStandardFlatApplication.id,
});
const viewsToCreate = this.resolveViewsToCreate({
flatViewMaps,
standardAllFlatEntityMaps,
});
const viewFieldsToCreate = LIST_VIEW_FIELD_UNIVERSAL_IDENTIFIERS.filter(
(universalIdentifier) =>
!isDefined(flatViewFieldMaps.byUniversalIdentifier[universalIdentifier]),
).map((universalIdentifier) => {
const standardViewField =
findFlatEntityByUniversalIdentifier<FlatViewField>({
flatEntityMaps: standardAllFlatEntityMaps.flatViewFieldMaps,
universalIdentifier,
});
if (!isDefined(standardViewField)) {
throw new Error(
`Standard application is missing messageList view column ${universalIdentifier}`,
);
}
return standardViewField;
});
if (viewsToCreate.length === 0 && viewFieldsToCreate.length === 0) {
this.logger.log(
`messageList view already exists for workspace ${workspaceId}, skipping`,
);
return;
}
if (isDryRun) {
this.logger.log(
`[DRY RUN] Workspace ${workspaceId}: ${viewsToCreate.length} view(s), ${viewFieldsToCreate.length} view column(s)`,
);
return;
}
const result =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
isSystemBuild: true,
workspaceId,
applicationUniversalIdentifier:
twentyStandardFlatApplication.universalIdentifier,
allFlatEntityOperationByMetadataName: {
view: {
flatEntityToCreate: viewsToCreate,
flatEntityToDelete: [],
flatEntityToUpdate: [],
},
viewField: {
flatEntityToCreate: viewFieldsToCreate,
flatEntityToDelete: [],
flatEntityToUpdate: [],
},
},
},
);
if (result.status === 'fail') {
this.logger.error(
`Failed to create messageList view:\n${JSON.stringify(result, null, 2)}`,
);
throw new Error(
`Failed to create messageList view for workspace ${workspaceId}`,
);
}
this.logger.log(`Created messageList view for workspace ${workspaceId}`);
}
private resolveViewsToCreate({
flatViewMaps,
standardAllFlatEntityMaps,
}: {
flatViewMaps: WorkspaceCacheDataMap['flatViewMaps'];
standardAllFlatEntityMaps: TwentyStandardAllFlatEntityMaps;
}): FlatView[] {
if (
isDefined(
flatViewMaps.byUniversalIdentifier[LIST_VIEW_UNIVERSAL_IDENTIFIER],
)
) {
return [];
}
const standardView = findFlatEntityByUniversalIdentifier<FlatView>({
flatEntityMaps: standardAllFlatEntityMaps.flatViewMaps,
universalIdentifier: LIST_VIEW_UNIVERSAL_IDENTIFIER,
});
if (!isDefined(standardView)) {
throw new Error(
`Standard application is missing messageList view ${LIST_VIEW_UNIVERSAL_IDENTIFIER}`,
);
}
return [standardView];
}
}
@@ -1,234 +0,0 @@
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
import { type WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service';
import { AddMessageCampaignStatFieldsCommand } from 'src/database/commands/upgrade-version-command/2-20/2-20-workspace-command-1783083423000-add-message-campaign-stat-fields.command';
import { type ApplicationService } from 'src/engine/core-modules/application/application.service';
import { computeTwentyStandardApplicationAllFlatEntityMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/twenty-standard-application-all-flat-entity-maps.constant';
import { type WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
import { type WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service';
jest.mock(
'src/engine/workspace-manager/twenty-standard-application/utils/twenty-standard-application-all-flat-entity-maps.constant',
);
const computeTwentyStandardApplicationAllFlatEntityMapsMock =
computeTwentyStandardApplicationAllFlatEntityMaps as jest.Mock;
const WORKSPACE_ID = '20202020-0000-0000-0000-000000000001';
const STANDARD_APPLICATION = {
id: '20202020-0000-0000-0000-0000000000aa',
universalIdentifier: '20202020-0000-0000-0000-0000000000bb',
};
const CAMPAIGN = STANDARD_OBJECTS.messageCampaign;
const STAT_FIELD_UNIVERSAL_IDENTIFIERS = [
CAMPAIGN.fields.sentCount.universalIdentifier,
CAMPAIGN.fields.failedCount.universalIdentifier,
CAMPAIGN.fields.bouncedCount.universalIdentifier,
CAMPAIGN.fields.complainedCount.universalIdentifier,
];
const CAMPAIGN_VIEW_UNIVERSAL_IDENTIFIER =
CAMPAIGN.views.allMessageCampaigns.universalIdentifier;
const CAMPAIGN_VIEW_FIELD_UNIVERSAL_IDENTIFIERS = Object.values(
CAMPAIGN.views.allMessageCampaigns.viewFields,
).map((viewField) => viewField.universalIdentifier);
const buildByUniversalIdentifierMap = (universalIdentifiers: string[]) => ({
byUniversalIdentifier: Object.fromEntries(
universalIdentifiers.map((universalIdentifier) => [
universalIdentifier,
{ universalIdentifier },
]),
),
});
describe('AddMessageCampaignStatFieldsCommand', () => {
let command: AddMessageCampaignStatFieldsCommand;
let findApplicationMock: jest.Mock;
let getOrRecomputeMock: jest.Mock;
let validateBuildAndRunWorkspaceMigrationMock: jest.Mock;
beforeEach(() => {
jest.clearAllMocks();
findApplicationMock = jest.fn().mockResolvedValue({
twentyStandardFlatApplication: STANDARD_APPLICATION,
});
getOrRecomputeMock = jest.fn();
validateBuildAndRunWorkspaceMigrationMock = jest
.fn()
.mockResolvedValue({ status: 'success' });
computeTwentyStandardApplicationAllFlatEntityMapsMock.mockReturnValue({
allFlatEntityMaps: {
flatFieldMetadataMaps: buildByUniversalIdentifierMap(
STAT_FIELD_UNIVERSAL_IDENTIFIERS,
),
flatViewMaps: buildByUniversalIdentifierMap([
CAMPAIGN_VIEW_UNIVERSAL_IDENTIFIER,
]),
flatViewFieldMaps: buildByUniversalIdentifierMap(
CAMPAIGN_VIEW_FIELD_UNIVERSAL_IDENTIFIERS,
),
},
});
command = new AddMessageCampaignStatFieldsCommand(
{} as WorkspaceIteratorService,
{
findWorkspaceTwentyStandardAndCustomApplicationOrThrow:
findApplicationMock,
} as unknown as ApplicationService,
{
getOrRecompute: getOrRecomputeMock,
} as unknown as WorkspaceCacheService,
{
validateBuildAndRunWorkspaceMigration:
validateBuildAndRunWorkspaceMigrationMock,
} as unknown as WorkspaceMigrationValidateBuildAndRunService,
);
});
const runOnWorkspace = (dryRun = false) =>
command.runOnWorkspace({
workspaceId: WORKSPACE_ID,
options: { dryRun },
index: 0,
total: 1,
});
const mockWorkspaceCache = ({
existingFields = [],
existingViews = [],
existingViewFields = [],
}: {
existingFields?: string[];
existingViews?: string[];
existingViewFields?: string[];
}) => {
getOrRecomputeMock.mockResolvedValue({
flatObjectMetadataMaps: buildByUniversalIdentifierMap([
CAMPAIGN.universalIdentifier,
]),
flatFieldMetadataMaps: buildByUniversalIdentifierMap(existingFields),
flatViewMaps: buildByUniversalIdentifierMap(existingViews),
flatViewFieldMaps: buildByUniversalIdentifierMap(existingViewFields),
});
};
it('creates missing stat fields, the campaign view, and all campaign view fields', async () => {
mockWorkspaceCache({});
await runOnWorkspace();
expect(validateBuildAndRunWorkspaceMigrationMock).toHaveBeenCalledWith({
isSystemBuild: true,
workspaceId: WORKSPACE_ID,
applicationUniversalIdentifier: STANDARD_APPLICATION.universalIdentifier,
allFlatEntityOperationByMetadataName: {
fieldMetadata: {
flatEntityToCreate: expect.arrayContaining(
STAT_FIELD_UNIVERSAL_IDENTIFIERS.map((universalIdentifier) =>
expect.objectContaining({ universalIdentifier }),
),
),
flatEntityToDelete: [],
flatEntityToUpdate: [],
},
view: {
flatEntityToCreate: [
expect.objectContaining({
universalIdentifier: CAMPAIGN_VIEW_UNIVERSAL_IDENTIFIER,
}),
],
flatEntityToDelete: [],
flatEntityToUpdate: [],
},
viewField: {
flatEntityToCreate: expect.arrayContaining(
CAMPAIGN_VIEW_FIELD_UNIVERSAL_IDENTIFIERS.map(
(universalIdentifier) =>
expect.objectContaining({ universalIdentifier }),
),
),
flatEntityToDelete: [],
flatEntityToUpdate: [],
},
},
});
expect(
validateBuildAndRunWorkspaceMigrationMock.mock.calls[0][0]
.allFlatEntityOperationByMetadataName.fieldMetadata.flatEntityToCreate,
).toHaveLength(STAT_FIELD_UNIVERSAL_IDENTIFIERS.length);
expect(
validateBuildAndRunWorkspaceMigrationMock.mock.calls[0][0]
.allFlatEntityOperationByMetadataName.viewField.flatEntityToCreate,
).toHaveLength(CAMPAIGN_VIEW_FIELD_UNIVERSAL_IDENTIFIERS.length);
});
it('creates only missing pieces when rerun against a partially migrated workspace', async () => {
mockWorkspaceCache({
existingFields: [CAMPAIGN.fields.sentCount.universalIdentifier],
existingViews: [CAMPAIGN_VIEW_UNIVERSAL_IDENTIFIER],
existingViewFields: [
CAMPAIGN.views.allMessageCampaigns.viewFields.subject
.universalIdentifier,
],
});
await runOnWorkspace();
const payload =
validateBuildAndRunWorkspaceMigrationMock.mock.calls[0][0]
.allFlatEntityOperationByMetadataName;
expect(payload.fieldMetadata.flatEntityToCreate).toEqual(
expect.arrayContaining(
STAT_FIELD_UNIVERSAL_IDENTIFIERS.slice(1).map((universalIdentifier) =>
expect.objectContaining({ universalIdentifier }),
),
),
);
expect(payload.fieldMetadata.flatEntityToCreate).toHaveLength(
STAT_FIELD_UNIVERSAL_IDENTIFIERS.length - 1,
);
expect(payload.view.flatEntityToCreate).toEqual([]);
expect(payload.viewField.flatEntityToCreate).toHaveLength(
CAMPAIGN_VIEW_FIELD_UNIVERSAL_IDENTIFIERS.length - 1,
);
});
it('skips the metadata migration when everything already exists', async () => {
mockWorkspaceCache({
existingFields: STAT_FIELD_UNIVERSAL_IDENTIFIERS,
existingViews: [CAMPAIGN_VIEW_UNIVERSAL_IDENTIFIER],
existingViewFields: CAMPAIGN_VIEW_FIELD_UNIVERSAL_IDENTIFIERS,
});
await runOnWorkspace();
expect(validateBuildAndRunWorkspaceMigrationMock).not.toHaveBeenCalled();
});
it('does not write metadata in dry-run mode', async () => {
mockWorkspaceCache({});
await runOnWorkspace(true);
expect(validateBuildAndRunWorkspaceMigrationMock).not.toHaveBeenCalled();
});
it('skips workspaces where the messageCampaign object is absent', async () => {
getOrRecomputeMock.mockResolvedValue({
flatObjectMetadataMaps: buildByUniversalIdentifierMap([]),
flatFieldMetadataMaps: buildByUniversalIdentifierMap([]),
flatViewMaps: buildByUniversalIdentifierMap([]),
flatViewFieldMaps: buildByUniversalIdentifierMap([]),
});
await runOnWorkspace();
expect(findApplicationMock).not.toHaveBeenCalled();
expect(validateBuildAndRunWorkspaceMigrationMock).not.toHaveBeenCalled();
});
});
@@ -1,202 +0,0 @@
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
import { type WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service';
import { CreateMessageListViewCommand } from 'src/database/commands/upgrade-version-command/2-20/2-20-workspace-command-1783083423001-create-message-list-view.command';
import { type ApplicationService } from 'src/engine/core-modules/application/application.service';
import { computeTwentyStandardApplicationAllFlatEntityMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/twenty-standard-application-all-flat-entity-maps.constant';
import { type WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
import { type WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service';
jest.mock(
'src/engine/workspace-manager/twenty-standard-application/utils/twenty-standard-application-all-flat-entity-maps.constant',
);
const computeTwentyStandardApplicationAllFlatEntityMapsMock =
computeTwentyStandardApplicationAllFlatEntityMaps as jest.Mock;
const WORKSPACE_ID = '20202020-0000-0000-0000-000000000001';
const STANDARD_APPLICATION = {
id: '20202020-0000-0000-0000-0000000000aa',
universalIdentifier: '20202020-0000-0000-0000-0000000000bb',
};
const LIST = STANDARD_OBJECTS.messageList;
const LIST_VIEW_UNIVERSAL_IDENTIFIER =
LIST.views.allMessageLists.universalIdentifier;
const LIST_VIEW_FIELD_UNIVERSAL_IDENTIFIERS = Object.values(
LIST.views.allMessageLists.viewFields,
).map((viewField) => viewField.universalIdentifier);
const buildByUniversalIdentifierMap = (universalIdentifiers: string[]) => ({
byUniversalIdentifier: Object.fromEntries(
universalIdentifiers.map((universalIdentifier) => [
universalIdentifier,
{ universalIdentifier },
]),
),
});
describe('CreateMessageListViewCommand', () => {
let command: CreateMessageListViewCommand;
let findApplicationMock: jest.Mock;
let getOrRecomputeMock: jest.Mock;
let validateBuildAndRunWorkspaceMigrationMock: jest.Mock;
beforeEach(() => {
jest.clearAllMocks();
findApplicationMock = jest.fn().mockResolvedValue({
twentyStandardFlatApplication: STANDARD_APPLICATION,
});
getOrRecomputeMock = jest.fn();
validateBuildAndRunWorkspaceMigrationMock = jest
.fn()
.mockResolvedValue({ status: 'success' });
computeTwentyStandardApplicationAllFlatEntityMapsMock.mockReturnValue({
allFlatEntityMaps: {
flatViewMaps: buildByUniversalIdentifierMap([
LIST_VIEW_UNIVERSAL_IDENTIFIER,
]),
flatViewFieldMaps: buildByUniversalIdentifierMap(
LIST_VIEW_FIELD_UNIVERSAL_IDENTIFIERS,
),
},
});
command = new CreateMessageListViewCommand(
{} as WorkspaceIteratorService,
{
findWorkspaceTwentyStandardAndCustomApplicationOrThrow:
findApplicationMock,
} as unknown as ApplicationService,
{
getOrRecompute: getOrRecomputeMock,
} as unknown as WorkspaceCacheService,
{
validateBuildAndRunWorkspaceMigration:
validateBuildAndRunWorkspaceMigrationMock,
} as unknown as WorkspaceMigrationValidateBuildAndRunService,
);
});
const runOnWorkspace = (dryRun = false) =>
command.runOnWorkspace({
workspaceId: WORKSPACE_ID,
options: { dryRun },
index: 0,
total: 1,
});
const mockWorkspaceCache = ({
existingViews = [],
existingViewFields = [],
}: {
existingViews?: string[];
existingViewFields?: string[];
}) => {
getOrRecomputeMock.mockResolvedValue({
flatObjectMetadataMaps: buildByUniversalIdentifierMap([
LIST.universalIdentifier,
]),
flatViewMaps: buildByUniversalIdentifierMap(existingViews),
flatViewFieldMaps: buildByUniversalIdentifierMap(existingViewFields),
});
};
it('creates the message list view and all view fields when missing', async () => {
mockWorkspaceCache({});
await runOnWorkspace();
expect(validateBuildAndRunWorkspaceMigrationMock).toHaveBeenCalledWith({
isSystemBuild: true,
workspaceId: WORKSPACE_ID,
applicationUniversalIdentifier: STANDARD_APPLICATION.universalIdentifier,
allFlatEntityOperationByMetadataName: {
view: {
flatEntityToCreate: [
expect.objectContaining({
universalIdentifier: LIST_VIEW_UNIVERSAL_IDENTIFIER,
}),
],
flatEntityToDelete: [],
flatEntityToUpdate: [],
},
viewField: {
flatEntityToCreate: expect.arrayContaining(
LIST_VIEW_FIELD_UNIVERSAL_IDENTIFIERS.map((universalIdentifier) =>
expect.objectContaining({ universalIdentifier }),
),
),
flatEntityToDelete: [],
flatEntityToUpdate: [],
},
},
});
expect(
validateBuildAndRunWorkspaceMigrationMock.mock.calls[0][0]
.allFlatEntityOperationByMetadataName.viewField.flatEntityToCreate,
).toHaveLength(LIST_VIEW_FIELD_UNIVERSAL_IDENTIFIERS.length);
});
it('creates missing view fields when the view already exists', async () => {
mockWorkspaceCache({
existingViews: [LIST_VIEW_UNIVERSAL_IDENTIFIER],
existingViewFields: [
LIST.views.allMessageLists.viewFields.name.universalIdentifier,
],
});
await runOnWorkspace();
const payload =
validateBuildAndRunWorkspaceMigrationMock.mock.calls[0][0]
.allFlatEntityOperationByMetadataName;
expect(payload.view.flatEntityToCreate).toEqual([]);
expect(payload.viewField.flatEntityToCreate).toEqual(
expect.arrayContaining(
LIST_VIEW_FIELD_UNIVERSAL_IDENTIFIERS.slice(1).map(
(universalIdentifier) =>
expect.objectContaining({ universalIdentifier }),
),
),
);
expect(payload.viewField.flatEntityToCreate).toHaveLength(
LIST_VIEW_FIELD_UNIVERSAL_IDENTIFIERS.length - 1,
);
});
it('does nothing when the view and view fields already exist', async () => {
mockWorkspaceCache({
existingViews: [LIST_VIEW_UNIVERSAL_IDENTIFIER],
existingViewFields: LIST_VIEW_FIELD_UNIVERSAL_IDENTIFIERS,
});
await runOnWorkspace();
expect(validateBuildAndRunWorkspaceMigrationMock).not.toHaveBeenCalled();
});
it('does not write metadata in dry-run mode', async () => {
mockWorkspaceCache({});
await runOnWorkspace(true);
expect(validateBuildAndRunWorkspaceMigrationMock).not.toHaveBeenCalled();
});
it('skips workspaces where the messageList object is absent', async () => {
getOrRecomputeMock.mockResolvedValue({
flatObjectMetadataMaps: buildByUniversalIdentifierMap([]),
flatViewMaps: buildByUniversalIdentifierMap([]),
flatViewFieldMaps: buildByUniversalIdentifierMap([]),
});
await runOnWorkspace();
expect(findApplicationMock).not.toHaveBeenCalled();
expect(validateBuildAndRunWorkspaceMigrationMock).not.toHaveBeenCalled();
});
});
@@ -1,6 +1,6 @@
import { type QueryRunner } from 'typeorm';
import { DropMetadataStandardOverridesColumnFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-20/2-20-instance-command-fast-1782991100000-drop-metadata-standard-overrides-column';
import { DropMetadataStandardOverridesColumnFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-20/2-20-instance-command-fast-1825000000000-drop-metadata-standard-overrides-column';
import { DROP_METADATA_STANDARD_OVERRIDES_COLUMN_UPGRADE_COMMAND_NAME } from 'src/database/commands/upgrade-version-command/2-20/drop-metadata-standard-overrides-column-upgrade-command-name.constant';
import { getRegisteredInstanceCommandMetadata } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator';
@@ -19,7 +19,7 @@ describe('DropMetadataStandardOverridesColumnFastInstanceCommand', () => {
expect(metadata).toEqual({
version: '2.20.0',
timestamp: 1782991100000,
timestamp: 1825000000000,
type: 'fast',
});
});
@@ -1,4 +1,4 @@
// Registered name of the 2.20 drop command. Wire to @WasRemovedInUpgrade on the
// standardOverrides columns once 2.20 is current.
export const DROP_METADATA_STANDARD_OVERRIDES_COLUMN_UPGRADE_COMMAND_NAME =
'2.20.0_DropMetadataStandardOverridesColumnFastInstanceCommand_1782991100000';
'2.20.0_DropMetadataStandardOverridesColumnFastInstanceCommand_1825000000000';
@@ -93,7 +93,7 @@ import { AddPendingQuestionMessageIdToAgentChatThreadFastInstanceCommand } from
import { AddWorkspaceDiscoverabilityToWorkspaceFastInstanceCommand } from './2-19/2-19-instance-command-fast-1783004140000-add-workspace-discoverability-to-workspace';
import { AddStatusToFileFastInstanceCommand } from './2-19/2-19-instance-command-fast-1783082964705-add-status-to-file';
import { AddPendingMimeCheckToFileFastInstanceCommand } from './2-19/2-19-instance-command-fast-1783094691548-add-pending-mime-check-to-file';
import { DropMetadataStandardOverridesColumnFastInstanceCommand } from './2-20/2-20-instance-command-fast-1782991100000-drop-metadata-standard-overrides-column';
import { DropMetadataStandardOverridesColumnFastInstanceCommand } from './2-20/2-20-instance-command-fast-1825000000000-drop-metadata-standard-overrides-column';
import { AddLogoToApplicationRegistrationFastInstanceCommand } from './2-19/2-19-instance-command-fast-1783069672191-add-logo-to-application-registration';
import { BackfillLogoOnApplicationRegistrationSlowInstanceCommand } from './2-19/2-19-instance-command-slow-1783069673191-backfill-logo-on-application-registration';
import { AddDisplayFieldsToApplicationRegistrationFastInstanceCommand } from './2-19/2-19-instance-command-fast-1783073776590-add-display-fields-to-application-registration';
@@ -20,7 +20,6 @@ import { V2_16_UpgradeVersionCommandModule } from 'src/database/commands/upgrade
import { V2_17_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/2-17/2-17-upgrade-version-command.module';
import { V2_18_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/2-18/2-18-upgrade-version-command.module';
import { V2_19_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/2-19/2-19-upgrade-version-command.module';
import { V2_20_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/2-20/2-20-upgrade-version-command.module';
@Module({
imports: [
@@ -44,7 +43,6 @@ import { V2_20_UpgradeVersionCommandModule } from 'src/database/commands/upgrade
V2_17_UpgradeVersionCommandModule,
V2_18_UpgradeVersionCommandModule,
V2_19_UpgradeVersionCommandModule,
V2_20_UpgradeVersionCommandModule,
],
})
export class WorkspaceCommandProviderModule {}
@@ -1,6 +1,5 @@
export enum CacheStorageNamespace {
ModuleMessaging = 'module:messaging',
ModuleEmailing = 'module:emailing',
ModuleCalendar = 'module:calendar',
ModuleWorkflow = 'module:workflow',
EngineWorkspace = 'engine:workspace',
@@ -17,11 +17,6 @@ export const CAMPAIGN_STATUS = {
export const MATERIALIZE_CAMPAIGN_JOB = 'MaterializeCampaignJob';
export const SEND_CAMPAIGN_EMAIL_JOB = 'SendCampaignEmailJob';
export const REFRESH_CAMPAIGN_STATS_JOB = 'RefreshCampaignStatsJob';
export const CAMPAIGN_STATS_REFRESH_DEBOUNCE_MS = 10_000;
export const CAMPAIGN_STATS_REFRESH_DELAY_MS =
CAMPAIGN_STATS_REFRESH_DEBOUNCE_MS + 2_000;
export const MAX_CAMPAIGN_RECIPIENTS = 10000;
@@ -1,4 +0,0 @@
export type RefreshCampaignStatsJobData = {
workspaceId: string;
campaignId: string;
};
@@ -22,7 +22,6 @@ import { EmailSenderJob } from 'src/engine/core-modules/email/email-sender.job';
import { EmailModule } from 'src/engine/core-modules/email/email.module';
import { EmailingModule } from 'src/modules/emailing/emailing.module';
import { MaterializeCampaignJob } from 'src/modules/emailing/jobs/materialize-campaign.job';
import { RefreshCampaignStatsJob } from 'src/modules/emailing/jobs/refresh-campaign-stats.job';
import { SendCampaignEmailJob } from 'src/modules/emailing/jobs/send-campaign-email.job';
import { EnterpriseModule } from 'src/engine/core-modules/enterprise/enterprise.module';
import { EventLogIngestionModule } from 'src/engine/core-modules/event-logs/ingest/event-log-ingestion.module';
@@ -105,7 +104,6 @@ import { WorkflowModule } from 'src/modules/workflow/workflow.module';
EmailSenderJob,
SendCampaignEmailJob,
MaterializeCampaignJob,
RefreshCampaignStatsJob,
UpdateSubscriptionQuantityJob,
HandleWorkspaceMemberDeletedJob,
CleanWorkspaceDeletionWarningUserVarsJob,
@@ -261,58 +261,6 @@ export const buildMessageCampaignStandardFlatFieldMetadatas = ({
settings: { displayFormat: DateDisplayFormat.RELATIVE },
},
}),
sentCount: createStandardFieldFlatMetadata({
...base,
context: {
fieldName: 'sentCount',
type: FieldMetadataType.NUMBER,
label: i18nLabel(msg`Sent count`),
description: i18nLabel(msg`Number of emails sent`),
icon: 'IconMailFast',
isNullable: false,
isUIEditable: false,
defaultValue: 0,
},
}),
failedCount: createStandardFieldFlatMetadata({
...base,
context: {
fieldName: 'failedCount',
type: FieldMetadataType.NUMBER,
label: i18nLabel(msg`Failed count`),
description: i18nLabel(msg`Number of emails that failed to send`),
icon: 'IconMailX',
isNullable: false,
isUIEditable: false,
defaultValue: 0,
},
}),
bouncedCount: createStandardFieldFlatMetadata({
...base,
context: {
fieldName: 'bouncedCount',
type: FieldMetadataType.NUMBER,
label: i18nLabel(msg`Bounced count`),
description: i18nLabel(msg`Number of emails that bounced`),
icon: 'IconMailOff',
isNullable: false,
isUIEditable: false,
defaultValue: 0,
},
}),
complainedCount: createStandardFieldFlatMetadata({
...base,
context: {
fieldName: 'complainedCount',
type: FieldMetadataType.NUMBER,
label: i18nLabel(msg`Complained count`),
description: i18nLabel(msg`Number of spam complaints received`),
icon: 'IconMoodSad',
isNullable: false,
isUIEditable: false,
defaultValue: 0,
},
}),
unsubscribeTopicId: createStandardFieldFlatMetadata({
...base,
context: {
@@ -11,10 +11,8 @@ import { computeStandardCalendarEventViewFields } from 'src/engine/workspace-man
import { computeStandardCallRecordingViewFields } from 'src/engine/workspace-manager/twenty-standard-application/utils/view-field/compute-standard-call-recording-view-fields.util';
import { computeStandardCompanyViewFields } from 'src/engine/workspace-manager/twenty-standard-application/utils/view-field/compute-standard-company-view-fields.util';
import { computeStandardDashboardViewFields } from 'src/engine/workspace-manager/twenty-standard-application/utils/view-field/compute-standard-dashboard-view-fields.util';
import { computeStandardMessageCampaignViewFields } from 'src/engine/workspace-manager/twenty-standard-application/utils/view-field/compute-standard-message-campaign-view-fields.util';
import { computeStandardMessageChannelMessageAssociationMessageFolderViewFields } from 'src/engine/workspace-manager/twenty-standard-application/utils/view-field/compute-standard-message-channel-message-association-message-folder-view-fields.util';
import { computeStandardMessageChannelMessageAssociationViewFields } from 'src/engine/workspace-manager/twenty-standard-application/utils/view-field/compute-standard-message-channel-message-association-view-fields.util';
import { computeStandardMessageListViewFields } from 'src/engine/workspace-manager/twenty-standard-application/utils/view-field/compute-standard-message-list-view-fields.util';
import { computeStandardMessageParticipantViewFields } from 'src/engine/workspace-manager/twenty-standard-application/utils/view-field/compute-standard-message-participant-view-fields.util';
import { computeStandardMessageThreadViewFields } from 'src/engine/workspace-manager/twenty-standard-application/utils/view-field/compute-standard-message-thread-view-fields.util';
import { computeStandardMessageViewFields } from 'src/engine/workspace-manager/twenty-standard-application/utils/view-field/compute-standard-message-view-fields.util';
@@ -47,12 +45,10 @@ const STANDARD_FLAT_VIEW_FIELD_METADATA_BUILDERS_BY_OBJECT_NAME = {
company: computeStandardCompanyViewFields,
dashboard: computeStandardDashboardViewFields,
message: computeStandardMessageViewFields,
messageCampaign: computeStandardMessageCampaignViewFields,
messageChannelMessageAssociation:
computeStandardMessageChannelMessageAssociationViewFields,
messageChannelMessageAssociationMessageFolder:
computeStandardMessageChannelMessageAssociationMessageFolderViewFields,
messageList: computeStandardMessageListViewFields,
messageParticipant: computeStandardMessageParticipantViewFields,
messageThread: computeStandardMessageThreadViewFields,
note: computeStandardNoteViewFields,
@@ -1,144 +0,0 @@
import { type FlatViewField } from 'src/engine/metadata-modules/flat-view-field/types/flat-view-field.type';
import {
createStandardViewFieldFlatMetadata,
type CreateStandardViewFieldArgs,
} from 'src/engine/workspace-manager/twenty-standard-application/utils/view-field/create-standard-view-field-flat-metadata.util';
export const computeStandardMessageCampaignViewFields = (
args: Omit<CreateStandardViewFieldArgs<'messageCampaign'>, 'context'>,
): Record<string, FlatViewField> => {
return {
allMessageCampaignsSubject: createStandardViewFieldFlatMetadata({
...args,
objectName: 'messageCampaign',
context: {
viewName: 'allMessageCampaigns',
viewFieldName: 'subject',
fieldName: 'subject',
position: 0,
isVisible: true,
size: 300,
},
}),
allMessageCampaignsStatus: createStandardViewFieldFlatMetadata({
...args,
objectName: 'messageCampaign',
context: {
viewName: 'allMessageCampaigns',
viewFieldName: 'status',
fieldName: 'status',
position: 1,
isVisible: true,
size: 120,
},
}),
allMessageCampaignsList: createStandardViewFieldFlatMetadata({
...args,
objectName: 'messageCampaign',
context: {
viewName: 'allMessageCampaigns',
viewFieldName: 'list',
fieldName: 'list',
position: 2,
isVisible: true,
size: 150,
},
}),
allMessageCampaignsFromAddress: createStandardViewFieldFlatMetadata({
...args,
objectName: 'messageCampaign',
context: {
viewName: 'allMessageCampaigns',
viewFieldName: 'fromAddress',
fieldName: 'fromAddress',
position: 3,
isVisible: true,
size: 200,
},
}),
allMessageCampaignsSentAt: createStandardViewFieldFlatMetadata({
...args,
objectName: 'messageCampaign',
context: {
viewName: 'allMessageCampaigns',
viewFieldName: 'sentAt',
fieldName: 'sentAt',
position: 4,
isVisible: true,
size: 150,
},
}),
allMessageCampaignsSentCount: createStandardViewFieldFlatMetadata({
...args,
objectName: 'messageCampaign',
context: {
viewName: 'allMessageCampaigns',
viewFieldName: 'sentCount',
fieldName: 'sentCount',
position: 5,
isVisible: true,
size: 100,
},
}),
allMessageCampaignsFailedCount: createStandardViewFieldFlatMetadata({
...args,
objectName: 'messageCampaign',
context: {
viewName: 'allMessageCampaigns',
viewFieldName: 'failedCount',
fieldName: 'failedCount',
position: 6,
isVisible: true,
size: 100,
},
}),
allMessageCampaignsBouncedCount: createStandardViewFieldFlatMetadata({
...args,
objectName: 'messageCampaign',
context: {
viewName: 'allMessageCampaigns',
viewFieldName: 'bouncedCount',
fieldName: 'bouncedCount',
position: 7,
isVisible: true,
size: 100,
},
}),
allMessageCampaignsComplainedCount: createStandardViewFieldFlatMetadata({
...args,
objectName: 'messageCampaign',
context: {
viewName: 'allMessageCampaigns',
viewFieldName: 'complainedCount',
fieldName: 'complainedCount',
position: 8,
isVisible: true,
size: 120,
},
}),
allMessageCampaignsRecipients: createStandardViewFieldFlatMetadata({
...args,
objectName: 'messageCampaign',
context: {
viewName: 'allMessageCampaigns',
viewFieldName: 'recipients',
fieldName: 'recipients',
position: 9,
isVisible: true,
size: 150,
},
}),
allMessageCampaignsCreatedAt: createStandardViewFieldFlatMetadata({
...args,
objectName: 'messageCampaign',
context: {
viewName: 'allMessageCampaigns',
viewFieldName: 'createdAt',
fieldName: 'createdAt',
position: 10,
isVisible: true,
size: 150,
},
}),
};
};
@@ -1,60 +0,0 @@
import { type FlatViewField } from 'src/engine/metadata-modules/flat-view-field/types/flat-view-field.type';
import {
createStandardViewFieldFlatMetadata,
type CreateStandardViewFieldArgs,
} from 'src/engine/workspace-manager/twenty-standard-application/utils/view-field/create-standard-view-field-flat-metadata.util';
export const computeStandardMessageListViewFields = (
args: Omit<CreateStandardViewFieldArgs<'messageList'>, 'context'>,
): Record<string, FlatViewField> => {
return {
allMessageListsName: createStandardViewFieldFlatMetadata({
...args,
objectName: 'messageList',
context: {
viewName: 'allMessageLists',
viewFieldName: 'name',
fieldName: 'name',
position: 0,
isVisible: true,
size: 300,
},
}),
allMessageListsMembers: createStandardViewFieldFlatMetadata({
...args,
objectName: 'messageList',
context: {
viewName: 'allMessageLists',
viewFieldName: 'members',
fieldName: 'members',
position: 1,
isVisible: true,
size: 150,
},
}),
allMessageListsCampaigns: createStandardViewFieldFlatMetadata({
...args,
objectName: 'messageList',
context: {
viewName: 'allMessageLists',
viewFieldName: 'campaigns',
fieldName: 'campaigns',
position: 2,
isVisible: true,
size: 150,
},
}),
allMessageListsCreatedAt: createStandardViewFieldFlatMetadata({
...args,
objectName: 'messageList',
context: {
viewName: 'allMessageLists',
viewFieldName: 'createdAt',
fieldName: 'createdAt',
position: 3,
isVisible: true,
size: 150,
},
}),
};
};
@@ -11,10 +11,8 @@ import { computeStandardCalendarEventViews } from 'src/engine/workspace-manager/
import { computeStandardCallRecordingViews } from 'src/engine/workspace-manager/twenty-standard-application/utils/view/compute-standard-call-recording-views.util';
import { computeStandardCompanyViews } from 'src/engine/workspace-manager/twenty-standard-application/utils/view/compute-standard-company-views.util';
import { computeStandardDashboardViews } from 'src/engine/workspace-manager/twenty-standard-application/utils/view/compute-standard-dashboard-views.util';
import { computeStandardMessageCampaignViews } from 'src/engine/workspace-manager/twenty-standard-application/utils/view/compute-standard-message-campaign-views.util';
import { computeStandardMessageChannelMessageAssociationMessageFolderViews } from 'src/engine/workspace-manager/twenty-standard-application/utils/view/compute-standard-message-channel-message-association-message-folder-views.util';
import { computeStandardMessageChannelMessageAssociationViews } from 'src/engine/workspace-manager/twenty-standard-application/utils/view/compute-standard-message-channel-message-association-views.util';
import { computeStandardMessageListViews } from 'src/engine/workspace-manager/twenty-standard-application/utils/view/compute-standard-message-list-views.util';
import { computeStandardMessageParticipantViews } from 'src/engine/workspace-manager/twenty-standard-application/utils/view/compute-standard-message-participant-views.util';
import { computeStandardMessageThreadViews } from 'src/engine/workspace-manager/twenty-standard-application/utils/view/compute-standard-message-thread-views.util';
import { computeStandardMessageViews } from 'src/engine/workspace-manager/twenty-standard-application/utils/view/compute-standard-message-views.util';
@@ -47,12 +45,10 @@ const STANDARD_FLAT_VIEW_METADATA_BUILDERS_BY_OBJECT_NAME = {
company: computeStandardCompanyViews,
dashboard: computeStandardDashboardViews,
message: computeStandardMessageViews,
messageCampaign: computeStandardMessageCampaignViews,
messageChannelMessageAssociation:
computeStandardMessageChannelMessageAssociationViews,
messageChannelMessageAssociationMessageFolder:
computeStandardMessageChannelMessageAssociationMessageFolderViews,
messageList: computeStandardMessageListViews,
messageParticipant: computeStandardMessageParticipantViews,
messageThread: computeStandardMessageThreadViews,
note: computeStandardNoteViews,
@@ -1,26 +0,0 @@
import { ViewType, ViewKey } from 'twenty-shared/types';
import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
import {
createStandardViewFlatMetadata,
type CreateStandardViewArgs,
} from 'src/engine/workspace-manager/twenty-standard-application/utils/view/create-standard-view-flat-metadata.util';
export const computeStandardMessageCampaignViews = (
args: Omit<CreateStandardViewArgs<'messageCampaign'>, 'context'>,
): Record<string, FlatView> => {
return {
allMessageCampaigns: createStandardViewFlatMetadata({
...args,
objectName: 'messageCampaign',
context: {
viewName: 'allMessageCampaigns',
name: 'All {objectLabelPlural}',
type: ViewType.TABLE,
key: ViewKey.INDEX,
position: 0,
icon: 'IconSend',
},
}),
};
};
@@ -1,26 +0,0 @@
import { ViewType, ViewKey } from 'twenty-shared/types';
import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
import {
createStandardViewFlatMetadata,
type CreateStandardViewArgs,
} from 'src/engine/workspace-manager/twenty-standard-application/utils/view/create-standard-view-flat-metadata.util';
export const computeStandardMessageListViews = (
args: Omit<CreateStandardViewArgs<'messageList'>, 'context'>,
): Record<string, FlatView> => {
return {
allMessageLists: createStandardViewFlatMetadata({
...args,
objectName: 'messageList',
context: {
viewName: 'allMessageLists',
name: 'All {objectLabelPlural}',
type: ViewType.TABLE,
key: ViewKey.INDEX,
position: 0,
icon: 'IconList',
},
}),
};
};
@@ -10,7 +10,6 @@ import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
import { MessageChannelMetadataModule } from 'src/engine/metadata-modules/message-channel/message-channel-metadata.module';
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.module';
import { provideWorkspaceScopedRepository } from 'src/engine/twenty-orm/workspace-scoped-repository/provide-workspace-scoped-repository';
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
import { WorkspaceEventEmitterModule } from 'src/engine/workspace-event-emitter/workspace-event-emitter.module';
@@ -19,7 +18,6 @@ import { EmailingSendResolver } from 'src/modules/emailing/resolvers/emailing-se
import { UnsubscribeTopicResolver } from 'src/modules/emailing/resolvers/unsubscribe-topic.resolver';
import { EmailBillingService } from 'src/modules/emailing/services/email-billing.service';
import { EmailingDomainSenderService } from 'src/modules/emailing/services/emailing-domain-sender.service';
import { MessageCampaignStatisticsService } from 'src/modules/emailing/services/message-campaign-statistics.service';
import { MessageCampaignService } from 'src/modules/emailing/services/message-campaign.service';
import { MessageSuppressionService } from 'src/modules/emailing/services/message-suppression.service';
import { UnsubscribeTopicService } from 'src/modules/emailing/services/unsubscribe-topic.service';
@@ -30,7 +28,6 @@ import { UnsubscribeTopicService } from 'src/modules/emailing/services/unsubscri
MessageChannelMetadataModule,
FeatureFlagModule,
PermissionsModule,
UserRoleModule,
BillingModule,
WorkspaceEventEmitterModule,
WorkspaceCacheModule,
@@ -45,7 +42,6 @@ import { UnsubscribeTopicService } from 'src/modules/emailing/services/unsubscri
providers: [
EmailBillingService,
MessageCampaignService,
MessageCampaignStatisticsService,
MessageSuppressionService,
UnsubscribeTopicService,
EmailingDomainSenderService,
@@ -58,7 +54,6 @@ import { UnsubscribeTopicService } from 'src/modules/emailing/services/unsubscri
exports: [
EmailingDomainSenderService,
MessageCampaignService,
MessageCampaignStatisticsService,
MessageSuppressionService,
UnsubscribeTopicService,
],
@@ -1,21 +0,0 @@
import { REFRESH_CAMPAIGN_STATS_JOB } from 'src/engine/core-modules/emailing-domain/constants/campaign.constant';
import { type RefreshCampaignStatsJobData } from 'src/engine/core-modules/emailing-domain/types/refresh-campaign-stats-job-data.type';
import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator';
import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator';
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
import { MessageCampaignStatisticsService } from 'src/modules/emailing/services/message-campaign-statistics.service';
@Processor(MessageQueue.emailQueue)
export class RefreshCampaignStatsJob {
constructor(
private readonly messageCampaignStatisticsService: MessageCampaignStatisticsService,
) {}
@Process(REFRESH_CAMPAIGN_STATS_JOB)
async handle(data: RefreshCampaignStatsJobData): Promise<void> {
await this.messageCampaignStatisticsService.refreshCampaignCounts({
workspaceId: data.workspaceId,
campaignId: data.campaignId,
});
}
}
@@ -97,13 +97,11 @@ export class EmailingSendResolver {
async previewMessageCampaignAudience(
@Args('input') input: PreviewMessageCampaignAudienceInput,
@AuthWorkspace() currentWorkspace: WorkspaceEntity,
@AuthUserWorkspaceId() userWorkspaceId: string,
): Promise<CampaignAudiencePreviewDTO> {
this.emailGroupAccessService.validateEmailGroupAccessOrThrow();
return this.messageCampaignService.previewAudience({
workspaceId: currentWorkspace.id,
userWorkspaceId,
listId: input.listId,
unsubscribeTopicId: input.unsubscribeTopicId,
});
@@ -1,75 +0,0 @@
import { Injectable } from '@nestjs/common';
import { CAMPAIGN_MESSAGE_DELIVERY_STATUS } from 'src/engine/core-modules/emailing-domain/constants/campaign.constant';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
import { MessageCampaignWorkspaceEntity } from 'src/modules/emailing/standard-objects/message-campaign.workspace-entity';
import { MessageWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message.workspace-entity';
type DeliveryStatusCountRow = {
deliveryStatus: string | null;
count: string;
};
@Injectable()
export class MessageCampaignStatisticsService {
constructor(
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
) {}
async refreshCampaignCounts({
workspaceId,
campaignId,
}: {
workspaceId: string;
campaignId: string;
}): Promise<void> {
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
const messageRepository =
await this.globalWorkspaceOrmManager.getRepository(
workspaceId,
MessageWorkspaceEntity,
{ shouldBypassPermissionChecks: true },
);
const deliveryStatusCountRows = await messageRepository
.createQueryBuilder('message')
.select('message.deliveryStatus', 'deliveryStatus')
.addSelect('COUNT(*)', 'count')
.where('message.messageCampaignId = :campaignId', { campaignId })
.groupBy('message.deliveryStatus')
.getRawMany<DeliveryStatusCountRow>();
const countByDeliveryStatus = new Map(
deliveryStatusCountRows.map((row) => [
row.deliveryStatus,
Number(row.count),
]),
);
const sentCount =
countByDeliveryStatus.get(CAMPAIGN_MESSAGE_DELIVERY_STATUS.SENT) ?? 0;
const failedCount =
countByDeliveryStatus.get(CAMPAIGN_MESSAGE_DELIVERY_STATUS.FAILED) ?? 0;
const bouncedCount =
countByDeliveryStatus.get(CAMPAIGN_MESSAGE_DELIVERY_STATUS.BOUNCED) ??
0;
const complainedCount =
countByDeliveryStatus.get(
CAMPAIGN_MESSAGE_DELIVERY_STATUS.COMPLAINED,
) ?? 0;
const campaignRepository =
await this.globalWorkspaceOrmManager.getRepository(
workspaceId,
MessageCampaignWorkspaceEntity,
{ shouldBypassPermissionChecks: true },
);
await campaignRepository.update(
{ id: campaignId },
{ sentCount, failedCount, bouncedCount, complainedCount },
);
}, buildSystemAuthContext(workspaceId));
}
}
@@ -7,12 +7,9 @@ import { v4, v5 } from 'uuid';
import {
CAMPAIGN_MESSAGE_DELIVERY_STATUS,
CAMPAIGN_MESSAGE_ID_NAMESPACE,
CAMPAIGN_STATS_REFRESH_DEBOUNCE_MS,
CAMPAIGN_STATS_REFRESH_DELAY_MS,
CAMPAIGN_STATUS,
MATERIALIZE_CAMPAIGN_JOB,
MAX_CAMPAIGN_RECIPIENTS,
REFRESH_CAMPAIGN_STATS_JOB,
SEND_CAMPAIGN_EMAIL_JOB,
} from 'src/engine/core-modules/emailing-domain/constants/campaign.constant';
import {
@@ -26,17 +23,12 @@ import { type CampaignRecipient } from 'src/engine/core-modules/emailing-domain/
import { type CampaignSkippedBreakdown } from 'src/engine/core-modules/emailing-domain/types/campaign-skipped-breakdown.type';
import { type MaterializeCampaignJobData } from 'src/engine/core-modules/emailing-domain/types/materialize-campaign-job-data.type';
import { type RawCampaignRecipient } from 'src/engine/core-modules/emailing-domain/types/raw-campaign-recipient.type';
import { type RefreshCampaignStatsJobData } from 'src/engine/core-modules/emailing-domain/types/refresh-campaign-stats-job-data.type';
import { type SendCampaignEmailJobData } from 'src/engine/core-modules/emailing-domain/types/send-campaign-email-job-data.type';
import { normalizeCampaignRecipients } from 'src/engine/core-modules/emailing-domain/utils/normalize-campaign-recipients.util';
import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decorators/cache-storage.decorator';
import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service';
import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum';
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
import { MessageChannelMetadataService } from 'src/engine/metadata-modules/message-channel/message-channel-metadata.service';
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
@@ -44,7 +36,6 @@ import { InjectWorkspaceScopedRepository } from 'src/engine/twenty-orm/workspace
import { WorkspaceScopedRepository } from 'src/engine/twenty-orm/workspace-scoped-repository/workspace-scoped-repository';
import { EmailBillingService } from 'src/modules/emailing/services/email-billing.service';
import { EmailingDomainSenderService } from 'src/modules/emailing/services/emailing-domain-sender.service';
import { MessageCampaignStatisticsService } from 'src/modules/emailing/services/message-campaign-statistics.service';
import { MessageSuppressionService } from 'src/modules/emailing/services/message-suppression.service';
import { MessageCampaignWorkspaceEntity } from 'src/modules/emailing/standard-objects/message-campaign.workspace-entity';
import { MessageListMemberWorkspaceEntity } from 'src/modules/emailing/standard-objects/message-list-member.workspace-entity';
@@ -108,21 +99,14 @@ export class MessageCampaignService {
private readonly messageQueueService: MessageQueueService,
private readonly messageChannelMetadataService: MessageChannelMetadataService,
private readonly messageSuppressionService: MessageSuppressionService,
private readonly userRoleService: UserRoleService,
private readonly messageCampaignStatisticsService: MessageCampaignStatisticsService,
private readonly emailBillingService: EmailBillingService,
@InjectCacheStorage(CacheStorageNamespace.ModuleEmailing)
private readonly cacheStorageService: CacheStorageService,
) {}
private getUserRepository<T extends ObjectLiteral>(
workspaceId: string,
entity: Type<T>,
roleId: string,
) {
return this.globalWorkspaceOrmManager.getRepository(workspaceId, entity, {
unionOf: [roleId],
});
return this.globalWorkspaceOrmManager.getRepository(workspaceId, entity);
}
private getSystemRepository<T extends ObjectLiteral>(
@@ -156,18 +140,12 @@ export class MessageCampaignService {
);
}
const roleId = await this.userRoleService.getRoleIdForUserWorkspace({
workspaceId,
userWorkspaceId,
});
const { campaignId, recipients, skipped } =
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
async () => {
const rawRecipients = await this.resolveRecipientsFromList(
workspaceId,
listId,
roleId,
);
const normalized = normalizeCampaignRecipients(
@@ -177,7 +155,6 @@ export class MessageCampaignService {
const newCampaignId = await this.createCampaign({
workspaceId,
roleId,
subject,
html,
fromAddress,
@@ -493,17 +470,11 @@ export class MessageCampaignService {
}
await messageRepository.update(message.id, { deliveryStatus });
await this.scheduleCampaignStatsRefresh({
workspaceId,
campaignId: message.messageCampaignId,
});
}, buildSystemAuthContext(workspaceId));
}
private async createCampaign({
workspaceId,
roleId,
subject,
html,
fromAddress,
@@ -511,7 +482,6 @@ export class MessageCampaignService {
listId,
}: {
workspaceId: string;
roleId: string;
subject: string;
html: string;
fromAddress: string;
@@ -521,7 +491,6 @@ export class MessageCampaignService {
const campaignRepository = await this.getUserRepository(
workspaceId,
MessageCampaignWorkspaceEntity,
roleId,
);
const { identifiers } = await campaignRepository.insert({
@@ -685,58 +654,22 @@ export class MessageCampaignService {
sentAt: new Date(),
},
);
await this.scheduleCampaignStatsRefresh({
workspaceId,
campaignId,
});
}
private async scheduleCampaignStatsRefresh({
workspaceId,
campaignId,
}: {
workspaceId: string;
campaignId: string;
}): Promise<void> {
const acquired = await this.cacheStorageService.acquireLock(
`campaign-stats-refresh:${workspaceId}:${campaignId}`,
CAMPAIGN_STATS_REFRESH_DEBOUNCE_MS,
);
if (!acquired) {
return;
}
await this.messageQueueService.add<RefreshCampaignStatsJobData>(
REFRESH_CAMPAIGN_STATS_JOB,
{ workspaceId, campaignId },
{ delay: CAMPAIGN_STATS_REFRESH_DELAY_MS },
);
}
async previewAudience({
workspaceId,
userWorkspaceId,
listId,
unsubscribeTopicId,
}: {
workspaceId: string;
userWorkspaceId: string;
listId: string;
unsubscribeTopicId?: string;
}): Promise<CampaignAudiencePreview> {
const roleId = await this.userRoleService.getRoleIdForUserWorkspace({
workspaceId,
userWorkspaceId,
});
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
async () => {
const rawRecipients = await this.resolveRecipientsFromList(
workspaceId,
listId,
roleId,
);
const totalMembers = rawRecipients.length;
@@ -791,12 +724,10 @@ export class MessageCampaignService {
private async resolveRecipientsFromList(
workspaceId: string,
listId: string,
roleId: string,
): Promise<RawCampaignRecipient[]> {
const listMemberRepository = await this.getUserRepository(
workspaceId,
MessageListMemberWorkspaceEntity,
roleId,
);
const members = await listMemberRepository.find({
@@ -806,14 +737,12 @@ export class MessageCampaignService {
return this.loadRecipientsByPersonIds(
workspaceId,
members.map((member) => member.personId),
roleId,
);
}
private async loadRecipientsByPersonIds(
workspaceId: string,
personIds: string[],
roleId: string,
): Promise<RawCampaignRecipient[]> {
if (personIds.length === 0) {
return [];
@@ -822,7 +751,6 @@ export class MessageCampaignService {
const personRepository = await this.getUserRepository(
workspaceId,
PersonWorkspaceEntity,
roleId,
);
const people = await personRepository.find({
@@ -12,10 +12,6 @@ export class MessageCampaignWorkspaceEntity extends BaseWorkspaceEntity {
fromAddress: EmailsMetadata | null;
status: string;
sentAt: Date | null;
sentCount: number;
failedCount: number;
bouncedCount: number;
complainedCount: number;
unsubscribeTopicId: string | null;
list: EntityRelation<MessageListWorkspaceEntity> | null;
listId: string | null;
@@ -1055,12 +1055,6 @@ exports[`STANDARD_OBJECTS universal identifiers should never change without an e
"bodyTemplate": {
"universalIdentifier": "b3a69d08-31ca-4a8d-8359-5ca462899342",
},
"bouncedCount": {
"universalIdentifier": "20d884a9-34dd-4667-8ecb-ceec224258e2",
},
"complainedCount": {
"universalIdentifier": "82842cfa-f12a-4bab-bbde-b2cf587d0406",
},
"createdAt": {
"universalIdentifier": "4260eb89-0d55-5078-a6e5-5768781111ba",
},
@@ -1070,9 +1064,6 @@ exports[`STANDARD_OBJECTS universal identifiers should never change without an e
"deletedAt": {
"universalIdentifier": "da77cebd-4e72-5568-b808-027e21f748e8",
},
"failedCount": {
"universalIdentifier": "d373fcd7-b1ce-4c77-8031-a5785c475028",
},
"fromAddress": {
"universalIdentifier": "91e1a33c-c1ff-411a-b720-9085e13c05db",
},
@@ -1097,9 +1088,6 @@ exports[`STANDARD_OBJECTS universal identifiers should never change without an e
"sentAt": {
"universalIdentifier": "e2315b4f-9edf-4df2-96b9-961e76368671",
},
"sentCount": {
"universalIdentifier": "2f333d2b-37b8-4ddc-ad0d-c07c6ce066ad",
},
"status": {
"universalIdentifier": "c7117256-3de6-48e1-87df-c99c32bad610",
},
@@ -1131,46 +1119,6 @@ exports[`STANDARD_OBJECTS universal identifiers should never change without an e
},
},
"universalIdentifier": "238acb94-dd4c-4036-bc55-19b99d821efd",
"views": {
"allMessageCampaigns": {
"universalIdentifier": "ffedb368-33f0-43a7-b84e-e622b4e97be9",
"viewFields": {
"bouncedCount": {
"universalIdentifier": "135d3fb9-bc45-4426-980b-9836ed792e3a",
},
"complainedCount": {
"universalIdentifier": "9957dc6d-05ae-4829-ae7d-7cdf9239cdd0",
},
"createdAt": {
"universalIdentifier": "6dffa47d-3128-4d54-924a-4f67676116c2",
},
"failedCount": {
"universalIdentifier": "ad346c8e-f462-499e-89c8-da01d5460dc6",
},
"fromAddress": {
"universalIdentifier": "553a0ea6-1f55-4143-a2d0-8c1ad0cf3dd1",
},
"list": {
"universalIdentifier": "c0bf6a48-8695-4520-81e6-7462d35033b5",
},
"recipients": {
"universalIdentifier": "2f99f444-4af3-44fd-b08d-46955c4ac2a2",
},
"sentAt": {
"universalIdentifier": "7f4a9d81-afac-4d27-98de-0a2da65114f7",
},
"sentCount": {
"universalIdentifier": "0be62e57-3955-49bd-a9ef-8f82f7a6e9aa",
},
"status": {
"universalIdentifier": "ef35df97-b1a5-4f16-8b85-5751e5019f63",
},
"subject": {
"universalIdentifier": "b017c851-e38d-4a81-ab38-0fb10e9f239e",
},
},
},
},
},
"messageChannelMessageAssociation": {
"fields": {
@@ -1418,25 +1366,6 @@ exports[`STANDARD_OBJECTS universal identifiers should never change without an e
},
},
"universalIdentifier": "826561ea-4816-411c-baa0-eec5e6ca8866",
"views": {
"allMessageLists": {
"universalIdentifier": "c72bae18-75e9-4cb0-baeb-379d3529b98f",
"viewFields": {
"campaigns": {
"universalIdentifier": "037030a2-9dad-4bfd-957a-313b362172b4",
},
"createdAt": {
"universalIdentifier": "29f3e7de-c40b-4597-8568-7318c146e4da",
},
"members": {
"universalIdentifier": "6d314c4b-215a-4094-963a-ff9dd8221aea",
},
"name": {
"universalIdentifier": "c9767580-34e6-420b-923d-b4abd8c13d96",
},
},
},
},
},
"messageListMember": {
"fields": {
@@ -790,18 +790,6 @@ export const STANDARD_OBJECTS = {
},
status: { universalIdentifier: 'c7117256-3de6-48e1-87df-c99c32bad610' },
sentAt: { universalIdentifier: 'e2315b4f-9edf-4df2-96b9-961e76368671' },
sentCount: {
universalIdentifier: '2f333d2b-37b8-4ddc-ad0d-c07c6ce066ad',
},
failedCount: {
universalIdentifier: 'd373fcd7-b1ce-4c77-8031-a5785c475028',
},
bouncedCount: {
universalIdentifier: '20d884a9-34dd-4667-8ecb-ceec224258e2',
},
complainedCount: {
universalIdentifier: '82842cfa-f12a-4bab-bbde-b2cf587d0406',
},
unsubscribeTopicId: {
universalIdentifier: '0648e7ad-1769-4ff6-a4d5-72da79ef169c',
},
@@ -825,46 +813,6 @@ export const STANDARD_OBJECTS = {
universalIdentifier: '975823ad-9b97-4f39-b2c7-fbd7d77f4bd1',
},
},
views: {
allMessageCampaigns: {
universalIdentifier: 'ffedb368-33f0-43a7-b84e-e622b4e97be9',
viewFields: {
subject: {
universalIdentifier: 'b017c851-e38d-4a81-ab38-0fb10e9f239e',
},
status: {
universalIdentifier: 'ef35df97-b1a5-4f16-8b85-5751e5019f63',
},
list: {
universalIdentifier: 'c0bf6a48-8695-4520-81e6-7462d35033b5',
},
fromAddress: {
universalIdentifier: '553a0ea6-1f55-4143-a2d0-8c1ad0cf3dd1',
},
sentAt: {
universalIdentifier: '7f4a9d81-afac-4d27-98de-0a2da65114f7',
},
sentCount: {
universalIdentifier: '0be62e57-3955-49bd-a9ef-8f82f7a6e9aa',
},
failedCount: {
universalIdentifier: 'ad346c8e-f462-499e-89c8-da01d5460dc6',
},
bouncedCount: {
universalIdentifier: '135d3fb9-bc45-4426-980b-9836ed792e3a',
},
complainedCount: {
universalIdentifier: '9957dc6d-05ae-4829-ae7d-7cdf9239cdd0',
},
recipients: {
universalIdentifier: '2f99f444-4af3-44fd-b08d-46955c4ac2a2',
},
createdAt: {
universalIdentifier: '6dffa47d-3128-4d54-924a-4f67676116c2',
},
},
},
},
},
messageList: {
universalIdentifier: '826561ea-4816-411c-baa0-eec5e6ca8866',
@@ -888,25 +836,6 @@ export const STANDARD_OBJECTS = {
universalIdentifier: '8e205171-ed74-4620-b7d2-674aab85033a',
},
},
views: {
allMessageLists: {
universalIdentifier: 'c72bae18-75e9-4cb0-baeb-379d3529b98f',
viewFields: {
name: {
universalIdentifier: 'c9767580-34e6-420b-923d-b4abd8c13d96',
},
members: {
universalIdentifier: '6d314c4b-215a-4094-963a-ff9dd8221aea',
},
campaigns: {
universalIdentifier: '037030a2-9dad-4bfd-957a-313b362172b4',
},
createdAt: {
universalIdentifier: '29f3e7de-c40b-4597-8568-7318c146e4da',
},
},
},
},
},
messageListMember: {
universalIdentifier: '27773d24-8ce3-40f8-aa6c-1f590f2c08d2',