@capillarytech/creatives-library 9.0.54-alpha.1 → 9.0.54-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/v2Containers/CommunicationFlow/CommunicationFlow.js +6 -2
- package/v2Containers/CommunicationFlow/CommunicationFlowCard.js +1 -1
- package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +60 -3
- package/v2Containers/CommunicationFlow/Tests/CommunicationFlowCard.test.js +2 -2
- package/v2Containers/CommunicationFlow/constants.js +20 -6
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.js +6 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +25 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/deliverySettingsConfig.test.js +11 -4
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/deliverySettingsConfig.js +24 -5
package/package.json
CHANGED
|
@@ -215,8 +215,12 @@ const CommunicationFlow = ({
|
|
|
215
215
|
singleChannelStrategy: {
|
|
216
216
|
variant: {
|
|
217
217
|
channel,
|
|
218
|
-
|
|
219
|
-
|
|
218
|
+
// `channel` spreads LAST in both objects below — contentItem.templateData
|
|
219
|
+
// (e.g. mobile push) carries the UI's own internal channel identifier
|
|
220
|
+
// (MOBILEPUSH), which must never win over CCS's normalized enum value
|
|
221
|
+
// (PUSH) once spread order puts it after.
|
|
222
|
+
...(contentKey && { [contentKey]: { ...contentPayload, channel } }),
|
|
223
|
+
...(deliveryKey && { [deliveryKey]: { channelSettings: { ...channelSettings, channel } } }),
|
|
220
224
|
},
|
|
221
225
|
},
|
|
222
226
|
};
|
|
@@ -42,7 +42,7 @@ const resolveValue = (val) => {
|
|
|
42
42
|
|
|
43
43
|
const SENDER_ID_RESOLVERS = {
|
|
44
44
|
[SMS]: (setting) => resolveValue(setting?.gsmSenderId),
|
|
45
|
-
[EMAIL]: (setting) => resolveValue(setting?.
|
|
45
|
+
[EMAIL]: (setting) => resolveValue(setting?.senderId),
|
|
46
46
|
[VIBER]: (setting) => resolveValue(setting?.sender) || resolveValue(setting?.gsmSenderId),
|
|
47
47
|
[WHATSAPP]: (setting) => resolveValue(setting?.senderMobNum),
|
|
48
48
|
[RCS]: (setting) => resolveValue(setting?.senderMobNum) || resolveValue(setting?.rcsSender),
|
|
@@ -407,7 +407,7 @@ describe('handleSave — CCS flow', () => {
|
|
|
407
407
|
initialData: {
|
|
408
408
|
contentItems: [{
|
|
409
409
|
channel: 'EMAIL',
|
|
410
|
-
templateData: { emailSubject: 'Order Confirmed',
|
|
410
|
+
templateData: { emailSubject: 'Order Confirmed', emailBody: '<html>Hi</html>' },
|
|
411
411
|
}],
|
|
412
412
|
},
|
|
413
413
|
onSave,
|
|
@@ -432,13 +432,67 @@ describe('handleSave — CCS flow', () => {
|
|
|
432
432
|
);
|
|
433
433
|
});
|
|
434
434
|
|
|
435
|
-
it('
|
|
435
|
+
it('transforms WEBPUSH content from the legacy editor\'s messageContent.content wrapping to CCS\'s flat shape', async () => {
|
|
436
436
|
const onSave = jest.fn();
|
|
437
|
+
const cta = { type: 'SITE_URL', actionLink: 'https://example.com' };
|
|
438
|
+
const expandableDetails = { media: [{ url: 'https://example.com/banner.png', type: 'IMAGE' }], ctas: [] };
|
|
437
439
|
renderWithFlow({
|
|
438
440
|
features: {},
|
|
439
441
|
config: ccsConfig,
|
|
440
442
|
initialData: {
|
|
441
|
-
contentItems: [{
|
|
443
|
+
contentItems: [{
|
|
444
|
+
channel: 'WEBPUSH',
|
|
445
|
+
templateData: {
|
|
446
|
+
messageContent: {
|
|
447
|
+
content: {
|
|
448
|
+
channel: 'WEBPUSH',
|
|
449
|
+
accountId: 13792,
|
|
450
|
+
content: { title: 'Hi', message: 'Body', iconImageUrl: 'https://example.com/icon.png', cta, expandableDetails },
|
|
451
|
+
messageSubject: 'Subject',
|
|
452
|
+
offers: [],
|
|
453
|
+
},
|
|
454
|
+
},
|
|
455
|
+
},
|
|
456
|
+
}],
|
|
457
|
+
},
|
|
458
|
+
onSave,
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
await userEvent.click(screen.getByRole('button', { name: /^save$/i }));
|
|
462
|
+
|
|
463
|
+
await waitFor(() => expect(createCommDefinition).toHaveBeenCalledTimes(1));
|
|
464
|
+
expect(createCommDefinition).toHaveBeenCalledWith(
|
|
465
|
+
expect.objectContaining({
|
|
466
|
+
singleChannelStrategy: expect.objectContaining({
|
|
467
|
+
variant: expect.objectContaining({
|
|
468
|
+
channel: 'WEBPUSH',
|
|
469
|
+
webPushMessageContent: {
|
|
470
|
+
channel: 'WEBPUSH',
|
|
471
|
+
messageSubject: 'Subject',
|
|
472
|
+
accountId: 13792,
|
|
473
|
+
content: { title: 'Hi', message: 'Body', iconImageUrl: 'https://example.com/icon.png', cta, expandableDetails },
|
|
474
|
+
},
|
|
475
|
+
}),
|
|
476
|
+
}),
|
|
477
|
+
}),
|
|
478
|
+
);
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
it('normalizes the internal MOBILEPUSH channel to CCS\'s PUSH enum (keys stay mpush*), even when templateData/channelSetting carry their own stray channel field', async () => {
|
|
482
|
+
const onSave = jest.fn();
|
|
483
|
+
renderWithFlow({
|
|
484
|
+
features: {},
|
|
485
|
+
config: ccsConfig,
|
|
486
|
+
initialData: {
|
|
487
|
+
// `channel: 'MOBILEPUSH'` inline on templateData reproduces the real
|
|
488
|
+
// production bug: contentItem.templateData carries the UI's own
|
|
489
|
+
// internal channel identifier, which must not win over the
|
|
490
|
+
// normalized 'PUSH' enum once spread into mpushMessageContent.
|
|
491
|
+
contentItems: [{ channel: 'MOBILEPUSH', templateData: { channel: 'MOBILEPUSH', messageSubject: 'Hi' } }],
|
|
492
|
+
// Likewise, deliverySetting.channelSetting.PUSH carrying its own
|
|
493
|
+
// stray `channel` key must not win over the normalized value once
|
|
494
|
+
// spread into mpushDeliverySettings.channelSettings.
|
|
495
|
+
deliverySetting: { channelSetting: { PUSH: { channel: 'MOBILEPUSH', domainId: 2002 } } },
|
|
442
496
|
},
|
|
443
497
|
onSave,
|
|
444
498
|
});
|
|
@@ -452,6 +506,9 @@ describe('handleSave — CCS flow', () => {
|
|
|
452
506
|
variant: expect.objectContaining({
|
|
453
507
|
channel: 'PUSH',
|
|
454
508
|
mpushMessageContent: { channel: 'PUSH', messageSubject: 'Hi' },
|
|
509
|
+
mpushDeliverySettings: {
|
|
510
|
+
channelSettings: { channel: 'PUSH', domainId: 2002 },
|
|
511
|
+
},
|
|
455
512
|
}),
|
|
456
513
|
}),
|
|
457
514
|
}),
|
|
@@ -247,11 +247,11 @@ describe('CommunicationFlowCard', () => {
|
|
|
247
247
|
expect(screen.getByText(/Sender ID/)).toBeInTheDocument();
|
|
248
248
|
});
|
|
249
249
|
|
|
250
|
-
it('shows
|
|
250
|
+
it('shows senderId for EMAIL with Sender ID label', () => {
|
|
251
251
|
renderCard({
|
|
252
252
|
initialData: makeSavedData({
|
|
253
253
|
contentItems: [{ channel: 'EMAIL', templateData: {} }],
|
|
254
|
-
deliverySetting: { channelSetting: { EMAIL: {
|
|
254
|
+
deliverySetting: { channelSetting: { EMAIL: { senderId: 'test@example.com' } } },
|
|
255
255
|
}),
|
|
256
256
|
});
|
|
257
257
|
expect(screen.getByText(/test@example.com/)).toBeInTheDocument();
|
|
@@ -68,15 +68,29 @@ export const CCS_CHANNEL_CONTENT_KEY_MAP = {
|
|
|
68
68
|
// editor (messageMeta-oriented field names) — for channels whose legacy field
|
|
69
69
|
// names don't match CCS's own content schema, transform before sending rather
|
|
70
70
|
// than dumping templateData as-is. EMAIL: legacy stores the subject as
|
|
71
|
-
// `emailSubject`
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
//
|
|
71
|
+
// `emailSubject` and the HTML body as `emailBody` (see
|
|
72
|
+
// extractContentForPreview.js and CreativesContainer/index.js, where the Bee
|
|
73
|
+
// editor's output is written back into templateData.emailBody — also
|
|
74
|
+
// mirrored by the existing legacy emailMessageContent builder in
|
|
75
|
+
// utils/transformerUtils.js). CCS expects `messageSubject`/`messageBody`.
|
|
75
76
|
export const CCS_CONTENT_TRANSFORMS = {
|
|
76
77
|
EMAIL: (templateData = {}) => ({
|
|
77
78
|
messageSubject: templateData.emailSubject,
|
|
78
|
-
messageBody: templateData.
|
|
79
|
+
messageBody: templateData.emailBody || templateData.emailHtml,
|
|
79
80
|
}),
|
|
81
|
+
// WEBPUSH: the legacy editor nests the real data under
|
|
82
|
+
// messageContent.content (see CommunicationFlowCard.js's own comment on
|
|
83
|
+
// this same wrapping, for the edit-mode case) — extracting it flat here
|
|
84
|
+
// matches CCS's shape ({ messageSubject, accountId, content }); `offers`
|
|
85
|
+
// (a legacy-only field) is deliberately dropped, not part of CCS's schema.
|
|
86
|
+
WEBPUSH: (templateData = {}) => {
|
|
87
|
+
const inner = templateData.messageContent?.content || templateData;
|
|
88
|
+
return {
|
|
89
|
+
messageSubject: inner.messageSubject,
|
|
90
|
+
accountId: inner.accountId,
|
|
91
|
+
content: inner.content,
|
|
92
|
+
};
|
|
93
|
+
},
|
|
80
94
|
};
|
|
81
95
|
|
|
82
96
|
export const CCS_CHANNEL_DELIVERY_KEY_MAP = {
|
|
@@ -298,4 +312,4 @@ export const INCENTIVE_TYPES = [
|
|
|
298
312
|
{
|
|
299
313
|
value: 'badges', label: <FormattedMessage {...messages.badges} />, isActive: false, disabled: false,
|
|
300
314
|
},
|
|
301
|
-
];
|
|
315
|
+
];
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
getEmailDefaultsForDomainId,
|
|
30
30
|
getSmsDefaultsForDomainId,
|
|
31
31
|
getRcsAccountName,
|
|
32
|
+
getEmailDomainGatewayMapIdByDomainId,
|
|
32
33
|
} from './deliverySettingsConfig';
|
|
33
34
|
import messages from '../../messages';
|
|
34
35
|
import './SenderDetails.scss';
|
|
@@ -151,6 +152,11 @@ const SenderDetails = ({
|
|
|
151
152
|
saveValues.whatsappDomainId = matchedDomain?.domainProperties?.id ?? matchedDomain?.domainId ?? matchedDomain?.id;
|
|
152
153
|
}
|
|
153
154
|
}
|
|
155
|
+
// Include EMAIL domainGatewayMapId derived from the selected domain for persistence -
|
|
156
|
+
// CCS needs it alongside domainId, but it's not a user-facing field.
|
|
157
|
+
if (entity && fieldValues.emailDomain != null && fieldValues.emailDomain !== '') {
|
|
158
|
+
saveValues.emailDomainGatewayMapId = getEmailDomainGatewayMapIdByDomainId(entity, fieldValues.emailDomain);
|
|
159
|
+
}
|
|
154
160
|
onSave(saveValues);
|
|
155
161
|
}
|
|
156
162
|
onClose();
|
package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js
CHANGED
|
@@ -83,6 +83,7 @@ const ENTITIES = {
|
|
|
83
83
|
emailTwoDomains: {
|
|
84
84
|
EMAIL: [
|
|
85
85
|
{
|
|
86
|
+
id: 'gwmap-a',
|
|
86
87
|
domainProperties: {
|
|
87
88
|
id: 'em-a',
|
|
88
89
|
domainName: 'Mail Alpha',
|
|
@@ -101,6 +102,7 @@ const ENTITIES = {
|
|
|
101
102
|
},
|
|
102
103
|
},
|
|
103
104
|
{
|
|
105
|
+
id: 'gwmap-b',
|
|
104
106
|
domainProperties: {
|
|
105
107
|
id: 'em-b',
|
|
106
108
|
domainName: 'Mail Beta',
|
|
@@ -322,6 +324,29 @@ describe('SenderDetails', () => {
|
|
|
322
324
|
});
|
|
323
325
|
});
|
|
324
326
|
|
|
327
|
+
it('persists emailDomainGatewayMapId derived from the selected email domain on save', async () => {
|
|
328
|
+
const { onSave } = renderSenderDetails({
|
|
329
|
+
channels: ['EMAIL'],
|
|
330
|
+
preloadedDomainProperties: ENTITIES.emailTwoDomains,
|
|
331
|
+
});
|
|
332
|
+
const root = document.querySelector('.sender-details');
|
|
333
|
+
await waitFor(() => expect(within(root).getByText('Mail Alpha')).toBeInTheDocument());
|
|
334
|
+
|
|
335
|
+
await openSelectAndChoose(root, 0, 'Mail Beta');
|
|
336
|
+
await waitFor(() => expect(within(root).getByText('beta@other.com')).toBeInTheDocument());
|
|
337
|
+
|
|
338
|
+
const saveBtn = within(root).getByRole('button', { name: /save changes/i });
|
|
339
|
+
await waitFor(() => expect(saveBtn).not.toBeDisabled());
|
|
340
|
+
fireEvent.click(saveBtn);
|
|
341
|
+
|
|
342
|
+
expect(onSave).toHaveBeenCalledWith(
|
|
343
|
+
expect.objectContaining({
|
|
344
|
+
emailDomain: 'em-b',
|
|
345
|
+
emailDomainGatewayMapId: 'gwmap-b',
|
|
346
|
+
}),
|
|
347
|
+
);
|
|
348
|
+
});
|
|
349
|
+
|
|
325
350
|
it('resets email domain row back to the first gateway', async () => {
|
|
326
351
|
renderSenderDetails({
|
|
327
352
|
channels: ['EMAIL'],
|
|
@@ -438,6 +438,7 @@ describe('deliverySettingsConfig — user-facing flows', () => {
|
|
|
438
438
|
smsDomain: 'd1',
|
|
439
439
|
smsSenderId: 'g1',
|
|
440
440
|
emailDomain: 'ed',
|
|
441
|
+
emailDomainGatewayMapId: 'gwmap-1',
|
|
441
442
|
emailSenderId: 'mail@x.com',
|
|
442
443
|
emailSenderName: 'N',
|
|
443
444
|
emailReplyToId: 'r@x.com',
|
|
@@ -455,9 +456,10 @@ describe('deliverySettingsConfig — user-facing flows', () => {
|
|
|
455
456
|
SMS: { domainId: 'd1', gsmSenderId: 'g1' },
|
|
456
457
|
EMAIL: {
|
|
457
458
|
domainId: 'ed',
|
|
458
|
-
|
|
459
|
+
domainGatewayMapId: 'gwmap-1',
|
|
460
|
+
senderId: 'mail@x.com',
|
|
459
461
|
senderLabel: 'N',
|
|
460
|
-
|
|
462
|
+
replyToId: 'r@x.com',
|
|
461
463
|
},
|
|
462
464
|
WHATSAPP: { domainId: 'wd', senderMobNum: '+w' },
|
|
463
465
|
RCS: { senderMobNum: '+r', rcsSender: '+r' },
|
|
@@ -472,9 +474,10 @@ describe('deliverySettingsConfig — user-facing flows', () => {
|
|
|
472
474
|
SMS: { domainId: 'd', gsmSenderId: 'g' },
|
|
473
475
|
EMAIL: {
|
|
474
476
|
domainId: 'ed',
|
|
475
|
-
|
|
477
|
+
domainGatewayMapId: 'gwmap-2',
|
|
478
|
+
senderId: 'e@e.com',
|
|
476
479
|
senderLabel: 'L',
|
|
477
|
-
|
|
480
|
+
replyToId: 'r@e.com',
|
|
478
481
|
},
|
|
479
482
|
WHATSAPP: { senderMobNum: '+w', domainId: 'wid' },
|
|
480
483
|
RCS: { rcsSender: '+r' },
|
|
@@ -486,6 +489,10 @@ describe('deliverySettingsConfig — user-facing flows', () => {
|
|
|
486
489
|
smsDomain: 'd',
|
|
487
490
|
smsSenderId: 'g',
|
|
488
491
|
emailDomain: 'ed',
|
|
492
|
+
emailDomainGatewayMapId: 'gwmap-2',
|
|
493
|
+
emailSenderId: 'e@e.com',
|
|
494
|
+
emailSenderName: 'L',
|
|
495
|
+
emailReplyToId: 'r@e.com',
|
|
489
496
|
whatsappDomainId: 'wid',
|
|
490
497
|
viberSenderId: 'vx',
|
|
491
498
|
});
|
|
@@ -181,6 +181,14 @@ const getEmailDomainValue = (entity) => {
|
|
|
181
181
|
return emailData.domainProperties?.id ?? emailData.domainId ?? emailData.id ?? '';
|
|
182
182
|
};
|
|
183
183
|
|
|
184
|
+
// CCS's domainGatewayMapId is the domainProperties API's own outer `id`
|
|
185
|
+
// (distinct from `domainProperties.id`, which is the numeric domainId) - the
|
|
186
|
+
// domain-gateway-mapping id for the selected domain entry.
|
|
187
|
+
const getEmailDomainGatewayMapIdByDomainId = (entity, domainId) => {
|
|
188
|
+
const domainEntry = getEmailDomainById(entity, domainId);
|
|
189
|
+
return domainEntry?.id ?? '';
|
|
190
|
+
};
|
|
191
|
+
|
|
184
192
|
/** Domain name for summary display (parseEntityForDisplay) */
|
|
185
193
|
const getEmailDomainNameForDisplay = (entity) => {
|
|
186
194
|
const emailData = getEmailData(entity);
|
|
@@ -262,6 +270,8 @@ const getEmailDefaultsForDomain = (domainData) => {
|
|
|
262
270
|
* context.fieldValues has current form state (e.g. selected emailDomain)
|
|
263
271
|
* Aligned with adiona-ui/cap-campaigns-v2: default = contactInfo item with default:true, or first
|
|
264
272
|
*/
|
|
273
|
+
export { getEmailDomainGatewayMapIdByDomainId };
|
|
274
|
+
|
|
265
275
|
export const getDefaultValueForField = (fieldKey, entity, context = {}) => {
|
|
266
276
|
if (!entity) return '';
|
|
267
277
|
const fieldValues = context?.fieldValues || {};
|
|
@@ -574,7 +584,11 @@ export const parseEntityForDisplay = (entity, context = {}) => {
|
|
|
574
584
|
senderDetails: entity,
|
|
575
585
|
smsSenderId: getSmsSenderIdValue(entity),
|
|
576
586
|
smsDomain: getSmsDomainValue(entity),
|
|
587
|
+
// emailDomain here is the domain NAME (for display only) - buildChannelSettingFromFieldValues
|
|
588
|
+
// needs the numeric domainId/domainGatewayMapId, sourced separately below.
|
|
577
589
|
emailDomain: getEmailDomainNameForDisplay(entity),
|
|
590
|
+
emailDomainId: getEmailDomainValue(entity),
|
|
591
|
+
emailDomainGatewayMapId: getEmailDomainGatewayMapIdByDomainId(entity, getEmailDomainValue(entity)),
|
|
578
592
|
emailSenderId: getEmailSenderIdValue(entity),
|
|
579
593
|
viberSenderId: getSenderIdFromContactInfo(entity, 'VIBER'),
|
|
580
594
|
zaloSenderId: getSenderIdFromContactInfo(entity, 'ZALO'),
|
|
@@ -600,10 +614,14 @@ export const buildChannelSettingFromFieldValues = (fieldValues = {}) => {
|
|
|
600
614
|
}
|
|
601
615
|
if (fieldValues.emailDomain != null || fieldValues.emailSenderId != null || fieldValues.emailSenderName != null || fieldValues.emailReplyToId != null) {
|
|
602
616
|
channelSetting.EMAIL = {
|
|
603
|
-
|
|
604
|
-
|
|
617
|
+
// emailDomainId (numeric, from the auto-save-defaults path via parseEntityForDisplay)
|
|
618
|
+
// takes priority over emailDomain, which is numeric when sourced from SenderDetails'
|
|
619
|
+
// own field state but a display NAME string when sourced from parseEntityForDisplay.
|
|
620
|
+
domainId: fieldValues.emailDomainId ?? fieldValues.emailDomain,
|
|
621
|
+
domainGatewayMapId: fieldValues.emailDomainGatewayMapId,
|
|
622
|
+
senderId: fieldValues.emailSenderId,
|
|
605
623
|
senderLabel: fieldValues.emailSenderName,
|
|
606
|
-
|
|
624
|
+
replyToId: fieldValues.emailReplyToId,
|
|
607
625
|
};
|
|
608
626
|
}
|
|
609
627
|
if (fieldValues.whatsappSenderNumber != null || fieldValues.whatsappAccount != null) {
|
|
@@ -673,9 +691,10 @@ export const parseChannelSettingForDisplay = (channelSetting = {}) => {
|
|
|
673
691
|
}
|
|
674
692
|
if (channelSetting.EMAIL) {
|
|
675
693
|
out.emailDomain = channelSetting.EMAIL.domainId;
|
|
676
|
-
out.
|
|
694
|
+
out.emailDomainGatewayMapId = channelSetting.EMAIL.domainGatewayMapId;
|
|
695
|
+
out.emailSenderId = channelSetting.EMAIL.senderId;
|
|
677
696
|
out.emailSenderName = channelSetting.EMAIL.senderLabel;
|
|
678
|
-
out.emailReplyToId = channelSetting.EMAIL.
|
|
697
|
+
out.emailReplyToId = channelSetting.EMAIL.replyToId;
|
|
679
698
|
}
|
|
680
699
|
if (channelSetting.WHATSAPP) {
|
|
681
700
|
out.whatsappSenderNumber = channelSetting.WHATSAPP.senderMobNum;
|