@capillarytech/creatives-library 9.0.55-alpha.0 → 9.0.55-alpha.2
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/services/api.js +2 -10
- package/styles/containers/layout/_layoutPage.scss +2 -1
- package/translations/en.json +2 -2
- package/v2Components/AccessForbidden/_accessForbidden.scss +22 -12
- package/v2Components/AccessForbidden/index.js +27 -8
- package/v2Components/AccessForbidden/messages.js +2 -3
- package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +13 -0
- package/v2Components/CommonTestAndPreview/constants.js +2 -0
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +106 -0
- package/v2Components/CommonTestAndPreview/tests/constants.test.js +9 -1
- package/v2Components/NavigationBar/mfeModuleHeader.config.js +1 -1
- package/v2Containers/Cap/tests/__snapshots__/index.test.js.snap +2 -2
- package/v2Containers/CommunicationFlow/CommunicationFlow.js +4 -13
- package/v2Containers/CommunicationFlow/Tests/constants.test.js +85 -0
- package/v2Containers/CommunicationFlow/constants.js +8 -38
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/deliverySettingsConfig.js +3 -8
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +5 -5
- package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +6 -6
- package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +4 -4
- package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +32 -32
- package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +36 -36
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +130 -130
- package/v2Containers/SmsTrai/Create/tests/__snapshots__/index.test.js.snap +8 -8
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +33 -33
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +302 -302
package/package.json
CHANGED
package/services/api.js
CHANGED
|
@@ -701,21 +701,13 @@ export const createCentralCommsMetaId = (payload, metaType = TRANSACTION) => {
|
|
|
701
701
|
return request(url, getAPICallObject('POST', payload, false, false, false, true));
|
|
702
702
|
};
|
|
703
703
|
|
|
704
|
-
// CCS CommDefinition API (new Veyron /api/v1/commdefinition surface, proxied
|
|
705
|
-
// through cap-creatives-api's /comm-definitions). Used by CommunicationFlow's
|
|
706
|
-
// own Save action — separate from the legacy messageMeta functions above,
|
|
707
|
-
// which remain in use by CreativesContainer's own save flow (Cap/sagas.js).
|
|
704
|
+
// CCS CommDefinition API (new Veyron /api/v1/commdefinition surface, proxied through cap-creatives-api /comm-definitions); used by CommunicationFlow's Save, while legacy messageMeta functions remain for CreativesContainer's save flow (Cap/sagas.js).
|
|
708
705
|
export const createCommDefinition = (payload) => {
|
|
709
706
|
const url = `${API_ENDPOINT}/comm-definitions`;
|
|
710
707
|
return request(url, getAPICallObject('POST', payload, false, false, false, true));
|
|
711
708
|
};
|
|
712
709
|
|
|
713
|
-
// Opens a new DRAFT version with the
|
|
714
|
-
// CommDefinition (same id/referenceId) — used by CommunicationFlow's edit-mode
|
|
715
|
-
// save (re-editing a rejected alert) instead of createCommDefinition, so the
|
|
716
|
-
// alert keeps its identity rather than becoming a brand-new CommDefinition.
|
|
717
|
-
// Route verified against a real Postman sample against Veyron's own
|
|
718
|
-
// /commdefinition/{id}/edit endpoint.
|
|
710
|
+
// Opens a new DRAFT version with the supplied content on an existing CommDefinition (same id/referenceId); used by CommunicationFlow edit-mode saves to preserve the alert's identity instead of creating a new CommDefinition. Route verified against Veyron's /commdefinition/{id}/edit endpoint.
|
|
719
711
|
export const editCommDefinition = (commDefinitionId, payload) => {
|
|
720
712
|
const url = `${API_ENDPOINT}/comm-definitions/${commDefinitionId}/edit`;
|
|
721
713
|
return request(url, getAPICallObject('POST', payload, false, false, false, true));
|
|
@@ -77,7 +77,8 @@
|
|
|
77
77
|
-webkit-flex: 1 1 auto;
|
|
78
78
|
-ms-flex: 1 1 auto;
|
|
79
79
|
flex: 1;
|
|
80
|
-
height:
|
|
80
|
+
// No height: flex stretch inside `.main` sizes this. A hardcoded
|
|
81
|
+
// calc(100vh - 50px) under-counted the chrome and overhung it by ~83px.
|
|
81
82
|
overflow: auto;
|
|
82
83
|
color: #333333;
|
|
83
84
|
}
|
package/translations/en.json
CHANGED
|
@@ -63,8 +63,8 @@
|
|
|
63
63
|
"app.v2containers.SmsTrai.Edit.traiEditTitle": "Message",
|
|
64
64
|
"app.v2containers.SmsTrai.Edit.unicodeLabel": "Allow unicode characters",
|
|
65
65
|
"app.v2containers.SmsTrai.Edit.unsupportedTagsValidationError": "Unsupported tags: {unsupportedTags}. Please remove them from this message.",
|
|
66
|
-
"creatives.components.AccessForbidden.forbiddenDesc": "
|
|
67
|
-
"creatives.components.AccessForbidden.forbiddenHeader": "
|
|
66
|
+
"creatives.components.AccessForbidden.forbiddenDesc": "This page access has not been provided to you.",
|
|
67
|
+
"creatives.components.AccessForbidden.forbiddenHeader": "Access Denied",
|
|
68
68
|
"creatives.components.BreadCrumbs.header": "This is the BreadCrumbs component !",
|
|
69
69
|
"creatives.components.CapTagList.Cancel": "Cancel",
|
|
70
70
|
"creatives.components.CapTagList.Ok": "Ok",
|
|
@@ -1,14 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
}
|
|
7
|
-
.no-access-title {
|
|
1
|
+
@import '~@capillarytech/cap-ui-library/styles/_variables';
|
|
2
|
+
|
|
3
|
+
.access-denied {
|
|
4
|
+
width: 100%;
|
|
5
|
+
// This module's ComponentWrapper has no padding, unlike the other MFEs'.
|
|
8
6
|
margin-top: 16px;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
padding-top: $CAP_SPACE_80;
|
|
8
|
+
text-align: center;
|
|
9
|
+
|
|
10
|
+
.access-denied-illustration {
|
|
11
|
+
width: 20rem;
|
|
12
|
+
height: 20rem;
|
|
13
|
+
margin-bottom: $CAP_SPACE_12;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.access-denied-title-container {
|
|
17
|
+
gap: $CAP_SPACE_08;
|
|
18
|
+
margin-bottom: $CAP_SPACE_08;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.access-denied-description {
|
|
22
|
+
color: $CAP_G04;
|
|
23
|
+
}
|
|
14
24
|
}
|
|
@@ -1,18 +1,37 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { FormattedMessage } from 'react-intl';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
CapRow,
|
|
5
|
+
CapImage,
|
|
6
|
+
CapIcon,
|
|
7
|
+
CapHeading,
|
|
8
|
+
CapLabel,
|
|
9
|
+
} from '@capillarytech/cap-ui-library';
|
|
10
|
+
import accessDeniedIllustration from '@capillarytech/cap-ui-library/assets/images/createNewIllustration.svg';
|
|
4
11
|
import messages from './messages';
|
|
5
12
|
import './_accessForbidden.scss';
|
|
6
13
|
|
|
7
14
|
const AccessForbidden = () => (
|
|
8
|
-
<CapRow
|
|
9
|
-
<
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
15
|
+
<CapRow vertical align="middle" justify="center" className="access-denied">
|
|
16
|
+
<CapImage
|
|
17
|
+
src={accessDeniedIllustration}
|
|
18
|
+
className="access-denied-illustration"
|
|
19
|
+
alt=""
|
|
20
|
+
/>
|
|
21
|
+
<CapRow
|
|
22
|
+
type="flex"
|
|
23
|
+
align="middle"
|
|
24
|
+
justify="center"
|
|
25
|
+
className="access-denied-title-container"
|
|
26
|
+
>
|
|
27
|
+
<CapIcon type="rbac-lock" size="m" />
|
|
28
|
+
<CapHeading type="h2">
|
|
29
|
+
<FormattedMessage {...messages.forbiddenHeader} />
|
|
30
|
+
</CapHeading>
|
|
31
|
+
</CapRow>
|
|
32
|
+
<CapLabel type="label2" className="access-denied-description">
|
|
14
33
|
<FormattedMessage {...messages.forbiddenDesc} />
|
|
15
|
-
</
|
|
34
|
+
</CapLabel>
|
|
16
35
|
</CapRow>
|
|
17
36
|
);
|
|
18
37
|
|
|
@@ -5,11 +5,10 @@ const prefix = 'creatives.components.AccessForbidden';
|
|
|
5
5
|
export default defineMessages({
|
|
6
6
|
forbiddenHeader: {
|
|
7
7
|
id: `${prefix}.forbiddenHeader`,
|
|
8
|
-
defaultMessage:
|
|
8
|
+
defaultMessage: 'Access Denied',
|
|
9
9
|
},
|
|
10
10
|
forbiddenDesc: {
|
|
11
11
|
id: `${prefix}.forbiddenDesc`,
|
|
12
|
-
defaultMessage:
|
|
13
|
-
'Please contact your organisation administrator to request access.',
|
|
12
|
+
defaultMessage: 'This page access has not been provided to you.',
|
|
14
13
|
},
|
|
15
14
|
});
|
|
@@ -139,6 +139,19 @@ const UnifiedPreview = ({
|
|
|
139
139
|
);
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
case CHANNELS.LINE:
|
|
143
|
+
// LINE currently carries a single text message; CCS stores lineMessageContent.messageBody as JSON for LINE's native messages[] (see testAndPreviewDataTransform.js), already reduced to plain text before this point, so reuse the SMS chat-bubble preview. Rich LINE types (image/video/sticker/imageMap/carousel/flex) are not specially rendered yet.
|
|
144
|
+
return (
|
|
145
|
+
<SmsPreviewContent
|
|
146
|
+
content={typeof content === 'string' ? content : (content?.resolvedBody || '')}
|
|
147
|
+
device={device}
|
|
148
|
+
isUpdating={isUpdating}
|
|
149
|
+
error={error}
|
|
150
|
+
formatMessage={formatMessage}
|
|
151
|
+
showHeader={showHeader}
|
|
152
|
+
/>
|
|
153
|
+
);
|
|
154
|
+
|
|
142
155
|
case CHANNELS.WHATSAPP: {
|
|
143
156
|
// WhatsApp content is an object with templateMsg, media, CTA, etc.
|
|
144
157
|
const whatsappContent = typeof content === 'object' ? content : {};
|
|
@@ -85,6 +85,7 @@ export const CHANNELS = {
|
|
|
85
85
|
VIBER: 'VIBER',
|
|
86
86
|
ZALO: 'ZALO',
|
|
87
87
|
WEBPUSH: 'WEBPUSH',
|
|
88
|
+
LINE: 'LINE',
|
|
88
89
|
};
|
|
89
90
|
|
|
90
91
|
/** Unified preview tab keys when RCS shows RCS + SMS fallback panes. */
|
|
@@ -100,6 +101,7 @@ export const CHANNELS_USING_ANDROID_PREVIEW_DEVICE = [
|
|
|
100
101
|
CHANNELS.INAPP,
|
|
101
102
|
CHANNELS.MOBILEPUSH,
|
|
102
103
|
CHANNELS.VIBER,
|
|
104
|
+
CHANNELS.LINE,
|
|
103
105
|
];
|
|
104
106
|
|
|
105
107
|
/** RCS createMessageMeta test payload — rich card envelope literals. */
|
|
@@ -479,6 +479,112 @@ describe('UnifiedPreview', () => {
|
|
|
479
479
|
});
|
|
480
480
|
});
|
|
481
481
|
|
|
482
|
+
describe('Channel Routing - LINE', () => {
|
|
483
|
+
it('should render SmsPreviewContent for LINE channel with string content', () => {
|
|
484
|
+
const props = {
|
|
485
|
+
...defaultProps,
|
|
486
|
+
channel: CHANNELS.LINE,
|
|
487
|
+
content: 'Hello world',
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
render(
|
|
491
|
+
<TestWrapper>
|
|
492
|
+
<ComponentToRender {...props} />
|
|
493
|
+
</TestWrapper>
|
|
494
|
+
);
|
|
495
|
+
|
|
496
|
+
expect(screen.getByTestId('sms-preview')).toBeTruthy();
|
|
497
|
+
expect(screen.getByTestId('sms-content')).toHaveTextContent('Hello world');
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
it('should handle object content with resolvedBody for LINE channel', () => {
|
|
501
|
+
const props = {
|
|
502
|
+
...defaultProps,
|
|
503
|
+
channel: CHANNELS.LINE,
|
|
504
|
+
content: {
|
|
505
|
+
resolvedBody: 'some text',
|
|
506
|
+
},
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
render(
|
|
510
|
+
<TestWrapper>
|
|
511
|
+
<ComponentToRender {...props} />
|
|
512
|
+
</TestWrapper>
|
|
513
|
+
);
|
|
514
|
+
|
|
515
|
+
expect(screen.getByTestId('sms-preview')).toBeTruthy();
|
|
516
|
+
expect(screen.getByTestId('sms-content')).toHaveTextContent('some text');
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
it('should not crash and render empty content for LINE channel when content is undefined', () => {
|
|
520
|
+
const props = {
|
|
521
|
+
...defaultProps,
|
|
522
|
+
channel: CHANNELS.LINE,
|
|
523
|
+
content: undefined,
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
render(
|
|
527
|
+
<TestWrapper>
|
|
528
|
+
<ComponentToRender {...props} />
|
|
529
|
+
</TestWrapper>
|
|
530
|
+
);
|
|
531
|
+
|
|
532
|
+
expect(screen.getByTestId('sms-preview')).toBeTruthy();
|
|
533
|
+
expect(screen.getByTestId('sms-content')).toHaveTextContent('');
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
it('should not crash and render empty content for LINE channel when content is null', () => {
|
|
537
|
+
const props = {
|
|
538
|
+
...defaultProps,
|
|
539
|
+
channel: CHANNELS.LINE,
|
|
540
|
+
content: null,
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
render(
|
|
544
|
+
<TestWrapper>
|
|
545
|
+
<ComponentToRender {...props} />
|
|
546
|
+
</TestWrapper>
|
|
547
|
+
);
|
|
548
|
+
|
|
549
|
+
expect(screen.getByTestId('sms-preview')).toBeTruthy();
|
|
550
|
+
expect(screen.getByTestId('sms-content')).toHaveTextContent('');
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
it('should render empty content for LINE channel when content is not a string and has no resolvedBody', () => {
|
|
554
|
+
const props = {
|
|
555
|
+
...defaultProps,
|
|
556
|
+
channel: CHANNELS.LINE,
|
|
557
|
+
content: { someOtherField: 'value' },
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
render(
|
|
561
|
+
<TestWrapper>
|
|
562
|
+
<ComponentToRender {...props} />
|
|
563
|
+
</TestWrapper>
|
|
564
|
+
);
|
|
565
|
+
|
|
566
|
+
expect(screen.getByTestId('sms-preview')).toBeTruthy();
|
|
567
|
+
expect(screen.getByTestId('sms-content')).toHaveTextContent('');
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
it('should not render the Unknown Channel fallback for LINE channel', () => {
|
|
571
|
+
const props = {
|
|
572
|
+
...defaultProps,
|
|
573
|
+
channel: CHANNELS.LINE,
|
|
574
|
+
content: 'Hello world',
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
render(
|
|
578
|
+
<TestWrapper>
|
|
579
|
+
<ComponentToRender {...props} />
|
|
580
|
+
</TestWrapper>
|
|
581
|
+
);
|
|
582
|
+
|
|
583
|
+
expect(screen.queryByText('Unknown Channel')).toBeNull();
|
|
584
|
+
expect(screen.getByTestId('sms-preview')).toBeTruthy();
|
|
585
|
+
});
|
|
586
|
+
});
|
|
587
|
+
|
|
482
588
|
describe('Channel Routing - WHATSAPP', () => {
|
|
483
589
|
it('should render WhatsAppPreviewContent for WHATSAPP channel', () => {
|
|
484
590
|
const props = {
|
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
CLEAR_PREVIEW_ERRORS,
|
|
43
43
|
// Channel Constants
|
|
44
44
|
CHANNELS,
|
|
45
|
+
CHANNELS_USING_ANDROID_PREVIEW_DEVICE,
|
|
45
46
|
// Device Constants
|
|
46
47
|
DESKTOP,
|
|
47
48
|
MOBILE,
|
|
@@ -180,15 +181,22 @@ describe('CommonTestAndPreview Constants', () => {
|
|
|
180
181
|
expect(CHANNELS.VIBER).toBe('VIBER');
|
|
181
182
|
expect(CHANNELS.ZALO).toBe('ZALO');
|
|
182
183
|
expect(CHANNELS.WEBPUSH).toBe('WEBPUSH');
|
|
184
|
+
expect(CHANNELS.LINE).toBe('LINE');
|
|
183
185
|
});
|
|
184
186
|
|
|
185
187
|
it('should have all required channel keys', () => {
|
|
186
|
-
const expectedChannels = ['EMAIL', 'SMS', 'RCS', 'WHATSAPP', 'INAPP', 'MOBILEPUSH', 'VIBER', 'ZALO', 'WEBPUSH'];
|
|
188
|
+
const expectedChannels = ['EMAIL', 'SMS', 'RCS', 'WHATSAPP', 'INAPP', 'MOBILEPUSH', 'VIBER', 'ZALO', 'WEBPUSH', 'LINE'];
|
|
187
189
|
expect(Object.keys(CHANNELS)).toEqual(expect.arrayContaining(expectedChannels));
|
|
188
190
|
expect(Object.keys(CHANNELS).length).toBe(expectedChannels.length);
|
|
189
191
|
});
|
|
190
192
|
});
|
|
191
193
|
|
|
194
|
+
describe('CHANNELS_USING_ANDROID_PREVIEW_DEVICE', () => {
|
|
195
|
+
it('should include CHANNELS.LINE', () => {
|
|
196
|
+
expect(CHANNELS_USING_ANDROID_PREVIEW_DEVICE).toContain(CHANNELS.LINE);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
192
200
|
describe('Device Constants', () => {
|
|
193
201
|
it('should export all device constants with correct values', () => {
|
|
194
202
|
expect(DESKTOP).toBe('desktop');
|
|
@@ -4,7 +4,7 @@ export const MFE_MODULE_HEADER = {
|
|
|
4
4
|
header: {
|
|
5
5
|
name: 'app.commonUtils.capUiLibrary.navigationSPA.moduleHeader.creatives.default.name',
|
|
6
6
|
description: 'app.commonUtils.capUiLibrary.navigationSPA.moduleHeader.creatives.default.description',
|
|
7
|
-
showBorder:
|
|
7
|
+
showBorder: true,
|
|
8
8
|
showSettings: false,
|
|
9
9
|
},
|
|
10
10
|
landingRoutes: [
|
|
@@ -141,8 +141,8 @@ exports[`<Cap /> should render correct component 1`] = `
|
|
|
141
141
|
"app.v2containers.SmsTrai.Edit.traiEditTitle": "Message",
|
|
142
142
|
"app.v2containers.SmsTrai.Edit.unicodeLabel": "Allow unicode characters",
|
|
143
143
|
"app.v2containers.SmsTrai.Edit.unsupportedTagsValidationError": "Unsupported tags: {unsupportedTags}. Please remove them from this message.",
|
|
144
|
-
"creatives.components.AccessForbidden.forbiddenDesc": "
|
|
145
|
-
"creatives.components.AccessForbidden.forbiddenHeader": "
|
|
144
|
+
"creatives.components.AccessForbidden.forbiddenDesc": "This page access has not been provided to you.",
|
|
145
|
+
"creatives.components.AccessForbidden.forbiddenHeader": "Access Denied",
|
|
146
146
|
"creatives.components.BreadCrumbs.header": "This is the BreadCrumbs component !",
|
|
147
147
|
"creatives.components.CapTagList.Cancel": "Cancel",
|
|
148
148
|
"creatives.components.CapTagList.Ok": "Ok",
|
|
@@ -67,15 +67,11 @@ const hasDeliverySettingForChannel = (channelSetting, channel) => {
|
|
|
67
67
|
return !!settings && Object.values(settings).some((v) => v !== null && v !== '' && v !== undefined);
|
|
68
68
|
};
|
|
69
69
|
|
|
70
|
-
// createCommDefinition resolves (
|
|
71
|
-
// for 4xx/5xx responses — see api.js's request()/checkStatus. A duplicate
|
|
72
|
-
// referenceId within the org comes back as this specific 409.
|
|
70
|
+
// createCommDefinition resolves with the CCS error envelope for 4xx/5xx responses (see api.js request()/checkStatus); duplicate referenceId within the org returns a 409.
|
|
73
71
|
const isDuplicateReferenceIdError = (res) => res?.success === false && res?.status?.message === 'REFERENCE_ID_EXISTS';
|
|
74
72
|
|
|
75
73
|
/**
|
|
76
|
-
* CCS requires referenceId on create
|
|
77
|
-
* mandatory but referenceId is optional in every consumer's own form. Generate
|
|
78
|
-
* a stable fallback from the name rather than failing the save.
|
|
74
|
+
* CCS requires referenceId on create, while consumers require only the Alert/comm name; generate a stable fallback from the name when referenceId is omitted instead of failing the save.
|
|
79
75
|
*/
|
|
80
76
|
const buildCcsReferenceId = (name) => {
|
|
81
77
|
const slug = (name || 'COMM')
|
|
@@ -215,19 +211,14 @@ const CommunicationFlow = ({
|
|
|
215
211
|
singleChannelStrategy: {
|
|
216
212
|
variant: {
|
|
217
213
|
channel,
|
|
218
|
-
// `channel`
|
|
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.
|
|
214
|
+
// `channel` must be spread last in both objects so CCS's normalized enum (e.g. `PUSH`) overrides the UI's internal identifier (e.g. `MOBILEPUSH`) from templateData.
|
|
222
215
|
...(contentKey && { [contentKey]: { ...contentPayload, channel } }),
|
|
223
216
|
...(deliveryKey && { [deliveryKey]: { channelSettings: { ...channelSettings, channel } } }),
|
|
224
217
|
},
|
|
225
218
|
},
|
|
226
219
|
};
|
|
227
220
|
|
|
228
|
-
// Editing an existing (e.g. rejected)
|
|
229
|
-
// opens a new DRAFT version on that SAME CommDefinition instead of creating a
|
|
230
|
-
// brand-new one, so the alert keeps its id/referenceId across the edit.
|
|
221
|
+
// Editing an existing alert (e.g. rejected) uses config.context.existingCommDefinitionId to create a new DRAFT version on the same CommDefinition, preserving its id/referenceId instead of creating a new alert.
|
|
231
222
|
const { existingCommDefinitionId } = config?.context || {};
|
|
232
223
|
|
|
233
224
|
try {
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CCS_CONTENT_TRANSFORMS,
|
|
3
|
+
CCS_CHANNEL_NAME_MAP,
|
|
4
|
+
CCS_CHANNEL_CONTENT_KEY_MAP,
|
|
5
|
+
CCS_CHANNEL_DELIVERY_KEY_MAP,
|
|
6
|
+
} from '../constants';
|
|
7
|
+
|
|
8
|
+
describe('CCS_CONTENT_TRANSFORMS', () => {
|
|
9
|
+
describe('EMAIL', () => {
|
|
10
|
+
it('prefers emailBody over emailHtml when both are present', () => {
|
|
11
|
+
expect(
|
|
12
|
+
CCS_CONTENT_TRANSFORMS.EMAIL({
|
|
13
|
+
emailSubject: 'Subject', emailBody: '<p>Body</p>', emailHtml: '<p>Html</p>',
|
|
14
|
+
}),
|
|
15
|
+
).toEqual({ messageSubject: 'Subject', messageBody: '<p>Body</p>' });
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('falls back to emailHtml when emailBody is absent', () => {
|
|
19
|
+
expect(
|
|
20
|
+
CCS_CONTENT_TRANSFORMS.EMAIL({ emailSubject: 'Subject', emailHtml: '<p>Html</p>' }),
|
|
21
|
+
).toEqual({ messageSubject: 'Subject', messageBody: '<p>Html</p>' });
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('defaults templateData to {} when called with no argument', () => {
|
|
25
|
+
expect(CCS_CONTENT_TRANSFORMS.EMAIL()).toEqual({
|
|
26
|
+
messageSubject: undefined, messageBody: undefined,
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('SMS', () => {
|
|
32
|
+
it('uses messageBody when present', () => {
|
|
33
|
+
expect(CCS_CONTENT_TRANSFORMS.SMS({ messageBody: 'Hello' })).toEqual({ message: 'Hello' });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('falls back to an empty string when messageBody is absent', () => {
|
|
37
|
+
expect(CCS_CONTENT_TRANSFORMS.SMS({})).toEqual({ message: '' });
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('defaults templateData to {} when called with no argument', () => {
|
|
41
|
+
expect(CCS_CONTENT_TRANSFORMS.SMS()).toEqual({ message: '' });
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('WEBPUSH', () => {
|
|
46
|
+
it('extracts fields from the nested messageContent.content wrapper', () => {
|
|
47
|
+
expect(
|
|
48
|
+
CCS_CONTENT_TRANSFORMS.WEBPUSH({
|
|
49
|
+
messageContent: {
|
|
50
|
+
content: {
|
|
51
|
+
messageSubject: 'Subject', accountId: 'acc_1', content: 'Body', offers: [{ id: 1 }],
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
}),
|
|
55
|
+
).toEqual({ messageSubject: 'Subject', accountId: 'acc_1', content: 'Body' });
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('falls back to the flat templateData when messageContent.content is absent', () => {
|
|
59
|
+
expect(
|
|
60
|
+
CCS_CONTENT_TRANSFORMS.WEBPUSH({
|
|
61
|
+
messageSubject: 'Subject', accountId: 'acc_1', content: 'Body',
|
|
62
|
+
}),
|
|
63
|
+
).toEqual({ messageSubject: 'Subject', accountId: 'acc_1', content: 'Body' });
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('defaults templateData to {} when called with no argument', () => {
|
|
67
|
+
expect(CCS_CONTENT_TRANSFORMS.WEBPUSH()).toEqual({
|
|
68
|
+
messageSubject: undefined, accountId: undefined, content: undefined,
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('CCS_CHANNEL_NAME_MAP', () => {
|
|
75
|
+
it('normalizes the UI\'s MOBILEPUSH identifier to CCS\'s PUSH enum value', () => {
|
|
76
|
+
expect(CCS_CHANNEL_NAME_MAP.MOBILEPUSH).toBe('PUSH');
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('CCS_CHANNEL_CONTENT_KEY_MAP / CCS_CHANNEL_DELIVERY_KEY_MAP', () => {
|
|
81
|
+
it('maps the PUSH channel to its mpush-prefixed content and delivery keys', () => {
|
|
82
|
+
expect(CCS_CHANNEL_CONTENT_KEY_MAP.PUSH).toBe('mpushMessageContent');
|
|
83
|
+
expect(CCS_CHANNEL_DELIVERY_KEY_MAP.PUSH).toBe('mpushDeliverySettings');
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -38,21 +38,15 @@ export const CHANNEL_PRIORITY = 'CHANNEL_PRIORITY';
|
|
|
38
38
|
export const AB_TEST = 'AB_TEST';
|
|
39
39
|
export const SINGLE_TEMPLATE = 'SINGLE_TEMPLATE';
|
|
40
40
|
|
|
41
|
-
// CCS CommDefinition strategyType — distinct from SINGLE_TEMPLATE above, which
|
|
42
|
-
// is this UI's own communicationStrategy value. Only SINGLE is supported this
|
|
43
|
-
// phase; CCS create is skipped entirely for CHANNEL_PRIORITY/AB_TEST.
|
|
41
|
+
// CCS CommDefinition strategyType — distinct from SINGLE_TEMPLATE above, which is this UI's own communicationStrategy value. Only SINGLE is supported this phase; CCS create is skipped entirely for CHANNEL_PRIORITY/AB_TEST.
|
|
44
42
|
export const CCS_STRATEGY_TYPE_SINGLE = 'SINGLE';
|
|
45
43
|
|
|
46
|
-
// Mirrors CCS
|
|
47
|
-
// Design doc sample payloads, verified against real create responses). Kept
|
|
48
|
-
// separate from CHANNEL_CONTENT_KEY_MAP/CHANNEL_DELIVERY_KEY_MAP below, which
|
|
49
|
-
// are the legacy messageMeta payload's keys.
|
|
44
|
+
// Mirrors CCS SingleChannelStrategy field names exactly (per Cap Notify API design samples and real create responses); kept separate from the legacy messageMeta CHANNEL_CONTENT_KEY_MAP/CHANNEL_DELIVERY_KEY_MAP.
|
|
50
45
|
export const CCS_CHANNEL_CONTENT_KEY_MAP = {
|
|
51
46
|
SMS: 'smsMessageContent',
|
|
52
47
|
EMAIL: 'emailMessageContent',
|
|
53
48
|
WHATSAPP: 'whatsappMessageContent',
|
|
54
|
-
// CCS's channel enum value for mobile push is 'PUSH' (verified against real
|
|
55
|
-
// create responses), even though the content/delivery keys stay 'mpush*'.
|
|
49
|
+
// CCS's channel enum value for mobile push is 'PUSH' (verified against real create responses), even though the content/delivery keys stay 'mpush*'.
|
|
56
50
|
PUSH: 'mpushMessageContent',
|
|
57
51
|
INAPP: 'inAppMessageContent',
|
|
58
52
|
ANDROID: 'androidMessageContent',
|
|
@@ -64,34 +58,17 @@ export const CCS_CHANNEL_CONTENT_KEY_MAP = {
|
|
|
64
58
|
RCS: 'rcsMessageContent',
|
|
65
59
|
};
|
|
66
60
|
|
|
67
|
-
// contentItem.templateData
|
|
68
|
-
// editor (messageMeta-oriented field names) — for channels whose legacy field
|
|
69
|
-
// names don't match CCS's own content schema, transform before sending rather
|
|
70
|
-
// than dumping templateData as-is. EMAIL: legacy stores the subject as
|
|
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`.
|
|
61
|
+
// contentItem.templateData uses legacy messageMeta field names, so transform mismatched channel fields before sending to CCS; for EMAIL, map legacy `emailSubject`/`emailBody` to CCS's `messageSubject`/`messageBody`.
|
|
76
62
|
export const CCS_CONTENT_TRANSFORMS = {
|
|
77
63
|
EMAIL: (templateData = {}) => ({
|
|
78
64
|
messageSubject: templateData.emailSubject,
|
|
79
65
|
messageBody: templateData.emailBody || templateData.emailHtml,
|
|
80
66
|
}),
|
|
81
|
-
// SMS: legacy stores
|
|
82
|
-
// CreativesContainer/index.js's getCreativesData SMS case, and the existing
|
|
83
|
-
// legacy smsMessageContent builder in utils/transformerUtils.js, both of
|
|
84
|
-
// which read/write this same field) — CCS's schema expects it as `message`
|
|
85
|
-
// (confirmed against a real CCS response showing smsMessageContent.message
|
|
86
|
-
// stayed null when messageBody was sent instead).
|
|
67
|
+
// SMS: legacy stores text as `messageBody`, while CCS expects `message`; map the field explicitly to avoid CCS returning smsMessageContent.message as null.
|
|
87
68
|
SMS: (templateData = {}) => ({
|
|
88
69
|
message: templateData.messageBody || '',
|
|
89
70
|
}),
|
|
90
|
-
// WEBPUSH: the legacy editor nests the
|
|
91
|
-
// messageContent.content (see CommunicationFlowCard.js's own comment on
|
|
92
|
-
// this same wrapping, for the edit-mode case) — extracting it flat here
|
|
93
|
-
// matches CCS's shape ({ messageSubject, accountId, content }); `offers`
|
|
94
|
-
// (a legacy-only field) is deliberately dropped, not part of CCS's schema.
|
|
71
|
+
// WEBPUSH: the legacy editor nests data under messageContent.content; flatten it to CCS's { messageSubject, accountId, content } shape and drop the legacy-only `offers` field.
|
|
95
72
|
WEBPUSH: (templateData = {}) => {
|
|
96
73
|
const inner = templateData.messageContent?.content || templateData;
|
|
97
74
|
return {
|
|
@@ -117,19 +94,12 @@ export const CCS_CHANNEL_DELIVERY_KEY_MAP = {
|
|
|
117
94
|
RCS: 'rcsDeliverySettings',
|
|
118
95
|
};
|
|
119
96
|
|
|
120
|
-
// The UI
|
|
121
|
-
// (see CreativesContainer/constants.js MOBILE_PUSH), but CCS's channel enum
|
|
122
|
-
// value is 'PUSH' (verified against real create responses — the content/
|
|
123
|
-
// delivery KEYS still say 'mpush*', only the enum VALUE is 'PUSH') —
|
|
124
|
-
// normalize before building the CCS payload. Every other channel identifier
|
|
125
|
-
// matches CCS as-is.
|
|
97
|
+
// The UI uses `MOBILEPUSH`, while CCS uses `PUSH` for the channel enum (verified against real create responses); normalize this value before building the CCS payload, while retaining the `mpush*` content/delivery keys. All other channel identifiers pass through unchanged.
|
|
126
98
|
export const CCS_CHANNEL_NAME_MAP = {
|
|
127
99
|
MOBILEPUSH: 'PUSH',
|
|
128
100
|
};
|
|
129
101
|
|
|
130
|
-
// Channel config
|
|
131
|
-
// paneKey: TemplatesV2 defaultPanes object key (for channelsToHide)
|
|
132
|
-
// channelProp: CreativesContainer channel prop (must match pane.key for tab to be active)
|
|
102
|
+
// Channel config — single source of truth for CreativesContainer and TemplatesV2; paneKey is the TemplatesV2 defaultPanes key used by channelsToHide, and channelProp is the CreativesContainer channel prop that must match pane.key for the tab to be active.
|
|
133
103
|
export const CHANNELS = [
|
|
134
104
|
{
|
|
135
105
|
value: 'SMS',
|
|
@@ -181,9 +181,7 @@ 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
|
|
185
|
-
// (distinct from `domainProperties.id`, which is the numeric domainId) - the
|
|
186
|
-
// domain-gateway-mapping id for the selected domain entry.
|
|
184
|
+
// CCS's domainGatewayMapId is the domainProperties API's outer `id`, distinct from domainProperties.id (the numeric domainId); it identifies the domain-gateway mapping for the selected domain.
|
|
187
185
|
const getEmailDomainGatewayMapIdByDomainId = (entity, domainId) => {
|
|
188
186
|
const domainEntry = getEmailDomainById(entity, domainId);
|
|
189
187
|
return domainEntry?.id ?? '';
|
|
@@ -584,8 +582,7 @@ export const parseEntityForDisplay = (entity, context = {}) => {
|
|
|
584
582
|
senderDetails: entity,
|
|
585
583
|
smsSenderId: getSmsSenderIdValue(entity),
|
|
586
584
|
smsDomain: getSmsDomainValue(entity),
|
|
587
|
-
// emailDomain here is the domain NAME (for display only) - buildChannelSettingFromFieldValues
|
|
588
|
-
// needs the numeric domainId/domainGatewayMapId, sourced separately below.
|
|
585
|
+
// emailDomain here is the domain NAME (for display only) - buildChannelSettingFromFieldValues needs the numeric domainId/domainGatewayMapId, sourced separately below.
|
|
589
586
|
emailDomain: getEmailDomainNameForDisplay(entity),
|
|
590
587
|
emailDomainId: getEmailDomainValue(entity),
|
|
591
588
|
emailDomainGatewayMapId: getEmailDomainGatewayMapIdByDomainId(entity, getEmailDomainValue(entity)),
|
|
@@ -614,9 +611,7 @@ export const buildChannelSettingFromFieldValues = (fieldValues = {}) => {
|
|
|
614
611
|
}
|
|
615
612
|
if (fieldValues.emailDomain != null || fieldValues.emailSenderId != null || fieldValues.emailSenderName != null || fieldValues.emailReplyToId != null) {
|
|
616
613
|
channelSetting.EMAIL = {
|
|
617
|
-
// emailDomainId (numeric, from
|
|
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.
|
|
614
|
+
// emailDomainId (numeric, from auto-save defaults via parseEntityForDisplay) takes precedence over emailDomain, which may be a numeric ID in SenderDetails state or a display name from parseEntityForDisplay.
|
|
620
615
|
domainId: fieldValues.emailDomainId ?? fieldValues.emailDomain,
|
|
621
616
|
domainGatewayMapId: fieldValues.emailDomainGatewayMapId,
|
|
622
617
|
senderId: fieldValues.emailSenderId,
|
|
@@ -89,7 +89,7 @@ exports[`Test SlideBoxContent container campaign message, add creative click rcs
|
|
|
89
89
|
/>
|
|
90
90
|
}
|
|
91
91
|
show={true}
|
|
92
|
-
size="size-
|
|
92
|
+
size="size-l"
|
|
93
93
|
/>
|
|
94
94
|
</CreativesSlideBoxWrapper>
|
|
95
95
|
`;
|
|
@@ -183,7 +183,7 @@ exports[`Test SlideBoxContent container campaign message, add creative click wha
|
|
|
183
183
|
/>
|
|
184
184
|
}
|
|
185
185
|
show={true}
|
|
186
|
-
size="size-
|
|
186
|
+
size="size-l"
|
|
187
187
|
/>
|
|
188
188
|
</CreativesSlideBoxWrapper>
|
|
189
189
|
`;
|
|
@@ -320,7 +320,7 @@ exports[`Test SlideBoxContent container campaign message, whatsapp edit all data
|
|
|
320
320
|
/>
|
|
321
321
|
}
|
|
322
322
|
show={false}
|
|
323
|
-
size="size-
|
|
323
|
+
size="size-l"
|
|
324
324
|
/>
|
|
325
325
|
</CreativesSlideBoxWrapper>
|
|
326
326
|
`;
|
|
@@ -457,7 +457,7 @@ exports[`Test SlideBoxContent container campaign message, whatsapp edit min data
|
|
|
457
457
|
/>
|
|
458
458
|
}
|
|
459
459
|
show={false}
|
|
460
|
-
size="size-
|
|
460
|
+
size="size-l"
|
|
461
461
|
/>
|
|
462
462
|
</CreativesSlideBoxWrapper>
|
|
463
463
|
`;
|
|
@@ -594,7 +594,7 @@ exports[`Test SlideBoxContent container it should clear the url, on channel chan
|
|
|
594
594
|
/>
|
|
595
595
|
}
|
|
596
596
|
show={false}
|
|
597
|
-
size="size-
|
|
597
|
+
size="size-l"
|
|
598
598
|
/>
|
|
599
599
|
</CreativesSlideBoxWrapper>
|
|
600
600
|
`;
|