@capillarytech/creatives-library 8.0.241 → 8.0.242-alpha.0
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/sagas/__tests__/assetPolling.test.js +607 -0
- package/sagas/assetPolling.js +156 -0
- package/services/api.js +16 -0
- package/services/tests/api.test.js +124 -0
- package/translations/en.json +1 -0
- package/utils/assetStatusConstants.js +12 -0
- package/utils/asyncAssetUpload.js +161 -0
- package/utils/tests/asyncAssetUpload.test.js +292 -0
- package/utils/transformerUtils.js +42 -0
- package/v2Components/CapImageUpload/constants.js +2 -0
- package/v2Components/CapImageUpload/index.js +54 -14
- package/v2Components/CapImageUpload/index.scss +4 -1
- package/v2Components/CapImageUpload/messages.js +4 -0
- package/v2Components/CapImageUrlUpload/constants.js +19 -0
- package/v2Components/CapImageUrlUpload/index.js +455 -0
- package/v2Components/CapImageUrlUpload/index.scss +35 -0
- package/v2Components/CapImageUrlUpload/messages.js +47 -0
- package/v2Containers/App/constants.js +5 -0
- package/v2Containers/Cap/tests/__snapshots__/index.test.js.snap +1 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +57 -2
- package/v2Containers/CreativesContainer/SlideBoxHeader.js +1 -0
- package/v2Containers/CreativesContainer/constants.js +2 -0
- package/v2Containers/CreativesContainer/index.js +152 -0
- package/v2Containers/CreativesContainer/messages.js +4 -0
- package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +3 -0
- package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +2 -0
- package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +25 -0
- package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +18 -0
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +46 -0
- package/v2Containers/SmsTrai/Create/tests/__snapshots__/index.test.js.snap +4 -0
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +8 -0
- package/v2Containers/Templates/ChannelTypeIllustration.js +13 -1
- package/v2Containers/Templates/_templates.scss +203 -0
- package/v2Containers/Templates/actions.js +2 -1
- package/v2Containers/Templates/constants.js +1 -0
- package/v2Containers/Templates/index.js +273 -30
- package/v2Containers/Templates/messages.js +24 -0
- package/v2Containers/Templates/reducer.js +2 -0
- package/v2Containers/Templates/tests/index.test.js +10 -0
- package/v2Containers/TemplatesV2/index.js +3 -2
- package/v2Containers/TemplatesV2/messages.js +4 -0
- package/v2Containers/WebPush/Create/components/ButtonForm.js +175 -0
- package/v2Containers/WebPush/Create/components/ButtonItem.js +101 -0
- package/v2Containers/WebPush/Create/components/ButtonList.js +144 -0
- package/v2Containers/WebPush/Create/components/_buttons.scss +246 -0
- package/v2Containers/WebPush/Create/components/tests/ButtonForm.test.js +554 -0
- package/v2Containers/WebPush/Create/components/tests/ButtonItem.test.js +607 -0
- package/v2Containers/WebPush/Create/components/tests/ButtonList.test.js +633 -0
- package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonForm.test.js.snap +666 -0
- package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonItem.test.js.snap +74 -0
- package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonList.test.js.snap +80 -0
- package/v2Containers/WebPush/Create/index.js +1755 -0
- package/v2Containers/WebPush/Create/index.scss +123 -0
- package/v2Containers/WebPush/Create/messages.js +199 -0
- package/v2Containers/WebPush/Create/preview/DevicePreviewContent.js +241 -0
- package/v2Containers/WebPush/Create/preview/NotificationContainer.js +290 -0
- package/v2Containers/WebPush/Create/preview/PreviewContent.js +81 -0
- package/v2Containers/WebPush/Create/preview/PreviewControls.js +240 -0
- package/v2Containers/WebPush/Create/preview/PreviewDisclaimer.js +23 -0
- package/v2Containers/WebPush/Create/preview/WebPushPreview.js +144 -0
- package/v2Containers/WebPush/Create/preview/assets/Light.svg +53 -0
- package/v2Containers/WebPush/Create/preview/assets/Top.svg +5 -0
- package/v2Containers/WebPush/Create/preview/assets/chrome-icon.png +0 -0
- package/v2Containers/WebPush/Create/preview/assets/edge-icon.png +0 -0
- package/v2Containers/WebPush/Create/preview/assets/firefox-icon.svg +106 -0
- package/v2Containers/WebPush/Create/preview/assets/iOS.svg +26 -0
- package/v2Containers/WebPush/Create/preview/assets/opera-icon.svg +18 -0
- package/v2Containers/WebPush/Create/preview/assets/safari-icon.svg +29 -0
- package/v2Containers/WebPush/Create/preview/components/AndroidMobileChromeHeader.js +44 -0
- package/v2Containers/WebPush/Create/preview/components/AndroidMobileExpanded.js +110 -0
- package/v2Containers/WebPush/Create/preview/components/IOSHeader.js +45 -0
- package/v2Containers/WebPush/Create/preview/components/NotificationExpandedContent.js +72 -0
- package/v2Containers/WebPush/Create/preview/components/NotificationHeader.js +55 -0
- package/v2Containers/WebPush/Create/preview/components/WindowsChromeExpanded.js +70 -0
- package/v2Containers/WebPush/Create/preview/components/tests/AndroidMobileExpanded.test.js +512 -0
- package/v2Containers/WebPush/Create/preview/components/tests/__snapshots__/AndroidMobileExpanded.test.js.snap +77 -0
- package/v2Containers/WebPush/Create/preview/config/notificationMappings.js +527 -0
- package/v2Containers/WebPush/Create/preview/constants.js +162 -0
- package/v2Containers/WebPush/Create/preview/notification-container.scss +104 -0
- package/v2Containers/WebPush/Create/preview/preview.scss +409 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-mobile-chrome.scss +300 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-mobile-edge.scss +12 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-mobile-firefox.scss +12 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-mobile-opera.scss +12 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-tablet-chrome.scss +303 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-tablet-edge.scss +11 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-tablet-firefox.scss +11 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-tablet-opera.scss +11 -0
- package/v2Containers/WebPush/Create/preview/styles/_base.scss +188 -0
- package/v2Containers/WebPush/Create/preview/styles/_ios.scss +106 -0
- package/v2Containers/WebPush/Create/preview/styles/_ipados.scss +107 -0
- package/v2Containers/WebPush/Create/preview/styles/_macos-chrome.scss +75 -0
- package/v2Containers/WebPush/Create/preview/styles/_windows-chrome.scss +174 -0
- package/v2Containers/WebPush/Create/preview/tests/DevicePreviewContent.test.js +909 -0
- package/v2Containers/WebPush/Create/preview/tests/NotificationContainer.test.js +1077 -0
- package/v2Containers/WebPush/Create/preview/tests/PreviewControls.test.js +723 -0
- package/v2Containers/WebPush/Create/preview/tests/WebPushPreview.test.js +943 -0
- package/v2Containers/WebPush/Create/preview/tests/__snapshots__/DevicePreviewContent.test.js.snap +128 -0
- package/v2Containers/WebPush/Create/preview/tests/__snapshots__/NotificationContainer.test.js.snap +121 -0
- package/v2Containers/WebPush/Create/preview/tests/__snapshots__/PreviewControls.test.js.snap +144 -0
- package/v2Containers/WebPush/Create/preview/tests/__snapshots__/WebPushPreview.test.js.snap +127 -0
- package/v2Containers/WebPush/Create/utils/urlValidation.js +116 -0
- package/v2Containers/WebPush/Create/utils/urlValidation.test.js +449 -0
- package/v2Containers/WebPush/actions.js +60 -0
- package/v2Containers/WebPush/constants.js +108 -0
- package/v2Containers/WebPush/index.js +2 -0
- package/v2Containers/WebPush/reducer.js +104 -0
- package/v2Containers/WebPush/sagas.js +119 -0
- package/v2Containers/WebPush/selectors.js +65 -0
- package/v2Containers/WebPush/tests/reducer.test.js +863 -0
- package/v2Containers/WebPush/tests/sagas.test.js +566 -0
- package/v2Containers/WebPush/tests/selectors.test.js +960 -0
- package/v2Containers/Whatsapp/constants.js +9 -0
- package/v2Containers/Whatsapp/reducer.js +34 -5
- package/v2Containers/Whatsapp/sagas.js +61 -10
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +132 -0
- package/v2Containers/Whatsapp/tests/reducer.test.js +188 -0
- package/v2Containers/Whatsapp/tests/saga.test.js +420 -7
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized Asset Status Polling Saga
|
|
3
|
+
* Reusable across all channels (WhatsApp, MobilePush, Email, SMS, etc.)
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* import { pollAssetStatus } from '../../sagas/assetPolling';
|
|
7
|
+
*
|
|
8
|
+
* yield call(pollAssetStatus, {
|
|
9
|
+
* type: 'video',
|
|
10
|
+
* assetId: 'asset-123',
|
|
11
|
+
* onProcessing: (data) => ({ type: 'ASSET_PROCESSING', payload: data }),
|
|
12
|
+
* onCompleted: (asset) => ({ type: 'ASSET_COMPLETED', payload: asset }),
|
|
13
|
+
* onFailed: (error) => ({ type: 'ASSET_FAILED', payload: error }),
|
|
14
|
+
* onTimeout: () => ({ type: 'ASSET_TIMEOUT' })
|
|
15
|
+
* });
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { call, put } from 'redux-saga/effects';
|
|
19
|
+
import { getAssetStatus } from '../services/api';
|
|
20
|
+
import { ASSET_STATUS } from '../utils/assetStatusConstants';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Delay utility function compatible with redux-saga 0.16.2
|
|
24
|
+
* Returns a promise that resolves after the specified milliseconds
|
|
25
|
+
* @param {number} ms - Milliseconds to delay
|
|
26
|
+
* @returns {Promise} Promise that resolves after delay
|
|
27
|
+
*/
|
|
28
|
+
export function delay(ms) {
|
|
29
|
+
return new Promise((resolve) => {
|
|
30
|
+
setTimeout(resolve, ms);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Generic asset status polling saga
|
|
36
|
+
* @param {Object} config - Polling configuration
|
|
37
|
+
* @param {string} config.type - Asset type (image, video, document, gif)
|
|
38
|
+
* @param {string} config.assetId - Asset ID to poll
|
|
39
|
+
* @param {Function} config.onCompleted - Action creator for completed state
|
|
40
|
+
* @param {Function} config.onFailed - Action creator for failed state
|
|
41
|
+
* @param {Function} config.onTimeout - Action creator for timeout state
|
|
42
|
+
* @param {number} config.maxDuration - Max polling duration in ms (default: 90000)
|
|
43
|
+
* @param {number} config.initialDelay - Initial delay before first poll in ms (default: 1000)
|
|
44
|
+
* @param {number} config.pollInterval - Interval between polls in ms (default: 2000)
|
|
45
|
+
*/
|
|
46
|
+
export function* pollAssetStatus(config) {
|
|
47
|
+
const {
|
|
48
|
+
type,
|
|
49
|
+
assetId,
|
|
50
|
+
onCompleted,
|
|
51
|
+
onFailed,
|
|
52
|
+
onTimeout,
|
|
53
|
+
maxDuration = 90000, // 90 seconds default
|
|
54
|
+
initialDelay: configInitialDelay = 1000, // 1 second default
|
|
55
|
+
pollInterval = 2000, // 2 seconds default
|
|
56
|
+
} = config;
|
|
57
|
+
|
|
58
|
+
const startTime = Date.now();
|
|
59
|
+
let isFirstPoll = true;
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
while (true) {
|
|
63
|
+
// Initial delay, then regular interval
|
|
64
|
+
// Using call() for compatibility with redux-saga 0.16.2
|
|
65
|
+
yield call(delay, isFirstPoll ? configInitialDelay : pollInterval);
|
|
66
|
+
isFirstPoll = false;
|
|
67
|
+
|
|
68
|
+
// Check timeout
|
|
69
|
+
if (Date.now() - startTime > maxDuration) {
|
|
70
|
+
if (onTimeout) {
|
|
71
|
+
const timeoutAction = onTimeout({
|
|
72
|
+
assetId,
|
|
73
|
+
message: 'Asset processing is taking longer than expected. Please check back later.',
|
|
74
|
+
duration: Date.now() - startTime,
|
|
75
|
+
});
|
|
76
|
+
yield put(timeoutAction);
|
|
77
|
+
}
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Poll status endpoint
|
|
82
|
+
const statusResponse = yield call(getAssetStatus, type, assetId);
|
|
83
|
+
|
|
84
|
+
// Validate API response structure
|
|
85
|
+
// Check for error indicators: success: false, error property, or missing response
|
|
86
|
+
if (!statusResponse || statusResponse.success === false || statusResponse.error) {
|
|
87
|
+
const errorMessage = statusResponse?.error ||
|
|
88
|
+
statusResponse?.message ||
|
|
89
|
+
'Failed to fetch asset status: Invalid API response';
|
|
90
|
+
|
|
91
|
+
if (onFailed) {
|
|
92
|
+
const failedAction = onFailed({
|
|
93
|
+
assetId,
|
|
94
|
+
error: errorMessage,
|
|
95
|
+
duration: Date.now() - startTime,
|
|
96
|
+
});
|
|
97
|
+
yield put(failedAction);
|
|
98
|
+
}
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Extract status data from response
|
|
103
|
+
const statusData = statusResponse?.response || {};
|
|
104
|
+
|
|
105
|
+
// Validate that statusData has a meaningful status property
|
|
106
|
+
if (!statusData || typeof statusData.status !== 'string') {
|
|
107
|
+
const errorMessage = 'Invalid asset status response: Missing or invalid status field';
|
|
108
|
+
|
|
109
|
+
if (onFailed) {
|
|
110
|
+
const failedAction = onFailed({
|
|
111
|
+
assetId,
|
|
112
|
+
error: errorMessage,
|
|
113
|
+
duration: Date.now() - startTime,
|
|
114
|
+
});
|
|
115
|
+
yield put(failedAction);
|
|
116
|
+
}
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (statusData.status === ASSET_STATUS.COMPLETED) {
|
|
121
|
+
// Asset completed successfully
|
|
122
|
+
if (onCompleted) {
|
|
123
|
+
const completedAction = onCompleted({
|
|
124
|
+
assetId,
|
|
125
|
+
asset: statusData.asset,
|
|
126
|
+
duration: Date.now() - startTime,
|
|
127
|
+
});
|
|
128
|
+
yield put(completedAction);
|
|
129
|
+
}
|
|
130
|
+
break;
|
|
131
|
+
} else if (statusData.status === ASSET_STATUS.FAILED) {
|
|
132
|
+
// Asset processing failed
|
|
133
|
+
if (onFailed) {
|
|
134
|
+
const failedAction = onFailed({
|
|
135
|
+
assetId,
|
|
136
|
+
error: statusData.error || 'Processing failed',
|
|
137
|
+
duration: Date.now() - startTime,
|
|
138
|
+
});
|
|
139
|
+
yield put(failedAction);
|
|
140
|
+
}
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Continue polling if still processing
|
|
145
|
+
}
|
|
146
|
+
} catch (error) {
|
|
147
|
+
// Handle polling errors (network issues, etc.)
|
|
148
|
+
if (onFailed) {
|
|
149
|
+
const errorAction = onFailed({
|
|
150
|
+
assetId,
|
|
151
|
+
error: `Failed to check asset status: ${error.message}`,
|
|
152
|
+
});
|
|
153
|
+
yield put(errorAction);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
package/services/api.js
CHANGED
|
@@ -287,6 +287,11 @@ export const createMobilePushTemplateV2 = (template) => {
|
|
|
287
287
|
return request(url, getAPICallObject('POST', template));
|
|
288
288
|
};
|
|
289
289
|
|
|
290
|
+
export const createWebPushTemplate = (template) => {
|
|
291
|
+
const url = `${API_ENDPOINT}/templates/WEBPUSH`;
|
|
292
|
+
return request(url, getAPICallObject('POST', template));
|
|
293
|
+
};
|
|
294
|
+
|
|
290
295
|
export const duplicateTemplate = ({id, channel}) => {
|
|
291
296
|
const url = `${API_ENDPOINT}/templates/duplicate/${id}/${channel}`;
|
|
292
297
|
return request(url, getAPICallObject('GET'));
|
|
@@ -709,4 +714,15 @@ export const updateTestMessageMeta = (payload) => {
|
|
|
709
714
|
return request(url, getAPICallObject('POST', payload?.data, false, true));
|
|
710
715
|
};
|
|
711
716
|
|
|
717
|
+
/**
|
|
718
|
+
* P4.1: Get asset processing status for async uploads
|
|
719
|
+
* @param {string} type - Asset type (image, video, document)
|
|
720
|
+
* @param {string} assetId - Asset ID to check status
|
|
721
|
+
* @returns {Promise} Promise resolving to asset status
|
|
722
|
+
*/
|
|
723
|
+
export const getAssetStatus = (type, assetId) => {
|
|
724
|
+
const url = `${API_ENDPOINT}/assets/${type}/${assetId}/status`;
|
|
725
|
+
return request(url, getAPICallObject('GET'));
|
|
726
|
+
};
|
|
727
|
+
|
|
712
728
|
export {request, getAPICallObject};
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
updateTestMessageMeta,
|
|
26
26
|
updateMetaConfig,
|
|
27
27
|
getMediaDetails,
|
|
28
|
+
getAssetStatus,
|
|
28
29
|
} from '../api';
|
|
29
30
|
import { mockData } from './mockData';
|
|
30
31
|
import getSchema from '../getSchema';
|
|
@@ -849,3 +850,126 @@ describe('getMediaDetails', () => {
|
|
|
849
850
|
});
|
|
850
851
|
});
|
|
851
852
|
});
|
|
853
|
+
|
|
854
|
+
describe('getAssetStatus', () => {
|
|
855
|
+
beforeEach(() => {
|
|
856
|
+
global.fetch = jest.fn();
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
afterEach(() => {
|
|
860
|
+
jest.restoreAllMocks();
|
|
861
|
+
});
|
|
862
|
+
|
|
863
|
+
it('should return correct response on success', async () => {
|
|
864
|
+
const mockResponse = {
|
|
865
|
+
status: 200,
|
|
866
|
+
json: () => Promise.resolve({
|
|
867
|
+
status: 200,
|
|
868
|
+
response: {
|
|
869
|
+
assetId: 'asset-123',
|
|
870
|
+
status: 'completed',
|
|
871
|
+
asset: {
|
|
872
|
+
_id: 'asset-123',
|
|
873
|
+
type: 'IMAGE',
|
|
874
|
+
url: 'https://example.com/image.jpg',
|
|
875
|
+
},
|
|
876
|
+
},
|
|
877
|
+
}),
|
|
878
|
+
};
|
|
879
|
+
global.fetch.mockReturnValue(Promise.resolve(mockResponse));
|
|
880
|
+
|
|
881
|
+
const result = await getAssetStatus('image', 'asset-123');
|
|
882
|
+
|
|
883
|
+
expect(result).toEqual({
|
|
884
|
+
status: 200,
|
|
885
|
+
response: {
|
|
886
|
+
assetId: 'asset-123',
|
|
887
|
+
status: 'completed',
|
|
888
|
+
asset: {
|
|
889
|
+
_id: 'asset-123',
|
|
890
|
+
type: 'IMAGE',
|
|
891
|
+
url: 'https://example.com/image.jpg',
|
|
892
|
+
},
|
|
893
|
+
},
|
|
894
|
+
});
|
|
895
|
+
});
|
|
896
|
+
|
|
897
|
+
it('should handle processing status', async () => {
|
|
898
|
+
const mockResponse = {
|
|
899
|
+
status: 200,
|
|
900
|
+
json: () => Promise.resolve({
|
|
901
|
+
status: 200,
|
|
902
|
+
response: {
|
|
903
|
+
assetId: 'asset-123',
|
|
904
|
+
status: 'processing',
|
|
905
|
+
message: 'Asset is being processed',
|
|
906
|
+
},
|
|
907
|
+
}),
|
|
908
|
+
};
|
|
909
|
+
global.fetch.mockReturnValue(Promise.resolve(mockResponse));
|
|
910
|
+
|
|
911
|
+
const result = await getAssetStatus('video', 'asset-123');
|
|
912
|
+
|
|
913
|
+
expect(result).toEqual({
|
|
914
|
+
status: 200,
|
|
915
|
+
response: {
|
|
916
|
+
assetId: 'asset-123',
|
|
917
|
+
status: 'processing',
|
|
918
|
+
message: 'Asset is being processed',
|
|
919
|
+
},
|
|
920
|
+
});
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
it('should handle failed status', async () => {
|
|
924
|
+
const mockResponse = {
|
|
925
|
+
status: 200,
|
|
926
|
+
json: () => Promise.resolve({
|
|
927
|
+
status: 200,
|
|
928
|
+
response: {
|
|
929
|
+
assetId: 'asset-123',
|
|
930
|
+
status: 'failed',
|
|
931
|
+
error: 'Processing failed',
|
|
932
|
+
},
|
|
933
|
+
}),
|
|
934
|
+
};
|
|
935
|
+
global.fetch.mockReturnValue(Promise.resolve(mockResponse));
|
|
936
|
+
|
|
937
|
+
const result = await getAssetStatus('document', 'asset-123');
|
|
938
|
+
|
|
939
|
+
expect(result).toEqual({
|
|
940
|
+
status: 200,
|
|
941
|
+
response: {
|
|
942
|
+
assetId: 'asset-123',
|
|
943
|
+
status: 'failed',
|
|
944
|
+
error: 'Processing failed',
|
|
945
|
+
},
|
|
946
|
+
});
|
|
947
|
+
});
|
|
948
|
+
|
|
949
|
+
it('should handle fetch failure', async () => {
|
|
950
|
+
global.fetch.mockRejectedValue({ error: 'Network error' });
|
|
951
|
+
|
|
952
|
+
const result = await getAssetStatus('image', 'asset-123');
|
|
953
|
+
|
|
954
|
+
expect(result).toEqual({
|
|
955
|
+
error: 'Network error',
|
|
956
|
+
});
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
it('should construct correct URL with type and assetId', async () => {
|
|
960
|
+
const mockResponse = {
|
|
961
|
+
status: 200,
|
|
962
|
+
json: () => Promise.resolve({
|
|
963
|
+
status: 200,
|
|
964
|
+
response: { assetId: 'asset-123', status: 'processing' },
|
|
965
|
+
}),
|
|
966
|
+
};
|
|
967
|
+
global.fetch.mockReturnValue(Promise.resolve(mockResponse));
|
|
968
|
+
|
|
969
|
+
await getAssetStatus('video', 'asset-456');
|
|
970
|
+
|
|
971
|
+
expect(global.fetch).toHaveBeenCalled();
|
|
972
|
+
const callArgs = global.fetch.mock.calls[0];
|
|
973
|
+
expect(callArgs[0]).toContain('/assets/video/asset-456/status');
|
|
974
|
+
});
|
|
975
|
+
});
|
package/translations/en.json
CHANGED
|
@@ -2022,6 +2022,7 @@
|
|
|
2022
2022
|
"creatives.containersV2.Whatsapp.vietnamese": "Vietnamese",
|
|
2023
2023
|
"creatives.containersV2.Whatsapp.whatsappCreateNotification": "{name} has been sent for approval",
|
|
2024
2024
|
"creatives.containersV2.Whatsapp.zulu": "Zulu",
|
|
2025
|
+
"creatives.containersV2.WebPush.addLabels": "Add labels",
|
|
2025
2026
|
"creatives.containersV2.addLabels": "Add labels",
|
|
2026
2027
|
"creatives.containersV2.appName": "App Name",
|
|
2027
2028
|
"creatives.containersV2.applyNow": "Apply now",
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asset Processing Status Constants
|
|
3
|
+
* Matches backend ASSET_STATUS constants from cap-creatives-api/src/constants/globals.js
|
|
4
|
+
* Used across sagas, reducers, and utilities for consistent status handling
|
|
5
|
+
*/
|
|
6
|
+
export const ASSET_STATUS = {
|
|
7
|
+
PROCESSING: 'processing',
|
|
8
|
+
COMPLETED: 'completed',
|
|
9
|
+
FAILED: 'failed',
|
|
10
|
+
TIMEOUT: 'timeout', // UI-specific status for polling timeout
|
|
11
|
+
};
|
|
12
|
+
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic Async Asset Upload Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides reusable constants, reducer cases, and action creators for async asset uploads
|
|
5
|
+
* across all channels (WhatsApp, MobilePush, Email, SMS, etc.)
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* import { createAsyncAssetUploadConstants, createAsyncAssetUploadReducerCases } from '../../utils/asyncAssetUpload';
|
|
9
|
+
*
|
|
10
|
+
* // In constants.js
|
|
11
|
+
* export const {
|
|
12
|
+
* UPLOAD_ASSET_PROCESSING,
|
|
13
|
+
* UPLOAD_ASSET_COMPLETED,
|
|
14
|
+
* UPLOAD_ASSET_FAILED,
|
|
15
|
+
* UPLOAD_ASSET_TIMEOUT,
|
|
16
|
+
* } = createAsyncAssetUploadConstants('WHATSAPP'); // or 'MOBILEPUSH', 'EMAIL', etc.
|
|
17
|
+
*
|
|
18
|
+
* // In reducer.js
|
|
19
|
+
* import { createAsyncAssetUploadReducerCases } from '../../utils/asyncAssetUpload';
|
|
20
|
+
* const asyncUploadCases = createAsyncAssetUploadReducerCases({
|
|
21
|
+
* PROCESSING: UPLOAD_ASSET_PROCESSING,
|
|
22
|
+
* COMPLETED: UPLOAD_ASSET_COMPLETED,
|
|
23
|
+
* FAILED: UPLOAD_ASSET_FAILED,
|
|
24
|
+
* TIMEOUT: UPLOAD_ASSET_TIMEOUT,
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* // In reducer switch statement
|
|
28
|
+
* case asyncUploadCases.PROCESSING:
|
|
29
|
+
* return asyncUploadCases.handleProcessing(state, action);
|
|
30
|
+
* case asyncUploadCases.COMPLETED:
|
|
31
|
+
* return asyncUploadCases.handleCompleted(state, action);
|
|
32
|
+
* // etc.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import { fromJS } from 'immutable';
|
|
36
|
+
import { ASSET_STATUS } from './assetStatusConstants';
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Creates async asset upload constants for a given channel
|
|
40
|
+
* @param {string} channelPrefix - Channel prefix (e.g., 'WHATSAPP', 'MOBILEPUSH', 'EMAIL')
|
|
41
|
+
* @param {string} containerPath - Container path (e.g., 'app/v2Containers/Whatsapp')
|
|
42
|
+
* @returns {Object} Object containing all async upload constants
|
|
43
|
+
*/
|
|
44
|
+
export function createAsyncAssetUploadConstants(channelPrefix, containerPath) {
|
|
45
|
+
const prefix = containerPath || `app/v2Containers/${channelPrefix}`;
|
|
46
|
+
const channelName = channelPrefix.toUpperCase();
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
[`UPLOAD_${channelName}_ASSET_PROCESSING`]: `${prefix}/UPLOAD_${channelName}_ASSET_PROCESSING`,
|
|
50
|
+
[`UPLOAD_${channelName}_ASSET_COMPLETED`]: `${prefix}/UPLOAD_${channelName}_ASSET_COMPLETED`,
|
|
51
|
+
[`UPLOAD_${channelName}_ASSET_FAILED`]: `${prefix}/UPLOAD_${channelName}_ASSET_FAILED`,
|
|
52
|
+
[`UPLOAD_${channelName}_ASSET_TIMEOUT`]: `${prefix}/UPLOAD_${channelName}_ASSET_TIMEOUT`,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Creates reducer case handlers for async asset uploads
|
|
58
|
+
* @param {Object} actionTypes - Action type constants
|
|
59
|
+
* @param {Object} options - Configuration options
|
|
60
|
+
* @param {string} options.uploadedAssetDataKey - Key for uploaded asset data (default: 'uploadedAssetData')
|
|
61
|
+
* @param {string} options.assetUploadingKey - Key for asset uploading flag (default: 'assetUploading')
|
|
62
|
+
* @param {string} options.uploadAssetSuccessKey - Key for upload success flag (default: 'uploadAssetSuccess')
|
|
63
|
+
* @param {string} options.assetProcessingKey - Key for asset processing state (default: 'assetProcessing')
|
|
64
|
+
* @returns {Object} Object containing reducer case handlers
|
|
65
|
+
*/
|
|
66
|
+
export function createAsyncAssetUploadReducerCases(actionTypes, options = {}) {
|
|
67
|
+
const {
|
|
68
|
+
uploadedAssetDataKey = 'uploadedAssetData',
|
|
69
|
+
assetUploadingKey = 'assetUploading',
|
|
70
|
+
uploadAssetSuccessKey = 'uploadAssetSuccess',
|
|
71
|
+
assetProcessingKey = 'assetProcessing',
|
|
72
|
+
} = options;
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
// Action type constants for use in switch statements
|
|
76
|
+
PROCESSING: actionTypes.PROCESSING,
|
|
77
|
+
COMPLETED: actionTypes.COMPLETED,
|
|
78
|
+
FAILED: actionTypes.FAILED,
|
|
79
|
+
TIMEOUT: actionTypes.TIMEOUT,
|
|
80
|
+
|
|
81
|
+
// Reducer handlers
|
|
82
|
+
handleProcessing: (state, action) => {
|
|
83
|
+
const startTime = new Date().getTime();
|
|
84
|
+
return state
|
|
85
|
+
.set(uploadAssetSuccessKey, false)
|
|
86
|
+
.set(assetUploadingKey, true)
|
|
87
|
+
.setIn([assetProcessingKey, action.payload.assetId], fromJS({
|
|
88
|
+
status: ASSET_STATUS.PROCESSING,
|
|
89
|
+
startTime,
|
|
90
|
+
asset: action.payload.asset,
|
|
91
|
+
error: null
|
|
92
|
+
}));
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
handleCompleted: (state, action) => {
|
|
96
|
+
const templateType = action.templateType;
|
|
97
|
+
const assetDataKey = templateType !== undefined
|
|
98
|
+
? `${uploadedAssetDataKey}${templateType}`
|
|
99
|
+
: uploadedAssetDataKey;
|
|
100
|
+
|
|
101
|
+
return state
|
|
102
|
+
.set(uploadAssetSuccessKey, true)
|
|
103
|
+
.set(assetUploadingKey, false)
|
|
104
|
+
.setIn([assetProcessingKey, action.payload.assetId], fromJS({
|
|
105
|
+
status: ASSET_STATUS.COMPLETED,
|
|
106
|
+
asset: action.payload.asset,
|
|
107
|
+
error: null
|
|
108
|
+
}))
|
|
109
|
+
.set(assetDataKey, fromJS(action.payload.asset));
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
handleFailed: (state, action) => {
|
|
113
|
+
return state
|
|
114
|
+
.set(uploadAssetSuccessKey, false)
|
|
115
|
+
.set(assetUploadingKey, false)
|
|
116
|
+
.setIn([assetProcessingKey, action.payload.assetId], fromJS({
|
|
117
|
+
status: ASSET_STATUS.FAILED,
|
|
118
|
+
error: action.payload.error
|
|
119
|
+
}));
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
handleTimeout: (state, action) => {
|
|
123
|
+
return state
|
|
124
|
+
.set(uploadAssetSuccessKey, false)
|
|
125
|
+
.set(assetUploadingKey, false)
|
|
126
|
+
.setIn([assetProcessingKey, action.payload.assetId], fromJS({
|
|
127
|
+
status: ASSET_STATUS.TIMEOUT,
|
|
128
|
+
error: action.payload.message
|
|
129
|
+
}));
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Creates polling configuration for use with centralized pollAssetStatus saga
|
|
136
|
+
* @param {string} assetType - Asset type ('image', 'video', 'document', etc.)
|
|
137
|
+
* @param {string} assetId - Asset ID
|
|
138
|
+
* @param {Object} actionTypes - Action type constants
|
|
139
|
+
* @returns {Object} Polling configuration object
|
|
140
|
+
*/
|
|
141
|
+
export function createPollingConfig(assetType, assetId, actionTypes, templateType) {
|
|
142
|
+
return {
|
|
143
|
+
type: assetType,
|
|
144
|
+
assetId,
|
|
145
|
+
onCompleted: (data) => ({
|
|
146
|
+
type: actionTypes.COMPLETED,
|
|
147
|
+
payload: data,
|
|
148
|
+
templateType,
|
|
149
|
+
}),
|
|
150
|
+
onFailed: (data) => ({
|
|
151
|
+
type: actionTypes.FAILED,
|
|
152
|
+
payload: data,
|
|
153
|
+
templateType,
|
|
154
|
+
}),
|
|
155
|
+
onTimeout: (data) => ({
|
|
156
|
+
type: actionTypes.TIMEOUT,
|
|
157
|
+
payload: data,
|
|
158
|
+
templateType,
|
|
159
|
+
}),
|
|
160
|
+
};
|
|
161
|
+
}
|