@capillarytech/creatives-library 7.17.25 → 7.17.26-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/utils/common.js +11 -14
- package/v2Containers/ChannelTemplates/actions.js +20 -0
- package/v2Containers/ChannelTemplates/constants.js +8 -0
- package/v2Containers/ChannelTemplates/index.js +47 -0
- package/v2Containers/ChannelTemplates/messages.js +13 -0
- package/v2Containers/ChannelTemplates/reducer.js +34 -0
- package/v2Containers/ChannelTemplates/sagas.js +32 -0
- package/v2Containers/ChannelTemplates/selectors.js +25 -0
- package/v2Containers/Sms/Create/tests/sagas.test.js +82 -0
- package/v2Containers/TagList/index.js +30 -39
package/package.json
CHANGED
package/utils/common.js
CHANGED
|
@@ -282,16 +282,13 @@ export const isTraiDLTEnable = (isFullMode, smsRegister) => {
|
|
|
282
282
|
return isTraiDltFeature;
|
|
283
283
|
};
|
|
284
284
|
|
|
285
|
-
export const intlKeyGenerator = (value = "") =>
|
|
286
|
-
return value.replace(/[^a-zA-Z0-9_]/g, "");
|
|
287
|
-
};
|
|
285
|
+
export const intlKeyGenerator = (value = "") => String(value).replace(/[^a-zA-Z0-9_]/g, "");
|
|
288
286
|
|
|
289
287
|
export const handleInjectedData = (data, scope) => {
|
|
290
|
-
const temp = _.cloneDeep(data);
|
|
291
288
|
let tagType;
|
|
292
|
-
for (const tagKey in
|
|
293
|
-
if (
|
|
294
|
-
const tag =
|
|
289
|
+
for (const tagKey in data) {
|
|
290
|
+
if (data.hasOwnProperty(tagKey)) {
|
|
291
|
+
const tag = data[tagKey];
|
|
295
292
|
|
|
296
293
|
if (tag?.name === REGISTRATION_CUSTOM_FIELD) {
|
|
297
294
|
tagType = CUSTOM_TAG;
|
|
@@ -300,19 +297,19 @@ export const handleInjectedData = (data, scope) => {
|
|
|
300
297
|
}
|
|
301
298
|
|
|
302
299
|
if (tag?.name) {
|
|
303
|
-
const name = tag
|
|
300
|
+
const name = tag.name;
|
|
304
301
|
const key = intlKeyGenerator(name);
|
|
305
302
|
const id = tagType
|
|
306
303
|
? `${scope}.${key}_name.${tagType}`
|
|
307
304
|
: `${scope}.${key}`;
|
|
308
305
|
|
|
309
|
-
tag
|
|
306
|
+
tag.name = apiMessageFormatHandler(id, name);
|
|
310
307
|
}
|
|
311
308
|
|
|
312
309
|
if (tag?.subtags) {
|
|
313
310
|
for (const subtagKey in tag.subtags) {
|
|
314
|
-
if (tag
|
|
315
|
-
const subtag = tag
|
|
311
|
+
if (tag.subtags.hasOwnProperty(subtagKey)) {
|
|
312
|
+
const subtag = tag.subtags[subtagKey];
|
|
316
313
|
if (subtag?.name) {
|
|
317
314
|
const name = subtag?.name;
|
|
318
315
|
const key = intlKeyGenerator(name);
|
|
@@ -320,7 +317,7 @@ export const handleInjectedData = (data, scope) => {
|
|
|
320
317
|
? `${scope}.${key}_name.${tagType}`
|
|
321
318
|
: `${scope}.${key}`;
|
|
322
319
|
|
|
323
|
-
subtag
|
|
320
|
+
subtag.name = apiMessageFormatHandler(id, name);
|
|
324
321
|
}
|
|
325
322
|
if (subtag?.desc) {
|
|
326
323
|
const desc = subtag?.desc;
|
|
@@ -329,7 +326,7 @@ export const handleInjectedData = (data, scope) => {
|
|
|
329
326
|
? `${scope}.${key}_desc.${tagType}`
|
|
330
327
|
: `${scope}.${key}`;
|
|
331
328
|
|
|
332
|
-
subtag
|
|
329
|
+
subtag.desc = apiMessageFormatHandler(id, desc);
|
|
333
330
|
}
|
|
334
331
|
}
|
|
335
332
|
}
|
|
@@ -337,5 +334,5 @@ export const handleInjectedData = (data, scope) => {
|
|
|
337
334
|
tagType = "";
|
|
338
335
|
}
|
|
339
336
|
}
|
|
340
|
-
return
|
|
337
|
+
return data;
|
|
341
338
|
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/*
|
|
2
|
+
*
|
|
3
|
+
* ChannelTemplates actions
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as types from './constants';
|
|
8
|
+
|
|
9
|
+
export function defaultAction() {
|
|
10
|
+
return {
|
|
11
|
+
type: types.DEFAULT_ACTION,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function getTemplates(channel) {
|
|
16
|
+
return {
|
|
17
|
+
type: types.GET_TEMPLATES_REQUEST,
|
|
18
|
+
channel,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/*
|
|
2
|
+
*
|
|
3
|
+
* ChannelTemplates
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import PropTypes from 'prop-types';
|
|
8
|
+
|
|
9
|
+
import React from 'react';
|
|
10
|
+
import { connect } from 'react-redux';
|
|
11
|
+
import { FormattedMessage } from 'react-intl';
|
|
12
|
+
import { bindActionCreators } from 'redux';
|
|
13
|
+
import { createStructuredSelector } from 'reselect';
|
|
14
|
+
import makeSelectChannelTemplates from './selectors';
|
|
15
|
+
import * as actions from './actions';
|
|
16
|
+
import messages from './messages';
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
export class ChannelTemplates extends React.Component { // eslint-disable-line react/prefer-stateless-function
|
|
20
|
+
componentDidMount() {
|
|
21
|
+
this.props.actions.getTemplates(this.props.channel);
|
|
22
|
+
}
|
|
23
|
+
render() {
|
|
24
|
+
return (
|
|
25
|
+
<CardGrid
|
|
26
|
+
cardDataList={[]}
|
|
27
|
+
/>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
ChannelTemplates.propTypes = {
|
|
33
|
+
actions: PropTypes.object.isRequired,
|
|
34
|
+
channel: PropTypes.string,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const mapStateToProps = createStructuredSelector({
|
|
38
|
+
ChannelTemplates: makeSelectChannelTemplates(),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
function mapDispatchToProps(dispatch) {
|
|
42
|
+
return {
|
|
43
|
+
actions: bindActionCreators(actions, dispatch),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export default connect(mapStateToProps, mapDispatchToProps)(ChannelTemplates);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* ChannelTemplates Messages
|
|
3
|
+
*
|
|
4
|
+
* This contains all the text for the ChannelTemplates component.
|
|
5
|
+
*/
|
|
6
|
+
import { defineMessages } from 'react-intl';
|
|
7
|
+
|
|
8
|
+
export default defineMessages({
|
|
9
|
+
header: {
|
|
10
|
+
id: 'creatives.containersV2.ChannelTemplates.header',
|
|
11
|
+
defaultMessage: 'This is ChannelTemplates container !',
|
|
12
|
+
},
|
|
13
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/*
|
|
2
|
+
*
|
|
3
|
+
* ChannelTemplates reducer
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { fromJS } from 'immutable';
|
|
8
|
+
import * as types from './constants';
|
|
9
|
+
|
|
10
|
+
const initialState = fromJS({
|
|
11
|
+
loadingTemplates: true,
|
|
12
|
+
smsTemplates: [],
|
|
13
|
+
emailTemplates: [],
|
|
14
|
+
wechatTemplates: [],
|
|
15
|
+
mobilepushTemplates: [],
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
function channelTemplatesReducer(state = initialState, action) {
|
|
19
|
+
const channelTemplates = `${action.channel}Templates`;
|
|
20
|
+
switch (action.type) {
|
|
21
|
+
case types.DEFAULT_ACTION:
|
|
22
|
+
return state;
|
|
23
|
+
case types.GET_TEMPLATES_REQUEST:
|
|
24
|
+
return state.set('loadingTemplates', true);
|
|
25
|
+
case types.GET_TEMPLATES_SUCESS:
|
|
26
|
+
return state.set('loadingTemplates', false).set(channelTemplates, action.templates);
|
|
27
|
+
case types.GET_TEMPLATES_FAILURE:
|
|
28
|
+
return state.set('loadingTemplates', false).set('error', action.error);
|
|
29
|
+
default:
|
|
30
|
+
return state;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default channelTemplatesReducer;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { take, takeLatest, call, put, cancel } from 'redux-saga/effects';
|
|
2
|
+
import { LOCATION_CHANGE } from 'react-router-redux';
|
|
3
|
+
import * as Api from '../../services/api';
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
import * as types from './constants';
|
|
7
|
+
// Individual exports for testing
|
|
8
|
+
export function* defaultSaga() {
|
|
9
|
+
// See example in v2Containers/HomePage/sagas.js
|
|
10
|
+
}
|
|
11
|
+
function* getTemplates(action) {
|
|
12
|
+
try {
|
|
13
|
+
const req = {
|
|
14
|
+
channel: action.channel,
|
|
15
|
+
queryParams: action.query,
|
|
16
|
+
};
|
|
17
|
+
const res = call(Api.getAllTemplates, req);
|
|
18
|
+
put({type: types.GET_TEMPLATES_SUCESS, templates: res.response, channel: action.channel});
|
|
19
|
+
} catch (error) {
|
|
20
|
+
put({type: types.GET_TEMPLATES_FAILURE, error});
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function* getTemplatesWatcher() {
|
|
24
|
+
const watcher = yield takeLatest(types.GET_TEMPLATES_REQUEST, getTemplates);
|
|
25
|
+
yield take(LOCATION_CHANGE);
|
|
26
|
+
yield cancel(watcher);
|
|
27
|
+
}
|
|
28
|
+
// All sagas to be loaded
|
|
29
|
+
export default [
|
|
30
|
+
defaultSaga,
|
|
31
|
+
getTemplatesWatcher,
|
|
32
|
+
];
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createSelector } from 'reselect';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Direct selector to the channelTemplates state domain
|
|
5
|
+
*/
|
|
6
|
+
const selectChannelTemplatesDomain = () => (state) => state.get('templates');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Other specific selectors
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Default selector used by ChannelTemplates
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const makeSelectChannelTemplates = () => createSelector(
|
|
18
|
+
selectChannelTemplatesDomain(),
|
|
19
|
+
(substate) => substate.toJS()
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
export default makeSelectChannelTemplates;
|
|
23
|
+
export {
|
|
24
|
+
selectChannelTemplatesDomain,
|
|
25
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { expectSaga } from "redux-saga-test-plan";
|
|
2
|
+
import { take, cancel, takeLatest } from "redux-saga/effects";
|
|
3
|
+
import * as matchers from "redux-saga-test-plan/matchers";
|
|
4
|
+
import { LOCATION_CHANGE } from "react-router-redux";
|
|
5
|
+
import { throwError } from "redux-saga-test-plan/providers";
|
|
6
|
+
import { createMockTask } from "redux-saga/utils";
|
|
7
|
+
import * as types from "../constants";
|
|
8
|
+
import { watchAiSuggestions, getAiSuggestions } from "../sagas";
|
|
9
|
+
import * as Api from "../../../../services/api";
|
|
10
|
+
|
|
11
|
+
describe("getAiSuggestions saga", () => {
|
|
12
|
+
it("Should handle valid response from api", () => {
|
|
13
|
+
const successCallback = () => {};
|
|
14
|
+
const failureCallback = () => {};
|
|
15
|
+
const action = {
|
|
16
|
+
type: types.GET_AI_SUGGESTIONS,
|
|
17
|
+
prompt: {},
|
|
18
|
+
successCallback,
|
|
19
|
+
failureCallback,
|
|
20
|
+
};
|
|
21
|
+
expectSaga(getAiSuggestions, action)
|
|
22
|
+
.provide([
|
|
23
|
+
[
|
|
24
|
+
matchers.call.fn(Api.getAiSuggestions),
|
|
25
|
+
{
|
|
26
|
+
success: true,
|
|
27
|
+
status: {
|
|
28
|
+
isError: false,
|
|
29
|
+
code: 200,
|
|
30
|
+
message: "success",
|
|
31
|
+
},
|
|
32
|
+
message: "Meta data fetched successfully",
|
|
33
|
+
response: {
|
|
34
|
+
"https://response.com": 1400,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
[matchers.call.fn(successCallback)],
|
|
39
|
+
])
|
|
40
|
+
.run();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("Should handles error thrown from api", () => {
|
|
44
|
+
const successCallback = () => {};
|
|
45
|
+
const failureCallback = () => {};
|
|
46
|
+
const action = {
|
|
47
|
+
type: types.GET_AI_SUGGESTIONS,
|
|
48
|
+
prompt: {},
|
|
49
|
+
successCallback,
|
|
50
|
+
failureCallback,
|
|
51
|
+
};
|
|
52
|
+
expectSaga(getAiSuggestions, action)
|
|
53
|
+
.provide([[matchers.call.fn(Api.getAiSuggestions), throwError()]])
|
|
54
|
+
.run();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe("watchAiSuggestions saga", () => {
|
|
59
|
+
let generator = null;
|
|
60
|
+
beforeEach(() => {
|
|
61
|
+
generator = watchAiSuggestions();
|
|
62
|
+
});
|
|
63
|
+
it("Should handle valid response from api", () => {
|
|
64
|
+
const progress1 = generator.next();
|
|
65
|
+
const mockTask = takeLatest(types.GET_AI_SUGGESTIONS, getAiSuggestions);
|
|
66
|
+
expect(progress1.value).toEqual(mockTask);
|
|
67
|
+
const progress2 = generator.next();
|
|
68
|
+
expect(progress2.value).toEqual(take(LOCATION_CHANGE));
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("Should handle LOCATION_CHANGE action and cancel the watcher", () => {
|
|
72
|
+
generator = watchAiSuggestions();
|
|
73
|
+
const mockTask = createMockTask();
|
|
74
|
+
|
|
75
|
+
expect(generator.next().value).toEqual(
|
|
76
|
+
takeLatest(types.GET_AI_SUGGESTIONS, getAiSuggestions)
|
|
77
|
+
);
|
|
78
|
+
expect(generator.next(mockTask).value).toEqual(take(LOCATION_CHANGE));
|
|
79
|
+
expect(generator.next().value).toEqual(cancel(mockTask));
|
|
80
|
+
expect(generator.next().done).toEqual(true);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -20,17 +20,18 @@ import * as globalActions from '../Cap/actions';
|
|
|
20
20
|
import CapTagList from '../../v2Components/CapTagList';
|
|
21
21
|
import './_tagList.scss';
|
|
22
22
|
import { selectCurrentOrgDetails } from '../Cap/selectors';
|
|
23
|
-
const TreeNode = Tree.TreeNode;
|
|
24
23
|
import { injectIntl } from 'react-intl';
|
|
25
24
|
import { scope } from './messages';
|
|
26
25
|
import { handleInjectedData } from '../../utils/common';
|
|
27
26
|
|
|
27
|
+
const TreeNode = Tree.TreeNode;
|
|
28
|
+
|
|
28
29
|
export class TagList extends React.Component { // eslint-disable-line react/prefer-stateless-function
|
|
29
30
|
constructor(props) {
|
|
30
31
|
super(props);
|
|
31
32
|
this.state = {
|
|
32
33
|
loading: false,
|
|
33
|
-
tags: []
|
|
34
|
+
tags: [],
|
|
34
35
|
};
|
|
35
36
|
this.renderTags = this.renderTags.bind(this);
|
|
36
37
|
this.populateTags = this.populateTags.bind(this);
|
|
@@ -40,35 +41,7 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
|
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
componentDidMount() {
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
componentDidUpdate(prevProps) {
|
|
47
|
-
if (this.props.tags !== prevProps.tags || this.props.injectedTags !== prevProps.injectedTags || this.props.selectedOfferDetails !== prevProps.selectedOfferDetails) {
|
|
48
|
-
this.setState({
|
|
49
|
-
tags: this.generateTags(this.props),
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
generateTags(props) {
|
|
55
|
-
let tags = {};
|
|
56
|
-
let injectedTags = {};
|
|
57
|
-
if (props.injectedTags && !_.isEmpty(props.injectedTags)) {
|
|
58
|
-
const formattedInjectedTags = handleInjectedData(
|
|
59
|
-
props.injectedTags,
|
|
60
|
-
scope
|
|
61
|
-
);
|
|
62
|
-
injectedTags = this.transformInjectedTags(formattedInjectedTags);
|
|
63
|
-
}
|
|
64
|
-
if (props.tags && props.tags.length > 0) {
|
|
65
|
-
tags = this.populateTags(props.tags);
|
|
66
|
-
console.log('populating tags', Object.keys(tags || {}).length);
|
|
67
|
-
}
|
|
68
|
-
if (props.selectedOfferDetails && !_.isEmpty(props.selectedOfferDetails) && (tags && tags.coupon)) {
|
|
69
|
-
this.transformCouponTags(props.selectedOfferDetails, tags);
|
|
70
|
-
}
|
|
71
|
-
return _.merge( {}, tags, injectedTags);
|
|
44
|
+
this.generateTags(this.props);
|
|
72
45
|
}
|
|
73
46
|
|
|
74
47
|
componentWillReceiveProps(nextProps) {
|
|
@@ -76,33 +49,52 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
|
|
|
76
49
|
this.setState({loading: true});
|
|
77
50
|
}
|
|
78
51
|
if (!_.isEqual(nextProps.injectedTags, this.props.injectedTags)) {
|
|
79
|
-
|
|
52
|
+
|
|
80
53
|
this.setState({loading: false});
|
|
81
54
|
}
|
|
82
55
|
if (!_.isEqual(nextProps.tags, this.props.tags)) {
|
|
83
|
-
|
|
56
|
+
|
|
84
57
|
this.setState({loading: false});
|
|
85
58
|
}
|
|
86
59
|
|
|
87
60
|
}
|
|
88
|
-
|
|
61
|
+
componentDidUpdate(prevProps) {
|
|
62
|
+
if (this.props.tags !== prevProps.tags || this.props.injectedTags !== prevProps.injectedTags || this.props.selectedOfferDetails !== prevProps.selectedOfferDetails) {
|
|
63
|
+
this.generateTags(this.props);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
89
66
|
onSelect = (selectedKeys) => {
|
|
90
67
|
this.props.onTagSelect(selectedKeys[0]);
|
|
91
68
|
};
|
|
92
69
|
|
|
93
70
|
onClick = (e, data) => {
|
|
94
|
-
|
|
71
|
+
|
|
95
72
|
};
|
|
96
73
|
|
|
97
74
|
getTagsforContext = (data) => {
|
|
98
75
|
//this.setState({loading: true});
|
|
99
76
|
this.props.onContextChange(data);
|
|
100
77
|
}
|
|
101
|
-
|
|
78
|
+
generateTags = (props) => {
|
|
79
|
+
let tags = {};
|
|
80
|
+
let injectedTags = {};
|
|
81
|
+
if (props.injectedTags && !_.isEmpty(props.injectedTags)) {
|
|
82
|
+
const formattedInjectedTags = handleInjectedData(
|
|
83
|
+
props.injectedTags,
|
|
84
|
+
scope
|
|
85
|
+
);
|
|
86
|
+
injectedTags = this.transformInjectedTags(formattedInjectedTags);
|
|
87
|
+
}
|
|
88
|
+
if (props.tags && props.tags.length > 0) {
|
|
89
|
+
tags = this.populateTags(props.tags);
|
|
90
|
+
}
|
|
91
|
+
if (props.selectedOfferDetails && !_.isEmpty(props.selectedOfferDetails) && (tags && tags.coupon)) {
|
|
92
|
+
this.transformCouponTags(props.selectedOfferDetails, tags);
|
|
93
|
+
}
|
|
94
|
+
this.setState({tags: _.merge( {}, tags, injectedTags)});
|
|
95
|
+
}
|
|
102
96
|
populateTags(tagsList) {
|
|
103
|
-
console.log('populating tags', (tagsList || []).length);
|
|
104
97
|
const mainTags = {};
|
|
105
|
-
|
|
106
98
|
//Form tags object with tag headers
|
|
107
99
|
_.forEach(tagsList, (temp) => {
|
|
108
100
|
const tag = temp.definition;
|
|
@@ -115,7 +107,6 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
|
|
|
115
107
|
} else if (tag['tag-header'] && mainTags[tag.value]) {
|
|
116
108
|
mainTags[tag.value].subtags = _.concat(mainTags[tag.value].subtags, tag.subtags);
|
|
117
109
|
} else if (!mainTags[tag.value]) {
|
|
118
|
-
//
|
|
119
110
|
mainTags[tag.value] = {
|
|
120
111
|
'tag-header': true,
|
|
121
112
|
"name": tag?.label[userLocale] ? tag?.label[userLocale] : tag?.label?.en,
|