@capillarytech/creatives-library 9.1.1 → 9.1.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/utils/cdnTransformation.js +78 -36
- package/utils/tests/__snapshots__/cdnTransformation.test.js.snap +2 -2
- package/utils/tests/cdnTransformation.mockdata.js +6 -6
- package/utils/tests/cdnTransformation.test.js +316 -16
- package/v2Components/CommonTestAndPreview/sagas.js +3 -2
- package/v2Components/CommonTestAndPreview/tests/sagas.test.js +55 -0
package/package.json
CHANGED
|
@@ -34,15 +34,20 @@ const HTTPS_STR = "https://"
|
|
|
34
34
|
const ALLOWED_EXTENSIONS_STR = ".*(?:jpg|png|jpeg|gif)$"
|
|
35
35
|
const ANY_STR = ".*"
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Floor and ceiling for the EMAIL quality (config value and size-based override alike). The value itself comes from CDN_IMG_QUALITY_CFG.EMAIL.
|
|
39
|
+
*/
|
|
40
|
+
export const EMAIL_MIN_QUALITY = 80;
|
|
41
|
+
export const EMAIL_MAX_QUALITY = 100;
|
|
42
|
+
|
|
37
43
|
/**
|
|
38
44
|
* CHANNEL_CONFIGS maintains the transformation details for each channel (and optionally subchannel ex. FACEBOOK/LINE)
|
|
39
45
|
* In case of SUBCHANNELS, they are nested inside CHANNEL.
|
|
46
|
+
* EMAIL has no width/height: images keep their uploaded resolution (resizing to the <img> attributes looked pixelated on retina screens).
|
|
40
47
|
*/
|
|
41
48
|
const CHANNEL_CONFIGS = {
|
|
42
49
|
EMAIL: {
|
|
43
50
|
transformations: {
|
|
44
|
-
width: SPECIFIED,
|
|
45
|
-
height: SPECIFIED,
|
|
46
51
|
format: FORMAT_TYPES.AUTO,
|
|
47
52
|
quality: QUALITY_TYPE.DECREASE,
|
|
48
53
|
},
|
|
@@ -137,10 +142,19 @@ const getClusterSpecificS3CdnConfigFromUrl = (url) => {
|
|
|
137
142
|
}
|
|
138
143
|
|
|
139
144
|
/**
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
|
|
143
|
-
|
|
145
|
+
* Clamps an email image quality into [EMAIL_MIN_QUALITY, EMAIL_MAX_QUALITY].
|
|
146
|
+
* Non-numeric input is returned as-is so the caller's existing fallbacks still apply.
|
|
147
|
+
*/
|
|
148
|
+
export const clampEmailQuality = (quality) => {
|
|
149
|
+
if (!isNumber(quality) || Number.isNaN(quality)) return quality;
|
|
150
|
+
return Math.min(EMAIL_MAX_QUALITY, Math.max(EMAIL_MIN_QUALITY, quality));
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
*
|
|
155
|
+
* @param {string} url : required
|
|
156
|
+
* @param {number} height : optional | used only by channels whose CHANNEL_CONFIGS has height: SPECIFIED. Ignored for EMAIL (never resized).
|
|
157
|
+
* @param {number} width : optional | used only by channels whose CHANNEL_CONFIGS has width: SPECIFIED. Ignored for EMAIL (never resized).
|
|
144
158
|
* @param {string} channelName : required
|
|
145
159
|
* @param {string} channelSubType : optional | required when channel has multiple subtypes
|
|
146
160
|
*
|
|
@@ -194,7 +208,15 @@ export const getCdnUrl = ({
|
|
|
194
208
|
//remove all leading and trailing forward slash with empty string, if leading/trailing forward slash is present in node env by mistake then url will not be correct hence removing them.
|
|
195
209
|
CREATIVES_S3_BUCKET_PATH = CREATIVES_S3_BUCKET_PATH?.replace(/^\/|\/$/g, '');
|
|
196
210
|
|
|
197
|
-
const
|
|
211
|
+
const isEmail = channelName === EMAIL;
|
|
212
|
+
let QUALITY = CREATIVES_CDN_QUALITY_CONFIG?.[channelName] || CREATIVES_CDN_QUALITY_CONFIG?.DEFAULT;
|
|
213
|
+
let overrideQuality = emailOverrideQuality;
|
|
214
|
+
//Email only: never go below EMAIL_MIN_QUALITY, whichever source the quality came from.
|
|
215
|
+
if (isEmail) {
|
|
216
|
+
QUALITY = clampEmailQuality(QUALITY);
|
|
217
|
+
overrideQuality = clampEmailQuality(overrideQuality);
|
|
218
|
+
}
|
|
219
|
+
//assetKey is everything after the bucket path, so an already transformed cdn url is rebuilt from scratch (old options dropped).
|
|
198
220
|
const [_, assetKey] = url.split(CREATIVES_S3_BUCKET_PATH);
|
|
199
221
|
|
|
200
222
|
const regexWithExtension = new RegExp(HTTPS_STR + ANY_STR + CREATIVES_S3_BUCKET_PATH + ANY_STR + ALLOWED_EXTENSIONS_STR)
|
|
@@ -255,10 +277,10 @@ export const getCdnUrl = ({
|
|
|
255
277
|
case "quality":
|
|
256
278
|
if (transformationParamVal === QUALITY_TYPE?.DECREASE && QUALITY && typeof QUALITY === "number") {
|
|
257
279
|
if (
|
|
258
|
-
|
|
259
|
-
typeof
|
|
280
|
+
overrideQuality &&
|
|
281
|
+
typeof overrideQuality === "number"
|
|
260
282
|
) {
|
|
261
|
-
cdnUrl += `${transformationParam}=${
|
|
283
|
+
cdnUrl += `${transformationParam}=${overrideQuality},`;
|
|
262
284
|
} else {
|
|
263
285
|
cdnUrl += `${transformationParam}=${QUALITY},`;
|
|
264
286
|
}
|
|
@@ -343,20 +365,40 @@ export const updateImagesInHtml = async (htmlContents) => {
|
|
|
343
365
|
* @param {*} contents
|
|
344
366
|
* @returns
|
|
345
367
|
* Transforms the email template's html by images replacing the s3 url / cdn url with cdn url.
|
|
368
|
+
* Covers versions.base and every versions.history entry for every selected language (the api uploads history[0] per language),
|
|
369
|
+
* transforming each language node once even when base and history[0] are the same object.
|
|
346
370
|
*/
|
|
347
371
|
export function transformEmailTemplates(contents){
|
|
348
372
|
const contentsCopy = cloneDeep(contents);
|
|
349
373
|
try {
|
|
350
374
|
const base = contentsCopy?.versions?.base;
|
|
375
|
+
const history = contentsCopy?.versions?.history;
|
|
351
376
|
const languages = base?.selectedLanguages;
|
|
377
|
+
const versionNodes = [base].concat(Array.isArray(history) ? history : []).filter(Boolean);
|
|
378
|
+
const transformedLangNodes = new Set();
|
|
379
|
+
const resultByHtml = new Map(); //identical html in base and history is transformed once (one file-size lookup)
|
|
352
380
|
let promises = [];
|
|
353
|
-
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
381
|
+
versionNodes.forEach((versionNode) => {
|
|
382
|
+
const nodeLanguages = versionNode?.selectedLanguages || languages;
|
|
383
|
+
nodeLanguages?.forEach((lang) => {
|
|
384
|
+
const langNode = versionNode?.[lang];
|
|
385
|
+
if (!langNode || typeof langNode !== "object") {
|
|
386
|
+
//malformed input (listed language without a node): caller returns the contents untouched, as before.
|
|
387
|
+
throw new Error("transformEmailTemplates: missing language node " + lang);
|
|
388
|
+
}
|
|
389
|
+
if (transformedLangNodes.has(langNode)) return;
|
|
390
|
+
transformedLangNodes.add(langNode);
|
|
391
|
+
const htmlContents = langNode?.["template-content"];
|
|
392
|
+
if (typeof htmlContents !== "string") return;
|
|
393
|
+
if (!resultByHtml.has(htmlContents)) {
|
|
394
|
+
resultByHtml.set(htmlContents, updateImagesInHtml(htmlContents));
|
|
395
|
+
}
|
|
396
|
+
promises.push(
|
|
397
|
+
resultByHtml.get(htmlContents).then(newHtmlContents=>{
|
|
398
|
+
langNode["template-content"] = newHtmlContents;
|
|
399
|
+
})
|
|
400
|
+
)
|
|
401
|
+
});
|
|
360
402
|
});
|
|
361
403
|
return Promise.all(promises).then(_=>contentsCopy).catch(e=>{
|
|
362
404
|
Bugsnag.leaveBreadcrumb(
|
|
@@ -648,9 +690,10 @@ export function checkValidUrl(url){
|
|
|
648
690
|
}
|
|
649
691
|
|
|
650
692
|
/**
|
|
651
|
-
*
|
|
693
|
+
*
|
|
652
694
|
* @param {string} url | Accepts cdn url
|
|
653
695
|
* @returns quality param value by extracting it from the url
|
|
696
|
+
* No longer used by the email flow (an old cdn url's quality must not survive a re-save); kept exported for backward compatibility.
|
|
654
697
|
*/
|
|
655
698
|
export function getCdnEmailImageQuality(url) {
|
|
656
699
|
try {
|
|
@@ -697,11 +740,14 @@ export function getS3EmailImageQualityMapping(
|
|
|
697
740
|
return {};
|
|
698
741
|
|
|
699
742
|
const urlToImageQualityMapping = {};
|
|
743
|
+
//exact key match (not substring, so a.jpg never matches a.jpg.jpeg); s3, plain cdn and old cdn urls of one file share the key.
|
|
744
|
+
const urlKeys = (s3UrlList || []).map((url) => [url, getKeyFromS3Url(url)]);
|
|
700
745
|
forOwn(urlKeysToFileSizeMapping, function (val, key, obj) {
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
746
|
+
urlKeys.forEach(([s3Url, urlKey]) => {
|
|
747
|
+
if (urlKey === key) {
|
|
748
|
+
urlToImageQualityMapping[s3Url] = getEmailImageOverrideQuality(val);
|
|
749
|
+
}
|
|
750
|
+
});
|
|
705
751
|
});
|
|
706
752
|
return urlToImageQualityMapping;
|
|
707
753
|
}
|
|
@@ -761,33 +807,29 @@ export function getEmailImageOverrideQuality(fileSize) {
|
|
|
761
807
|
* @param {array} urlList ex ['1.jpg','2.jpg'] // these can be any urls (s3, cdn or others).
|
|
762
808
|
* This function accepts list of urls(can be s3, cdn or other).
|
|
763
809
|
* This function returns quality corresponding to the url.
|
|
764
|
-
*
|
|
765
|
-
*
|
|
766
|
-
* If
|
|
767
|
-
* a. If image is selected from gallery or image is manuall uploaded(not zip upload),
|
|
810
|
+
*
|
|
811
|
+
* For both s3 and cdn urls (a cdn url's old `quality=` is never reused):
|
|
812
|
+
* a. If image is selected from gallery or image is manuall uploaded(not zip upload),
|
|
768
813
|
* then we have file size information in window object. We deterermine quality from it.
|
|
769
|
-
* b. If not in a, we append the
|
|
770
|
-
*
|
|
814
|
+
* b. If not in a, we append the url into nonSelectedS3Urls
|
|
815
|
+
*
|
|
771
816
|
* If we have elements in nonSelectedS3Urls array. We make an api call to get file size details and then determine quality.
|
|
772
|
-
*
|
|
817
|
+
* getKeyFromS3Url works on cdn urls too, so an already transformed cdn url resolves to the same file key as its s3 url.
|
|
818
|
+
*
|
|
773
819
|
* We return the quality mapping, key being url and value being quality
|
|
774
|
-
*
|
|
820
|
+
*
|
|
775
821
|
*/
|
|
776
822
|
export async function getUrlsToQualityMapping(urlList){
|
|
777
823
|
if(!urlList?.length) return {};
|
|
778
824
|
|
|
779
825
|
const urlQualityMapping = {};
|
|
780
826
|
|
|
781
|
-
const nonSelectedS3Urls = []; //we do not know file sizes of these s3 urls yet.
|
|
827
|
+
const nonSelectedS3Urls = []; //we do not know file sizes of these s3/cdn urls yet.
|
|
782
828
|
|
|
783
829
|
urlList?.forEach(url=>{
|
|
784
830
|
const {isS3Url, isCdnUrl} = checkValidUrl(url);
|
|
785
831
|
|
|
786
|
-
if
|
|
787
|
-
urlQualityMapping[url] = getCdnEmailImageQuality(url);
|
|
788
|
-
return;
|
|
789
|
-
}
|
|
790
|
-
if(isS3Url){
|
|
832
|
+
if(isS3Url || isCdnUrl){
|
|
791
833
|
const s3FileQuality = getS3EmailImageQuality(url);
|
|
792
834
|
if(s3FileQuality){
|
|
793
835
|
urlQualityMapping[url] = s3FileQuality;
|
|
@@ -119,7 +119,7 @@ img.fullwidthOnMobile {
|
|
|
119
119
|
<body class=\\"clean-body\\" style=\\"margin: 0; padding: 0; -webkit-text-size-adjust: 100%; background-color: #FFFFFF;\\">
|
|
120
120
|
<p><!--[if IE]><div class=\\"ie-browser\\"><![endif]-->hello world</p>
|
|
121
121
|
|
|
122
|
-
<p><img alt=\\"Capillary Image\\" height=\\"171\\" src=\\"https://storage.crm.n.content-cdn.io/cdn-cgi/image/
|
|
122
|
+
<p><img alt=\\"Capillary Image\\" height=\\"171\\" src=\\"https://storage.crm.n.content-cdn.io/cdn-cgi/image/format=auto,quality=85,/intouch_creative_assets/b4b63b22-8a81-41ee-a8df-6db967fc.jpg\\" width=\\"257\\"></p>
|
|
123
123
|
|
|
124
124
|
<table bgcolor=\\"#FFFFFF\\" cellpadding=\\"0\\" cellspacing=\\"0\\" class=\\"nl-container\\" role=\\"presentation\\" style=\\"background-color:#ffffff; border-collapse:collapse; border-spacing:0; margin:0 auto; min-width:320px; mso-table-lspace:0pt; mso-table-rspace:0pt; table-layout:fixed; vertical-align:top; width:100%\\" valign=\\"top\\">
|
|
125
125
|
<tbody>
|
|
@@ -263,7 +263,7 @@ img.fullwidthOnMobile {
|
|
|
263
263
|
<body class=\\"clean-body\\" style=\\"margin: 0; padding: 0; -webkit-text-size-adjust: 100%; background-color: #FFFFFF;\\">
|
|
264
264
|
<p><!--[if IE]><div class=\\"ie-browser\\"><![endif]-->hello world</p>
|
|
265
265
|
|
|
266
|
-
<p><img alt=\\"Capillary Image\\" height=\\"171\\" src=\\"https://crm
|
|
266
|
+
<p><img alt=\\"Capillary Image\\" height=\\"171\\" src=\\"https://storage.crm.n.content-cdn.io/cdn-cgi/image/format=auto,quality=85,/intouch_creative_assets/b4b63b22-8a81-41ee-a8df-6db967fc.jpg\\" width=\\"257\\"></p>
|
|
267
267
|
|
|
268
268
|
<table bgcolor=\\"#FFFFFF\\" cellpadding=\\"0\\" cellspacing=\\"0\\" class=\\"nl-container\\" role=\\"presentation\\" style=\\"background-color:#ffffff; border-collapse:collapse; border-spacing:0; margin:0 auto; min-width:320px; mso-table-lspace:0pt; mso-table-rspace:0pt; table-layout:fixed; vertical-align:top; width:100%\\" valign=\\"top\\">
|
|
269
269
|
<tbody>
|
|
@@ -226,7 +226,7 @@ img.fullwidthOnMobile {
|
|
|
226
226
|
</style>
|
|
227
227
|
</head>
|
|
228
228
|
<body class="clean-body" style="margin: 0; padding: 0; -webkit-text-size-adjust: 100%; background-color: #FFFFFF;">
|
|
229
|
-
<p><img alt="Capillary Image" height="184" src="https://storage.crm.n.content-cdn.io/cdn-cgi/image/
|
|
229
|
+
<p><img alt="Capillary Image" height="184" src="https://storage.crm.n.content-cdn.io/cdn-cgi/image/format=auto,quality=85,/intouch_creative_assets/d5679e9b-1ff4-4e7c-8f77-30160048.png" width="328"></p>
|
|
230
230
|
<!--[if IE]><div class="ie-browser"><![endif]-->
|
|
231
231
|
|
|
232
232
|
<p>Hello world</p>
|
|
@@ -564,7 +564,7 @@ img.fullwidthOnMobile {
|
|
|
564
564
|
</style>
|
|
565
565
|
</head>
|
|
566
566
|
<body class="clean-body" style="margin: 0; padding: 0; -webkit-text-size-adjust: 100%; background-color: #FFFFFF;">
|
|
567
|
-
<p><img alt="Capillary Image" src="https://storage.crm.n.content-cdn.io/cdn-cgi/image/format=auto,quality=
|
|
567
|
+
<p><img alt="Capillary Image" src="https://storage.crm.n.content-cdn.io/cdn-cgi/image/format=auto,quality=80,/intouch_creative_assets/8d1498ab-099d-4145-ad15-edd77f7.jpeg"></p>
|
|
568
568
|
|
|
569
569
|
<p><img alt="Capillary Image" src="https://storage.crm.n.content-cdn.io/cdn-cgi/image/format=auto,quality=90,/intouch_creative_assets/546a3d95-48be-4955-a07c-c4775da.jpeg"></p>
|
|
570
570
|
<!--[if IE]><div class="ie-browser"><![endif]-->
|
|
@@ -691,9 +691,9 @@ img.fullwidthOnMobile {
|
|
|
691
691
|
</style>
|
|
692
692
|
</head>
|
|
693
693
|
<body class="clean-body" style="margin: 0; padding: 0; -webkit-text-size-adjust: 100%; background-color: #FFFFFF;">
|
|
694
|
-
<p><img alt="Capillary Image" src="https://storage.crm.n.content-cdn.io/cdn-cgi/image/format=auto,quality=
|
|
694
|
+
<p><img alt="Capillary Image" src="https://storage.crm.n.content-cdn.io/cdn-cgi/image/format=auto,quality=85,/intouch_creative_assets/8d1498ab-099d-4145-ad15-edd77f7.jpeg"></p>
|
|
695
695
|
|
|
696
|
-
<p><img alt="Capillary Image" src="https://storage.crm.n.content-cdn.io/cdn-cgi/image/format=auto,quality=
|
|
696
|
+
<p><img alt="Capillary Image" src="https://storage.crm.n.content-cdn.io/cdn-cgi/image/format=auto,quality=85,/intouch_creative_assets/546a3d95-48be-4955-a07c-c4775da.jpeg"></p>
|
|
697
697
|
<!--[if IE]><div class="ie-browser"><![endif]-->
|
|
698
698
|
|
|
699
699
|
<table bgcolor="#FFFFFF" cellpadding="0" cellspacing="0" class="nl-container" role="presentation" style="background-color:#ffffff; border-collapse:collapse; border-spacing:0; margin:0 auto; min-width:320px; mso-table-lspace:0pt; mso-table-rspace:0pt; table-layout:fixed; vertical-align:top; width:100%" valign="top">
|
|
@@ -744,7 +744,7 @@ export const emailRawTransformed2 = `<!DOCTYPE html>
|
|
|
744
744
|
<body>
|
|
745
745
|
<p>test for 3 urls, two are s3 and one is cdn url. Among two s3 url, one s3 url is in window object(since that was manually selected from gallery)</p>
|
|
746
746
|
<img src="https://storage.crm.n.content-cdn.io/cdn-cgi/image/format=auto,quality=85,/intouch_creative_assets/394c053f-309b-49fc-ba74-6e700b1.jpeg">
|
|
747
|
-
<img src="https://storage.crm.n.content-cdn.io/cdn-cgi/image/format=auto,quality=
|
|
747
|
+
<img src="https://storage.crm.n.content-cdn.io/cdn-cgi/image/format=auto,quality=85,/intouch_creative_assets/f98ce0c0-53f8-4fbd-b4e3-05953dda.jpg">
|
|
748
748
|
<img src="https://storage.crm.n.content-cdn.io/cdn-cgi/image/format=auto,quality=85,/intouch_creative_assets/2c9233ed-0959-4aff-b749-9171b5c8.jpg">
|
|
749
749
|
</body>
|
|
750
750
|
</html>`
|
|
@@ -760,7 +760,7 @@ export const emailRawTransformed3 = `<!DOCTYPE html>
|
|
|
760
760
|
<body>
|
|
761
761
|
<p>test for 3 urls, two are s3 and one is cdn url. Among two s3 url, one s3 url is in window object(since that was manually selected from gallery)</p>
|
|
762
762
|
<img src="https://storage.crm.n.content-cdn.io/cdn-cgi/image/format=auto,quality=85,/intouch_creative_assets/394c053f-309b-49fc-ba74-6e700b1.jpeg">
|
|
763
|
-
<img src="https://storage.crm.n.content-cdn.io/cdn-cgi/image/format=auto,quality=
|
|
763
|
+
<img src="https://storage.crm.n.content-cdn.io/cdn-cgi/image/format=auto,quality=85,/intouch_creative_assets/f98ce0c0-53f8-4fbd-b4e3-05953dda.jpg">
|
|
764
764
|
<img src="https://storage.crm.n.content-cdn.io/cdn-cgi/image/format=auto,quality=85,/intouch_creative_assets/2c9233ed-0959-4aff-b749-9171b5c8.jpg">
|
|
765
765
|
</body>
|
|
766
766
|
</html>`
|
|
@@ -15,7 +15,7 @@ beforeEach(() => {
|
|
|
15
15
|
CREATIVES_S3_BUCKET_PATH: "intouch_creative_assets",
|
|
16
16
|
CREATIVES_CDN_BASE_URL: "https://storage.crm.n.content-cdn.io",
|
|
17
17
|
CREATIVES_CDN_QUALITY_CONFIG:
|
|
18
|
-
'{"EMAIL":
|
|
18
|
+
'{"EMAIL":85,"RCS":75,"VIBER":75,"WHATSAPP":75,"MOBILE_PUSH":75,"FACEBOOK":75,"LINE":75,"DEFAULT":75}',
|
|
19
19
|
S3_CDN_MAP: '{"crm-nightly-new-fileservice.s3.amazonaws.com":{"cdn_host":"storage.crm.n.content-cdn.io","bucket_path":"intouch_creative_assets"}, "crm-prod-new-fileservice.s3.amazonaws.com": { "cdn_host": "storage.crm.p.content-cdn.io", "bucket_path": "fileservice.p/intouch_creative_assets" }}',
|
|
20
20
|
};
|
|
21
21
|
return {
|
|
@@ -40,7 +40,7 @@ beforeEach(() => {
|
|
|
40
40
|
afterEach(() => {
|
|
41
41
|
localStorage.setItem(
|
|
42
42
|
"CREATIVES_CDN_QUALITY_CONFIG",
|
|
43
|
-
'{"EMAIL":
|
|
43
|
+
'{"EMAIL":85,"RCS":75,"VIBER":75,"WHATSAPP":75,"MOBILE_PUSH":75,"FACEBOOK":75,"LINE":75,"DEFAULT":75}'
|
|
44
44
|
);
|
|
45
45
|
localStorage.setItem(
|
|
46
46
|
"CREATIVES_S3_BUCKET_PATH", "intouch_creative_assets"
|
|
@@ -83,11 +83,11 @@ describe("cdnTransformationTests", () => {
|
|
|
83
83
|
url: TEST_S3_URL,
|
|
84
84
|
channelName: "EMAIL",
|
|
85
85
|
});
|
|
86
|
-
const EXPECTED_CDN_URL = `https://storage.crm.n.content-cdn.io/cdn-cgi/image/format=auto,quality=
|
|
86
|
+
const EXPECTED_CDN_URL = `https://storage.crm.n.content-cdn.io/cdn-cgi/image/format=auto,quality=85,/intouch_creative_assets/2c9233ed-0959-4aff-b749-9171b5c8.jpg`;
|
|
87
87
|
expect(GENERATED_CDN_URL).toEqual(EXPECTED_CDN_URL);
|
|
88
88
|
});
|
|
89
89
|
|
|
90
|
-
it("Should replace s3 url with cdn url for email when h/w are provided", () => {
|
|
90
|
+
it("Should replace s3 url with cdn url for email when h/w are provided (h/w are ignored for email)", () => {
|
|
91
91
|
const TEST_S3_URL = `https://crm-nightly-new-fileservice.s3.amazonaws.com/intouch_creative_assets/2c9233ed-0959-4aff-b749-9171b5c8.jpg`;
|
|
92
92
|
const GENERATED_CDN_URL = cdnUtils.getCdnUrl({
|
|
93
93
|
url: TEST_S3_URL,
|
|
@@ -95,7 +95,7 @@ describe("cdnTransformationTests", () => {
|
|
|
95
95
|
height: 100,
|
|
96
96
|
width: 200,
|
|
97
97
|
});
|
|
98
|
-
const EXPECTED_CDN_URL = `https://storage.crm.n.content-cdn.io/cdn-cgi/image/
|
|
98
|
+
const EXPECTED_CDN_URL = `https://storage.crm.n.content-cdn.io/cdn-cgi/image/format=auto,quality=85,/intouch_creative_assets/2c9233ed-0959-4aff-b749-9171b5c8.jpg`;
|
|
99
99
|
expect(GENERATED_CDN_URL).toEqual(EXPECTED_CDN_URL);
|
|
100
100
|
});
|
|
101
101
|
|
|
@@ -108,7 +108,7 @@ describe("cdnTransformationTests", () => {
|
|
|
108
108
|
height: 100,
|
|
109
109
|
width: 200,
|
|
110
110
|
});
|
|
111
|
-
const EXPECTED_CDN_URL = `https://storage.crm.p.content-cdn.io/cdn-cgi/image/
|
|
111
|
+
const EXPECTED_CDN_URL = `https://storage.crm.p.content-cdn.io/cdn-cgi/image/format=auto,quality=85,/fileservice.p/intouch_creative_assets/2c9233ed-0959-4aff-b749-9171b5c8.jpg`;
|
|
112
112
|
expect(GENERATED_CDN_URL).toEqual(EXPECTED_CDN_URL);
|
|
113
113
|
});
|
|
114
114
|
|
|
@@ -121,7 +121,7 @@ describe("cdnTransformationTests", () => {
|
|
|
121
121
|
height: 100,
|
|
122
122
|
width: 200,
|
|
123
123
|
});
|
|
124
|
-
const EXPECTED_CDN_URL = `https://storage.crm.p.content-cdn.io/cdn-cgi/image/
|
|
124
|
+
const EXPECTED_CDN_URL = `https://storage.crm.p.content-cdn.io/cdn-cgi/image/format=auto,quality=85,/fileservice.p/intouch_creative_assets/2c9233ed-0959-4aff-b749-9171b5c8.jpg`;
|
|
125
125
|
expect(GENERATED_CDN_URL).toEqual(EXPECTED_CDN_URL);
|
|
126
126
|
});
|
|
127
127
|
|
|
@@ -133,7 +133,7 @@ describe("cdnTransformationTests", () => {
|
|
|
133
133
|
height: 100,
|
|
134
134
|
width: 200,
|
|
135
135
|
});
|
|
136
|
-
const EXPECTED_CDN_URL = `https://storage.crm.n.content-cdn.io/cdn-cgi/image/
|
|
136
|
+
const EXPECTED_CDN_URL = `https://storage.crm.n.content-cdn.io/cdn-cgi/image/format=auto,quality=85,/intouch_creative_assets/02557e68-6b7b-4d0e-ba6f-efcf8972.gif`;
|
|
137
137
|
expect(GENERATED_CDN_URL).toEqual(EXPECTED_CDN_URL);
|
|
138
138
|
});
|
|
139
139
|
|
|
@@ -149,15 +149,15 @@ describe("cdnTransformationTests", () => {
|
|
|
149
149
|
expect(GENERATED_CDN_URL).toEqual(EXPECTED_CDN_URL);
|
|
150
150
|
});
|
|
151
151
|
|
|
152
|
-
it("should
|
|
153
|
-
const TEST_S3_URL = `https://storage.crm.n.content-cdn.io/cdn-cgi/image/width=200,height=100,format=auto,quality=
|
|
152
|
+
it("should rebuild an already transformed cdn url for email, dropping old width/height/quality", () => {
|
|
153
|
+
const TEST_S3_URL = `https://storage.crm.n.content-cdn.io/cdn-cgi/image/width=200,height=100,format=auto,quality=65,/intouch_creative_assets/2c9233ed-0959-4aff-b749-9171b5c8.jpg`;
|
|
154
154
|
const GENERATED_CDN_URL = cdnUtils.getCdnUrl({
|
|
155
155
|
url: TEST_S3_URL,
|
|
156
156
|
channelName: "EMAIL",
|
|
157
157
|
height: 400,
|
|
158
158
|
width: 500,
|
|
159
159
|
});
|
|
160
|
-
const EXPECTED_CDN_URL = `https://storage.crm.n.content-cdn.io/cdn-cgi/image/
|
|
160
|
+
const EXPECTED_CDN_URL = `https://storage.crm.n.content-cdn.io/cdn-cgi/image/format=auto,quality=85,/intouch_creative_assets/2c9233ed-0959-4aff-b749-9171b5c8.jpg`;
|
|
161
161
|
expect(GENERATED_CDN_URL).toEqual(EXPECTED_CDN_URL);
|
|
162
162
|
});
|
|
163
163
|
|
|
@@ -205,7 +205,7 @@ describe("cdnTransformationTests", () => {
|
|
|
205
205
|
height: 100,
|
|
206
206
|
width: 200,
|
|
207
207
|
});
|
|
208
|
-
const EXPECTED_CDN_URL = `https://storage.crm.n.content-cdn.io/cdn-cgi/image/
|
|
208
|
+
const EXPECTED_CDN_URL = `https://storage.crm.n.content-cdn.io/cdn-cgi/image/format=auto,quality=100,/intouch_creative_assets/2c9233ed-0959-4aff-b749-9171b5c8.jpg`;
|
|
209
209
|
|
|
210
210
|
expect(GENERATED_CDN_URL).toEqual(EXPECTED_CDN_URL);
|
|
211
211
|
});
|
|
@@ -246,6 +246,8 @@ describe("cdnTransformationTests", () => {
|
|
|
246
246
|
"https://crm-nightly-new-fileservice.s3.amazonaws.com/intouch_creative_assets/394c053f-309b-49fc-ba74-6e700b1.jpeg": 76800, //this image has quality 85 since file size less than 80kb
|
|
247
247
|
};
|
|
248
248
|
|
|
249
|
+
//The msw handler does not intercept this call (same as before this change), so f98ce0c0 and the cdn url 2c9233ed fall back to the config quality 85.
|
|
250
|
+
//The cdn url is looked up by file key like an s3 url; its own quality= is not reused.
|
|
249
251
|
const htmlContentsInput = mockdata.emailRawInput2;
|
|
250
252
|
const htmlContentsOutput = await cdnUtils.updateImagesInHtml(htmlContentsInput);
|
|
251
253
|
const expected = mockdata.emailRawTransformed2;
|
|
@@ -278,6 +280,57 @@ describe("cdnTransformationTests", () => {
|
|
|
278
280
|
await cdnUtils.transformEmailTemplates(emailTemplateInput);
|
|
279
281
|
expect(emailTemplateOutput).toMatchSnapshot();
|
|
280
282
|
});
|
|
283
|
+
it("should transform every version node: base and every history entry, all selected languages, old cdn urls included", async () => {
|
|
284
|
+
const OLD_EN = "https://storage.crm.n.content-cdn.io/cdn-cgi/image/width=600,height=auto,format=auto,quality=65,/intouch_creative_assets/2c9233ed-0959-4aff-b749-9171b5c8.jpg";
|
|
285
|
+
const S3_ES = "https://crm-nightly-new-fileservice.s3.amazonaws.com/intouch_creative_assets/02557e68-6b7b-4d0e-ba6f-efcf8972.gif";
|
|
286
|
+
const NEW_EN = "https://storage.crm.n.content-cdn.io/cdn-cgi/image/format=auto,quality=85,/intouch_creative_assets/2c9233ed-0959-4aff-b749-9171b5c8.jpg";
|
|
287
|
+
const NEW_ES = "https://storage.crm.n.content-cdn.io/cdn-cgi/image/format=auto,quality=85,/intouch_creative_assets/02557e68-6b7b-4d0e-ba6f-efcf8972.gif";
|
|
288
|
+
const makeNode = () => ({
|
|
289
|
+
selectedLanguages: ["en", "es"],
|
|
290
|
+
activeTab: "en",
|
|
291
|
+
en: { "template-content": `<img src="${OLD_EN}" width="600" height="auto">` },
|
|
292
|
+
es: { "template-content": `<img src="${S3_ES}">` },
|
|
293
|
+
});
|
|
294
|
+
const input = { type: "EMAIL", versions: { base: makeNode(), history: [makeNode(), makeNode()] } };
|
|
295
|
+
|
|
296
|
+
const output = await cdnUtils.transformEmailTemplates(input);
|
|
297
|
+
|
|
298
|
+
[output.versions.base, output.versions.history[0], output.versions.history[1]].forEach((node) => {
|
|
299
|
+
expect(node.en["template-content"]).toEqual(`<img src="${NEW_EN}" width="600" height="auto">`);
|
|
300
|
+
expect(node.es["template-content"]).toEqual(`<img src="${NEW_ES}">`);
|
|
301
|
+
});
|
|
302
|
+
// input is not mutated
|
|
303
|
+
expect(input.versions.history[0].en["template-content"]).toContain("quality=65");
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it("should transform a language node once when base and history[0] are the same object, and skip nodes without html", async () => {
|
|
307
|
+
const S3 = "https://crm-nightly-new-fileservice.s3.amazonaws.com/intouch_creative_assets/2c9233ed-0959-4aff-b749-9171b5c8.jpg";
|
|
308
|
+
const shared = { selectedLanguages: ["en", "fr"], activeTab: "en", en: { "template-content": `<img src="${S3}">` }, fr: { is_drag_drop: false } };
|
|
309
|
+
const input = { versions: { base: shared, history: [shared] } };
|
|
310
|
+
const parseSpy = jest.spyOn(nodeHtmlParser, "parse");
|
|
311
|
+
|
|
312
|
+
const output = await cdnUtils.transformEmailTemplates(input);
|
|
313
|
+
|
|
314
|
+
expect(output.versions.base).toBe(output.versions.history[0]); // shared reference survives cloneDeep
|
|
315
|
+
expect(output.versions.base.en["template-content"]).toContain("cdn-cgi/image/format=auto,quality=85,/");
|
|
316
|
+
expect(output.versions.base.fr).toEqual({ is_drag_drop: false }); // no "template-content" key added
|
|
317
|
+
expect(parseSpy).toHaveBeenCalledTimes(1);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it("should transform identical html held by distinct base and history nodes once", async () => {
|
|
321
|
+
const S3 = "https://crm-nightly-new-fileservice.s3.amazonaws.com/intouch_creative_assets/2c9233ed-0959-4aff-b749-9171b5c8.jpg";
|
|
322
|
+
const makeNode = () => ({ selectedLanguages: ["en"], activeTab: "en", en: { "template-content": `<img src="${S3}" width="600">` } });
|
|
323
|
+
const input = { versions: { base: makeNode(), history: [makeNode()] } };
|
|
324
|
+
const parseSpy = jest.spyOn(nodeHtmlParser, "parse");
|
|
325
|
+
|
|
326
|
+
const output = await cdnUtils.transformEmailTemplates(input);
|
|
327
|
+
|
|
328
|
+
expect(output.versions.base).not.toBe(output.versions.history[0]);
|
|
329
|
+
expect(output.versions.history[0].en["template-content"]).toEqual(output.versions.base.en["template-content"]);
|
|
330
|
+
expect(output.versions.base.en["template-content"]).toContain("cdn-cgi/image/format=auto,quality=85,/");
|
|
331
|
+
expect(parseSpy).toHaveBeenCalledTimes(1);
|
|
332
|
+
});
|
|
333
|
+
|
|
281
334
|
it("should return original contents transform email template throws an error inside. This might be due to improper input or unexpected error.", async () => {
|
|
282
335
|
const emailTemplateInputWithError = mockdata.emailTemplateRawWithError;
|
|
283
336
|
const emailTemplateOutput =
|
|
@@ -304,19 +357,22 @@ describe("cdnTransformationTests", () => {
|
|
|
304
357
|
const emailTemplateInput = mockdata.emailTemplateOverrideInput1;
|
|
305
358
|
const emailTemplateOutput =
|
|
306
359
|
await cdnUtils.updateImagesInHtml(emailTemplateInput);
|
|
307
|
-
expect(emailTemplateOutput).toEqual(mockdata.emailTemplateOverrideOutput1); //first image
|
|
360
|
+
expect(emailTemplateOutput).toEqual(mockdata.emailTemplateOverrideOutput1); //first image maps to q=75 (less than 400KB) and is clamped to the email minimum 80, second image has q=90 since less than 30kB
|
|
308
361
|
|
|
309
362
|
});
|
|
310
363
|
|
|
311
364
|
|
|
312
|
-
it("should reuse the
|
|
365
|
+
it("should NOT reuse the previous quality from a cdn url when overrideEmailQuality is set true", async () => {
|
|
313
366
|
localStorage.setItem("CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY", "true");
|
|
314
367
|
localStorage.setItem("CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY_MAPPING", "[[30, 90], [80, 85], [150, 80], [400, 75], [600, 70], [1200, 65], [102400, 60]]");
|
|
315
368
|
|
|
369
|
+
//Input already carries quality=80 and quality=90. File sizes are unknown (no window entry, file size api not mocked),
|
|
370
|
+
//so the current email rule applies: the config quality (85) for both images, not the quality found in the url.
|
|
316
371
|
const emailTemplateInput = mockdata.emailTemplateOverrideOutput1;
|
|
317
372
|
const emailTemplateOutput =
|
|
318
373
|
await cdnUtils.updateImagesInHtml(emailTemplateInput);
|
|
319
|
-
expect(emailTemplateOutput).toEqual(mockdata.
|
|
374
|
+
expect(emailTemplateOutput).toEqual(mockdata.emailTemplateOverrideOutputWithWindowDeleted1);
|
|
375
|
+
expect(emailTemplateOutput).not.toEqual(emailTemplateInput);
|
|
320
376
|
});
|
|
321
377
|
|
|
322
378
|
it("should transform email template with qualtyCfg if window objects CREATIVES_S3_ASSET_FILESIZES is somehow deleted", async () => {
|
|
@@ -328,7 +384,7 @@ describe("cdnTransformationTests", () => {
|
|
|
328
384
|
const emailTemplateInput = mockdata.emailTemplateOverrideInput1;
|
|
329
385
|
const emailTemplateOutput =
|
|
330
386
|
await cdnUtils.updateImagesInHtml(emailTemplateInput);
|
|
331
|
-
expect(emailTemplateOutput).toEqual(mockdata.emailTemplateOverrideOutputWithWindowDeleted1); //
|
|
387
|
+
expect(emailTemplateOutput).toEqual(mockdata.emailTemplateOverrideOutputWithWindowDeleted1); //file sizes unknown, both images get the config email quality (85)
|
|
332
388
|
|
|
333
389
|
});
|
|
334
390
|
});
|
|
@@ -825,4 +881,248 @@ describe("cdnTransformationTests", () => {
|
|
|
825
881
|
});
|
|
826
882
|
|
|
827
883
|
})
|
|
884
|
+
|
|
885
|
+
/**
|
|
886
|
+
* Email images are never resized, the email quality is clamped to [80, 100],
|
|
887
|
+
* and old transformed cdn urls are rebuilt from the file key with the current settings.
|
|
888
|
+
* Covers the eucrm, incrm, seacrm and asiacrm S3_CDN_MAP shapes (path-style buckets included).
|
|
889
|
+
*/
|
|
890
|
+
describe("EMAIL channel: no resizing, clamped quality, old cdn urls rebuilt", () => {
|
|
891
|
+
const PROD_S3_CDN_MAP = {
|
|
892
|
+
"crm-nightly-new-fileservice.s3.amazonaws.com": { cdn_host: "storage.crm.n.content-cdn.io", bucket_path: "intouch_creative_assets" },
|
|
893
|
+
"asiacrm-fileservicepgxdbptutk.s3.ap-south-1.amazonaws.com": { cdn_host: "storage.crm.asia.content-cdn.io", bucket_path: "intouch_creative_assets" },
|
|
894
|
+
"s3-eu-west-1.amazonaws.com": { cdn_host: "storage.crm.eu.content-cdn.io", bucket_path: "fs.capillary.eu/intouch_creative_assets" },
|
|
895
|
+
"s3.amazonaws.com": { cdn_host: "storage.crm.in.content-cdn.io", bucket_path: "fileservice.in/intouch_creative_assets" },
|
|
896
|
+
"s3-ap-southeast-1.amazonaws.com": { cdn_host: "storage.crm.sg.content-cdn.io", bucket_path: "fs.capillary.sg/intouch_creative_assets" },
|
|
897
|
+
};
|
|
898
|
+
|
|
899
|
+
// For every cluster: the s3 url, the plain cdn url and an old transformed cdn url of ONE file must all become `expected`.
|
|
900
|
+
const CLUSTER_CASES = [
|
|
901
|
+
{
|
|
902
|
+
cluster: "eucrm",
|
|
903
|
+
s3: "https://s3-eu-west-1.amazonaws.com/fs.capillary.eu/intouch_creative_assets/26c03d91-ab03-42a9-904c-1840f093.png",
|
|
904
|
+
plainCdn: "https://storage.crm.eu.content-cdn.io/fs.capillary.eu/intouch_creative_assets/26c03d91-ab03-42a9-904c-1840f093.png",
|
|
905
|
+
oldCdn: "https://storage.crm.eu.content-cdn.io/cdn-cgi/image/width=600,height=auto,format=auto,quality=65,/fs.capillary.eu/intouch_creative_assets/26c03d91-ab03-42a9-904c-1840f093.png",
|
|
906
|
+
expected: "https://storage.crm.eu.content-cdn.io/cdn-cgi/image/format=auto,quality=85,/fs.capillary.eu/intouch_creative_assets/26c03d91-ab03-42a9-904c-1840f093.png",
|
|
907
|
+
},
|
|
908
|
+
{
|
|
909
|
+
cluster: "incrm",
|
|
910
|
+
s3: "https://s3.amazonaws.com/fileservice.in/intouch_creative_assets/60221e2020ba572f6787.png",
|
|
911
|
+
plainCdn: "https://storage.crm.in.content-cdn.io/fileservice.in/intouch_creative_assets/60221e2020ba572f6787.png",
|
|
912
|
+
oldCdn: "https://storage.crm.in.content-cdn.io/cdn-cgi/image/width=371,height=82,format=auto,quality=90,/fileservice.in/intouch_creative_assets/60221e2020ba572f6787.png",
|
|
913
|
+
expected: "https://storage.crm.in.content-cdn.io/cdn-cgi/image/format=auto,quality=85,/fileservice.in/intouch_creative_assets/60221e2020ba572f6787.png",
|
|
914
|
+
},
|
|
915
|
+
{
|
|
916
|
+
cluster: "seacrm",
|
|
917
|
+
s3: "https://s3-ap-southeast-1.amazonaws.com/fs.capillary.sg/intouch_creative_assets/7e3f2abd-6862-4f2d-8537-7deca610.png",
|
|
918
|
+
plainCdn: "https://storage.crm.sg.content-cdn.io/fs.capillary.sg/intouch_creative_assets/7e3f2abd-6862-4f2d-8537-7deca610.png",
|
|
919
|
+
oldCdn: "https://storage.crm.sg.content-cdn.io/cdn-cgi/image/quality=75,/fs.capillary.sg/intouch_creative_assets/7e3f2abd-6862-4f2d-8537-7deca610.png",
|
|
920
|
+
expected: "https://storage.crm.sg.content-cdn.io/cdn-cgi/image/format=auto,quality=85,/fs.capillary.sg/intouch_creative_assets/7e3f2abd-6862-4f2d-8537-7deca610.png",
|
|
921
|
+
},
|
|
922
|
+
{
|
|
923
|
+
cluster: "asiacrm",
|
|
924
|
+
s3: "https://asiacrm-fileservicepgxdbptutk.s3.ap-south-1.amazonaws.com/intouch_creative_assets/8d1498ab-099d-4145-ad15-edd77f7.jpeg",
|
|
925
|
+
plainCdn: "https://storage.crm.asia.content-cdn.io/intouch_creative_assets/8d1498ab-099d-4145-ad15-edd77f7.jpeg",
|
|
926
|
+
oldCdn: "https://storage.crm.asia.content-cdn.io/cdn-cgi/image/width=1080,height=1080,format=auto,quality=60,/intouch_creative_assets/8d1498ab-099d-4145-ad15-edd77f7.jpeg",
|
|
927
|
+
expected: "https://storage.crm.asia.content-cdn.io/cdn-cgi/image/format=auto,quality=85,/intouch_creative_assets/8d1498ab-099d-4145-ad15-edd77f7.jpeg",
|
|
928
|
+
},
|
|
929
|
+
];
|
|
930
|
+
|
|
931
|
+
const setQualityConfig = (cfg) => localStorage.setItem("CREATIVES_CDN_QUALITY_CONFIG", JSON.stringify(cfg));
|
|
932
|
+
// What updateImagesInHtml passes for a typical <img width="600" height="auto">.
|
|
933
|
+
const toEmailUrl = (url, width = 600, height = "auto") => cdnUtils.getCdnUrl({
|
|
934
|
+
url,
|
|
935
|
+
channelName: "EMAIL",
|
|
936
|
+
width,
|
|
937
|
+
height,
|
|
938
|
+
});
|
|
939
|
+
const EU = CLUSTER_CASES[0];
|
|
940
|
+
const EXTERNAL_URL_1 = "https://example.com/newsletter/banner.jpg";
|
|
941
|
+
const EXTERNAL_URL_2 = "https://d1oco4z2z1fhwp.cloudfront.net/templates/x.png";
|
|
942
|
+
const EMAIL_HTML = [
|
|
943
|
+
"<!DOCTYPE html><html><body>",
|
|
944
|
+
`<img src="${EU.oldCdn}" width="600" height="auto">`,
|
|
945
|
+
`<img src="${EU.s3}" width="300" height="200">`,
|
|
946
|
+
`<img src="${EU.plainCdn}">`,
|
|
947
|
+
`<img src="${EXTERNAL_URL_1}" width="100" height="50">`,
|
|
948
|
+
`<img src="${EXTERNAL_URL_2}">`,
|
|
949
|
+
"</body></html>",
|
|
950
|
+
].join("");
|
|
951
|
+
|
|
952
|
+
beforeEach(() => {
|
|
953
|
+
localStorage.setItem("S3_CDN_MAP", JSON.stringify(PROD_S3_CDN_MAP));
|
|
954
|
+
setQualityConfig({
|
|
955
|
+
EMAIL: 85, RCS: 75, VIBER: 75, WHATSAPP: 75, MOBILE_PUSH: 75, FACEBOOK: 75, LINE: 75, DEFAULT: 75,
|
|
956
|
+
});
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
describe("getCdnUrl()", () => {
|
|
960
|
+
it("never adds width/height to email urls even when they are passed; format=auto and the config quality are present", () => {
|
|
961
|
+
const url = toEmailUrl(EU.s3);
|
|
962
|
+
expect(url).toEqual(EU.expected);
|
|
963
|
+
expect(url).not.toContain("width=");
|
|
964
|
+
expect(url).not.toContain("height=");
|
|
965
|
+
expect(url).toContain("format=auto,");
|
|
966
|
+
expect(url).toContain("quality=85,");
|
|
967
|
+
|
|
968
|
+
const urlWithNumericHw = toEmailUrl(EU.s3, 1200, 1258);
|
|
969
|
+
expect(urlWithNumericHw).toEqual(EU.expected);
|
|
970
|
+
});
|
|
971
|
+
|
|
972
|
+
it("clamps the email config quality into [80, 100]: 60->80, 85->85, 120->100", () => {
|
|
973
|
+
[[60, 80], [85, 85], [120, 100], [80, 80], [100, 100]].forEach(([configured, served]) => {
|
|
974
|
+
setQualityConfig({ EMAIL: configured, DEFAULT: 75 });
|
|
975
|
+
expect(cdnUtils.getCdnUrl({ url: EU.s3, channelName: "EMAIL" }))
|
|
976
|
+
.toEqual(EU.expected.replace("quality=85,", `quality=${served},`));
|
|
977
|
+
});
|
|
978
|
+
});
|
|
979
|
+
|
|
980
|
+
it("clamps emailOverrideQuality for email: 65->80, 90->90, 150->100", () => {
|
|
981
|
+
[[65, 80], [90, 90], [150, 100]].forEach(([override, served]) => {
|
|
982
|
+
expect(cdnUtils.getCdnUrl({ url: EU.s3, channelName: "EMAIL", emailOverrideQuality: override }))
|
|
983
|
+
.toEqual(EU.expected.replace("quality=85,", `quality=${served},`));
|
|
984
|
+
});
|
|
985
|
+
});
|
|
986
|
+
|
|
987
|
+
it("exposes the clamp helper and the bounds", () => {
|
|
988
|
+
expect(cdnUtils.EMAIL_MIN_QUALITY).toEqual(80);
|
|
989
|
+
expect(cdnUtils.EMAIL_MAX_QUALITY).toEqual(100);
|
|
990
|
+
expect(cdnUtils.clampEmailQuality(10)).toEqual(80);
|
|
991
|
+
expect(cdnUtils.clampEmailQuality(80)).toEqual(80);
|
|
992
|
+
expect(cdnUtils.clampEmailQuality(90)).toEqual(90);
|
|
993
|
+
expect(cdnUtils.clampEmailQuality(999)).toEqual(100);
|
|
994
|
+
expect(cdnUtils.clampEmailQuality(null)).toEqual(null);
|
|
995
|
+
expect(cdnUtils.clampEmailQuality("a")).toEqual("a");
|
|
996
|
+
});
|
|
997
|
+
|
|
998
|
+
it("does not clamp or resize other channels: a non-email channel configured at 70 still produces 70 with its own h/w", () => {
|
|
999
|
+
setQualityConfig({
|
|
1000
|
+
EMAIL: 85, RCS: 70, VIBER: 70, WHATSAPP: 70, DEFAULT: 70,
|
|
1001
|
+
});
|
|
1002
|
+
const S3_URL = "https://crm-nightly-new-fileservice.s3.amazonaws.com/intouch_creative_assets/2c9233ed-0959-4aff-b749-9171b5c8.jpg";
|
|
1003
|
+
expect(cdnUtils.getCdnUrl({ url: S3_URL, channelName: "RCS" }))
|
|
1004
|
+
.toEqual("https://storage.crm.n.content-cdn.io/cdn-cgi/image/width=1440,height=720,quality=70,/intouch_creative_assets/2c9233ed-0959-4aff-b749-9171b5c8.jpg");
|
|
1005
|
+
expect(cdnUtils.getCdnUrl({ url: S3_URL, channelName: "VIBER" }))
|
|
1006
|
+
.toEqual("https://storage.crm.n.content-cdn.io/cdn-cgi/image/width=300,height=400,quality=70,/intouch_creative_assets/2c9233ed-0959-4aff-b749-9171b5c8.jpg");
|
|
1007
|
+
expect(cdnUtils.getCdnUrl({ url: S3_URL, channelName: "WHATSAPP" }))
|
|
1008
|
+
.toEqual("https://storage.crm.n.content-cdn.io/cdn-cgi/image/quality=70,/intouch_creative_assets/2c9233ed-0959-4aff-b749-9171b5c8.jpg");
|
|
1009
|
+
});
|
|
1010
|
+
|
|
1011
|
+
it("gives the identical email url for the s3 url, the plain cdn url and an old transformed cdn url (incl. height=auto), for every cluster map shape", () => {
|
|
1012
|
+
CLUSTER_CASES.forEach((c) => {
|
|
1013
|
+
expect([c.cluster, toEmailUrl(c.s3)]).toEqual([c.cluster, c.expected]);
|
|
1014
|
+
expect([c.cluster, toEmailUrl(c.plainCdn)]).toEqual([c.cluster, c.expected]);
|
|
1015
|
+
expect([c.cluster, toEmailUrl(c.oldCdn)]).toEqual([c.cluster, c.expected]);
|
|
1016
|
+
});
|
|
1017
|
+
});
|
|
1018
|
+
|
|
1019
|
+
it("leaves external (non S3_CDN_MAP) urls unchanged", () => {
|
|
1020
|
+
expect(toEmailUrl(EXTERNAL_URL_1, 100, 50)).toEqual(EXTERNAL_URL_1);
|
|
1021
|
+
expect(cdnUtils.getCdnUrl({ url: EXTERNAL_URL_2, channelName: "EMAIL" })).toEqual(EXTERNAL_URL_2);
|
|
1022
|
+
});
|
|
1023
|
+
});
|
|
1024
|
+
|
|
1025
|
+
describe("updateImagesInHtml()", () => {
|
|
1026
|
+
const expectRebuiltHtml = (html, quality) => {
|
|
1027
|
+
const root = nodeHtmlParser.parse(html);
|
|
1028
|
+
const srcs = root.querySelectorAll("img").map((img) => img.getAttribute("src"));
|
|
1029
|
+
const expected = EU.expected.replace("quality=85,", `quality=${quality},`);
|
|
1030
|
+
expect(srcs).toEqual([expected, expected, expected, EXTERNAL_URL_1, EXTERNAL_URL_2]);
|
|
1031
|
+
srcs.slice(0, 3).forEach((src) => {
|
|
1032
|
+
expect(src).not.toContain("width=");
|
|
1033
|
+
expect(src).not.toContain("height=");
|
|
1034
|
+
expect(src).not.toContain("quality=65");
|
|
1035
|
+
});
|
|
1036
|
+
// The <img> width/height attributes themselves are untouched; only src changes.
|
|
1037
|
+
expect(html).toContain(`<img src="${expected}" width="600" height="auto">`);
|
|
1038
|
+
expect(html).toContain(`<img src="${EXTERNAL_URL_1}" width="100" height="50">`);
|
|
1039
|
+
};
|
|
1040
|
+
|
|
1041
|
+
it("OVERRIDE_IMAGE_QUALITY off: rebuilds an old width=600,height=auto,quality=65 url with no width/height and the config quality; s3 and plain cdn urls give the identical url; external urls unchanged; idempotent", async () => {
|
|
1042
|
+
localStorage.removeItem("CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY");
|
|
1043
|
+
localStorage.removeItem("CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY_MAPPING");
|
|
1044
|
+
|
|
1045
|
+
const once = await cdnUtils.updateImagesInHtml(EMAIL_HTML);
|
|
1046
|
+
expectRebuiltHtml(once, 85);
|
|
1047
|
+
|
|
1048
|
+
const twice = await cdnUtils.updateImagesInHtml(once);
|
|
1049
|
+
expect(twice).toEqual(once);
|
|
1050
|
+
});
|
|
1051
|
+
|
|
1052
|
+
// POST /assets/files/metadata answer for the eucrm banner: 948.6 KB (the api returns KB) keyed by s3 key.
|
|
1053
|
+
// 948.6 KB falls in the [1200, 65] band of IMAGE_QUALITY_MAPPING, which the email clamp raises to 80.
|
|
1054
|
+
const FILE_SIZE_API_RESPONSE = {
|
|
1055
|
+
success: true,
|
|
1056
|
+
status: { isError: false, code: 200, message: "success" },
|
|
1057
|
+
response: { "26c03d91-ab03-42a9-904c-1840f093.png": 948.6 },
|
|
1058
|
+
};
|
|
1059
|
+
|
|
1060
|
+
it("OVERRIDE_IMAGE_QUALITY on: the old quality=65 is NOT reused; all three url forms resolve via the file size lookup and are clamped to 80; external urls unchanged; idempotent", async () => {
|
|
1061
|
+
localStorage.setItem("CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY", "true");
|
|
1062
|
+
localStorage.setItem("CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY_MAPPING", "[[30, 90], [80, 85], [150, 80], [400, 75], [600, 70], [1200, 65], [102400, 60]]");
|
|
1063
|
+
const apiSpy = jest.spyOn(Api, "getS3UrlFileSizes").mockResolvedValue(FILE_SIZE_API_RESPONSE);
|
|
1064
|
+
|
|
1065
|
+
const once = await cdnUtils.updateImagesInHtml(EMAIL_HTML);
|
|
1066
|
+
// 948.6 KB -> band 65 -> clamped to the email minimum 80 (never 65 from the old url, never below 80).
|
|
1067
|
+
expectRebuiltHtml(once, 80);
|
|
1068
|
+
// The old cdn url and the plain cdn url are looked up by the same file key as the s3 url, never by the quality= they carry.
|
|
1069
|
+
expect(apiSpy).toHaveBeenCalledTimes(1);
|
|
1070
|
+
expect(apiSpy.mock.calls[0][0].keys).toEqual([
|
|
1071
|
+
"26c03d91-ab03-42a9-904c-1840f093.png",
|
|
1072
|
+
"26c03d91-ab03-42a9-904c-1840f093.png",
|
|
1073
|
+
"26c03d91-ab03-42a9-904c-1840f093.png",
|
|
1074
|
+
]);
|
|
1075
|
+
apiSpy.mockResolvedValue(FILE_SIZE_API_RESPONSE);
|
|
1076
|
+
|
|
1077
|
+
const twice = await cdnUtils.updateImagesInHtml(once);
|
|
1078
|
+
expect(twice).toEqual(once);
|
|
1079
|
+
});
|
|
1080
|
+
|
|
1081
|
+
it("OVERRIDE_IMAGE_QUALITY on but the file size api fails: falls back to the config quality for every url form", async () => {
|
|
1082
|
+
jest.spyOn(Api, "getS3UrlFileSizes").mockRejectedValue(new Error("network"));
|
|
1083
|
+
localStorage.setItem("CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY", "true");
|
|
1084
|
+
localStorage.setItem("CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY_MAPPING", "[[30, 90], [80, 85], [150, 80], [400, 75], [600, 70], [1200, 65], [102400, 60]]");
|
|
1085
|
+
const once = await cdnUtils.updateImagesInHtml(EMAIL_HTML);
|
|
1086
|
+
expectRebuiltHtml(once, 85);
|
|
1087
|
+
});
|
|
1088
|
+
|
|
1089
|
+
it("OVERRIDE_IMAGE_QUALITY on with the file size known from the window object (gallery/upload path): clamped to 80 for the s3 url and identical for the cdn forms", async () => {
|
|
1090
|
+
jest.spyOn(Api, "getS3UrlFileSizes").mockResolvedValue(FILE_SIZE_API_RESPONSE);
|
|
1091
|
+
localStorage.setItem("CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY", "true");
|
|
1092
|
+
localStorage.setItem("CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY_MAPPING", "[[30, 90], [80, 85], [150, 80], [400, 75], [600, 70], [1200, 65], [102400, 60]]");
|
|
1093
|
+
window[cdnUtils.CREATIVES_S3_ASSET_FILESIZES] = { [EU.s3]: 971372 };
|
|
1094
|
+
|
|
1095
|
+
const once = await cdnUtils.updateImagesInHtml(EMAIL_HTML);
|
|
1096
|
+
expectRebuiltHtml(once, 80);
|
|
1097
|
+
});
|
|
1098
|
+
|
|
1099
|
+
it("OVERRIDE_IMAGE_QUALITY on: file sizes are matched by exact key, so a.jpg never takes a.jpg.jpeg's size", async () => {
|
|
1100
|
+
localStorage.setItem("CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY", "true");
|
|
1101
|
+
localStorage.setItem("CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY_MAPPING", "[[30, 90], [80, 85], [150, 80], [400, 75], [600, 70], [1200, 65], [102400, 60]]");
|
|
1102
|
+
const SHORT = "https://s3-eu-west-1.amazonaws.com/fs.capillary.eu/intouch_creative_assets/a.jpg";
|
|
1103
|
+
const LONG = "https://s3-eu-west-1.amazonaws.com/fs.capillary.eu/intouch_creative_assets/a.jpg.jpeg";
|
|
1104
|
+
jest.spyOn(Api, "getS3UrlFileSizes").mockResolvedValue({
|
|
1105
|
+
success: true,
|
|
1106
|
+
status: { isError: false, code: 200, message: "success" },
|
|
1107
|
+
response: { "a.jpg.jpeg": 20, "a.jpg": 948.6 }, // 20 KB -> 90, 948.6 KB -> 65 -> clamped 80
|
|
1108
|
+
});
|
|
1109
|
+
|
|
1110
|
+
const once = await cdnUtils.updateImagesInHtml(`<img src="${LONG}"><img src="${SHORT}">`);
|
|
1111
|
+
|
|
1112
|
+
expect(once).toEqual(
|
|
1113
|
+
`<img src="https://storage.crm.eu.content-cdn.io/cdn-cgi/image/format=auto,quality=90,/fs.capillary.eu/intouch_creative_assets/a.jpg.jpeg">`
|
|
1114
|
+
+ `<img src="https://storage.crm.eu.content-cdn.io/cdn-cgi/image/format=auto,quality=80,/fs.capillary.eu/intouch_creative_assets/a.jpg">`
|
|
1115
|
+
);
|
|
1116
|
+
});
|
|
1117
|
+
|
|
1118
|
+
it("OVERRIDE_IMAGE_QUALITY on: a file size that maps above 80 keeps that quality (clamp is a floor, not a constant)", async () => {
|
|
1119
|
+
localStorage.setItem("CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY", "true");
|
|
1120
|
+
localStorage.setItem("CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY_MAPPING", "[[30, 90], [80, 85], [150, 80], [400, 75], [600, 70], [1200, 65], [102400, 60]]");
|
|
1121
|
+
window[cdnUtils.CREATIVES_S3_ASSET_FILESIZES] = { [EU.s3]: 20 * 1024 }; // 20 KB -> 90
|
|
1122
|
+
|
|
1123
|
+
const once = await cdnUtils.updateImagesInHtml(`<img src="${EU.s3}" width="600" height="auto">`);
|
|
1124
|
+
expect(once).toEqual(`<img src="${EU.expected.replace("quality=85,", "quality=90,")}" width="600" height="auto">`);
|
|
1125
|
+
});
|
|
1126
|
+
});
|
|
1127
|
+
});
|
|
828
1128
|
});
|
|
@@ -177,7 +177,8 @@ export function* getBulkCustomerDetails({fetchedUserIds}) {
|
|
|
177
177
|
(item) => {
|
|
178
178
|
const profile = get(item, 'entity.profiles[0]', {});
|
|
179
179
|
if (isEmpty(profile)) {
|
|
180
|
-
|
|
180
|
+
// Skip customers without a profile (e.g. PII-deleted) instead of failing the whole list.
|
|
181
|
+
return null;
|
|
181
182
|
}
|
|
182
183
|
const mobileIdentifier = profile?.identifiers?.find(
|
|
183
184
|
(identifier) => identifier.type === IDENTIFIER_TYPE_MOBILE,
|
|
@@ -193,7 +194,7 @@ export function* getBulkCustomerDetails({fetchedUserIds}) {
|
|
|
193
194
|
email: emailIdentifier?.value,
|
|
194
195
|
};
|
|
195
196
|
},
|
|
196
|
-
);
|
|
197
|
+
).filter(Boolean);
|
|
197
198
|
yield put({
|
|
198
199
|
type: GET_TEST_CUSTOMERS_SUCCESS,
|
|
199
200
|
payload: { customers: transformedData },
|
|
@@ -957,6 +957,61 @@ describe('CommonTestAndPreview Sagas', () => {
|
|
|
957
957
|
});
|
|
958
958
|
});
|
|
959
959
|
|
|
960
|
+
describe('getBulkCustomerDetails', () => {
|
|
961
|
+
const validItem = {
|
|
962
|
+
entity: {
|
|
963
|
+
id: 101,
|
|
964
|
+
profiles: [{
|
|
965
|
+
firstName: 'Jane',
|
|
966
|
+
lastName: 'Doe',
|
|
967
|
+
identifiers: [
|
|
968
|
+
{ type: 'mobile', value: '919999999999' },
|
|
969
|
+
{ type: 'email', value: 'jane@example.com' },
|
|
970
|
+
],
|
|
971
|
+
}],
|
|
972
|
+
},
|
|
973
|
+
};
|
|
974
|
+
|
|
975
|
+
it('should map bulk customer profiles to test customers', () => {
|
|
976
|
+
const generator = getBulkCustomerDetails({ fetchedUserIds: [101] });
|
|
977
|
+
|
|
978
|
+
expect(generator.next().value).toEqual(
|
|
979
|
+
call(Api.getBulkCustomerDetails, { userIds: [101] })
|
|
980
|
+
);
|
|
981
|
+
expect(generator.next({ result: { data: [validItem] } }).value).toEqual(
|
|
982
|
+
put({
|
|
983
|
+
type: GET_TEST_CUSTOMERS_SUCCESS,
|
|
984
|
+
payload: {
|
|
985
|
+
customers: [{
|
|
986
|
+
userId: 101, name: 'Jane Doe', mobile: '919999999999', email: 'jane@example.com',
|
|
987
|
+
}],
|
|
988
|
+
},
|
|
989
|
+
})
|
|
990
|
+
);
|
|
991
|
+
});
|
|
992
|
+
|
|
993
|
+
it('should skip customers with an empty profile instead of failing the whole list', () => {
|
|
994
|
+
const emptyProfileItem = {
|
|
995
|
+
entity: { id: 102, profiles: [] },
|
|
996
|
+
errors: [{ message: 'Customer is deleted after PII delete request', code: 8109, status: false }],
|
|
997
|
+
success: false,
|
|
998
|
+
};
|
|
999
|
+
const generator = getBulkCustomerDetails({ fetchedUserIds: [101, 102] });
|
|
1000
|
+
|
|
1001
|
+
generator.next();
|
|
1002
|
+
expect(generator.next({ result: { data: [emptyProfileItem, validItem] } }).value).toEqual(
|
|
1003
|
+
put({
|
|
1004
|
+
type: GET_TEST_CUSTOMERS_SUCCESS,
|
|
1005
|
+
payload: {
|
|
1006
|
+
customers: [{
|
|
1007
|
+
userId: 101, name: 'Jane Doe', mobile: '919999999999', email: 'jane@example.com',
|
|
1008
|
+
}],
|
|
1009
|
+
},
|
|
1010
|
+
})
|
|
1011
|
+
);
|
|
1012
|
+
});
|
|
1013
|
+
});
|
|
1014
|
+
|
|
960
1015
|
describe('fetchTestGroupsSaga', () => {
|
|
961
1016
|
it('should fetch test groups successfully', () => {
|
|
962
1017
|
const response = {
|