@capillarytech/creatives-library 7.17.21-alpha.0 → 7.17.21-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/containers/App/constants.js +23 -0
- package/package.json +1 -1
- package/utils/common.js +18 -4
- package/utils/tests/common.mockdata.js +125 -0
- package/utils/tests/common.test.js +98 -0
- package/utils/tests/commonUtil.test.js +21 -0
- package/v2Components/CapTagList/index.js +11 -6
- 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 +31 -19
|
@@ -5,6 +5,7 @@ export const GET_SIDEBAR_FAILURE = 'app/App/GET_SIDEBAR_FAILURE';
|
|
|
5
5
|
export const STORE2DOOR_PLUS_ENABLED = 'STORE2DOOR_PLUS_ENABLED';
|
|
6
6
|
export const TRAI_DLT = 'TRAI_DLT';
|
|
7
7
|
export const CARD_BASED_SCOPE = 'CARD_BASED_SCOPE';
|
|
8
|
+
export const HOSPITALITY_BASED_SCOPE = 'HOSPITALITY_BASED_SCOPE';
|
|
8
9
|
|
|
9
10
|
export const CARD_RELATED_TAGS = [
|
|
10
11
|
'card_series',
|
|
@@ -14,3 +15,25 @@ export const CARD_RELATED_TAGS = [
|
|
|
14
15
|
'card_series_name',
|
|
15
16
|
'card_name',
|
|
16
17
|
];
|
|
18
|
+
|
|
19
|
+
export const HOSPITALITY_RELATED_TAGS = [
|
|
20
|
+
"hospitality_series",
|
|
21
|
+
"tax_Code",
|
|
22
|
+
"roomTypeCode",
|
|
23
|
+
"bookingChannel",
|
|
24
|
+
"hotelReservationID_ResID_Type",
|
|
25
|
+
"lastModifyDateTime",
|
|
26
|
+
"guestCounts_IsPerRoom",
|
|
27
|
+
"ratePlan_RatePlanCode",
|
|
28
|
+
"resID_Value",
|
|
29
|
+
"numberOfUnits",
|
|
30
|
+
"rateTimeUnit",
|
|
31
|
+
"resStatus",
|
|
32
|
+
"timeSpan_End",
|
|
33
|
+
"roomStay_MarketCode",
|
|
34
|
+
"createDateTime",
|
|
35
|
+
"amount",
|
|
36
|
+
"timeSpan_Start",
|
|
37
|
+
"email",
|
|
38
|
+
"resID_Source",
|
|
39
|
+
];
|
package/package.json
CHANGED
package/utils/common.js
CHANGED
|
@@ -5,6 +5,8 @@ import {
|
|
|
5
5
|
TRAI_DLT,
|
|
6
6
|
CARD_RELATED_TAGS,
|
|
7
7
|
CARD_BASED_SCOPE,
|
|
8
|
+
HOSPITALITY_RELATED_TAGS,
|
|
9
|
+
HOSPITALITY_BASED_SCOPE,
|
|
8
10
|
} from '../containers/App/constants';
|
|
9
11
|
import { apiMessageFormatHandler } from './commonUtils';
|
|
10
12
|
|
|
@@ -54,16 +56,28 @@ export const hasCardBasedScope = Auth.hasFeatureAccess.bind(
|
|
|
54
56
|
CARD_BASED_SCOPE,
|
|
55
57
|
);
|
|
56
58
|
|
|
59
|
+
export const hasHospitalityBasedScope = Auth.hasFeatureAccess.bind(
|
|
60
|
+
null,
|
|
61
|
+
HOSPITALITY_BASED_SCOPE,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
//filtering tags based on scope
|
|
65
|
+
export const filterTags = (tagsToFilter, tagsList) => tagsList?.filter(
|
|
66
|
+
(tag) => !tagsToFilter?.includes(tag?.definition?.value)
|
|
67
|
+
);
|
|
68
|
+
|
|
57
69
|
export function getTreeStructuredTags({tagsList, userLocale = 'en', offerDetails = [], disableTagsDetails = {}}) {
|
|
58
|
-
console.log('populating tags', (tagsList || []).length);
|
|
59
70
|
const mainTags = {};
|
|
60
71
|
const { disableRelatedTags, childTagsToDisable, parentTagstoDisable} = disableTagsDetails;
|
|
61
72
|
|
|
62
73
|
let clonedTags = tagsList;
|
|
63
74
|
if (!hasCardBasedScope()) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
75
|
+
//filtering CARD_RELATED_TAGS if org does not have CARD_BASED_SCOPE feature enabled
|
|
76
|
+
clonedTags = filterTags(CARD_RELATED_TAGS, tagsList);
|
|
77
|
+
}
|
|
78
|
+
if (!hasHospitalityBasedScope()) {
|
|
79
|
+
//filtering HOSPITALITY_RELATED_TAGS if org does not have HOSPITALITY_BASED_SCOPE feature enabled
|
|
80
|
+
clonedTags = filterTags(HOSPITALITY_RELATED_TAGS, tagsList);
|
|
67
81
|
}
|
|
68
82
|
_.forEach(clonedTags, (temp) => {
|
|
69
83
|
const tag = temp.definition;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
export const tagsList = [
|
|
2
|
+
{
|
|
3
|
+
_id: "6501b8b2ddd26e9da234b7c0",
|
|
4
|
+
type: "TAG",
|
|
5
|
+
definition: {
|
|
6
|
+
"resolutionDetails": {
|
|
7
|
+
resolver: "EI",
|
|
8
|
+
reference:
|
|
9
|
+
"select check_in_date.date as timeSpan_Start from read_api_2000014.behavioural_catalog_reservation a join read_api_2000014.date check_in_date on check_in_date.date_id=a.dim_timespan_start_id join read_api_2000014.date modified_date on modified_date.date_id=a.dim_latest_updated_date_id join read_api_2000014.date lastmodifydate on lastmodifydate.date_id=a.dim_lastmodifydatetime_id join read_api_2000014.date d ON a.dim_event_date_id = d.date_id join read_api_2000014.date booking_date ON a.dim_createdatetime_id = booking_date.date_id join read_api_2000014.date check_out_date ON a.dim_timespan_end_id = check_out_date.date_id join (select resid_value, max(date) as max_updated_date from read_api_2000014.behavioural_catalog_reservation a join read_api_2000014.date d on d.date_id=a.dim_latest_updated_date_id group by 1 ) b on a.resid_value=b.resid_value and b.max_updated_date=modified_date.date where resstatus='Reserved' and datediff(check_in_date.date, date(now()) )= 3",
|
|
10
|
+
type: "QUERY",
|
|
11
|
+
},
|
|
12
|
+
"subType": "EXTERNAL",
|
|
13
|
+
"label": {
|
|
14
|
+
"zh-cn": "",
|
|
15
|
+
"en": "Checkin date",
|
|
16
|
+
},
|
|
17
|
+
"value": "timeSpan_Start",
|
|
18
|
+
"subtags": [],
|
|
19
|
+
"tag-header": false,
|
|
20
|
+
"supportedModules": [
|
|
21
|
+
{
|
|
22
|
+
mandatory: false,
|
|
23
|
+
layout: "sms",
|
|
24
|
+
context: "default",
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
mandatory: false,
|
|
28
|
+
layout: "sms",
|
|
29
|
+
context: "outbound",
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
mandatory: false,
|
|
33
|
+
layout: "email",
|
|
34
|
+
context: "default",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
mandatory: false,
|
|
38
|
+
layout: "email",
|
|
39
|
+
context: "outbound",
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
scope: {
|
|
44
|
+
tag: "STANDARD",
|
|
45
|
+
orgId: -1,
|
|
46
|
+
verticals: [],
|
|
47
|
+
},
|
|
48
|
+
isActive: true,
|
|
49
|
+
createdBy: 50685535,
|
|
50
|
+
updatedBy: 50685535,
|
|
51
|
+
createdAt: "2023-09-13T13:27:14.653Z",
|
|
52
|
+
updatedAt: "2023-09-13T13:27:14.653Z",
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
_id: "5a291cdd85c3db29a2041e5a",
|
|
56
|
+
type: "TAG",
|
|
57
|
+
definition: {
|
|
58
|
+
"label": {
|
|
59
|
+
"en": "Optout",
|
|
60
|
+
"ja-JP": "撤回",
|
|
61
|
+
},
|
|
62
|
+
"value": "optout",
|
|
63
|
+
"subtags": [],
|
|
64
|
+
"tag-header": false,
|
|
65
|
+
"supportedModules": [
|
|
66
|
+
{
|
|
67
|
+
context: "journey",
|
|
68
|
+
layout: "sms",
|
|
69
|
+
mandatory: false,
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
context: "outbound",
|
|
73
|
+
layout: "sms",
|
|
74
|
+
mandatory: false,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
context: "default",
|
|
78
|
+
layout: "sms",
|
|
79
|
+
mandatory: false,
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
context: "coupon_expiry",
|
|
83
|
+
layout: "sms",
|
|
84
|
+
mandatory: false,
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
context: "coupons",
|
|
88
|
+
layout: "sms",
|
|
89
|
+
mandatory: false,
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
scope: {
|
|
94
|
+
tag: "STANDARD",
|
|
95
|
+
orgId: -1,
|
|
96
|
+
verticals: [],
|
|
97
|
+
},
|
|
98
|
+
isActive: true,
|
|
99
|
+
createdBy: 4,
|
|
100
|
+
updatedBy: 15000449,
|
|
101
|
+
createdAt: "2017-12-07T10:50:05.800Z",
|
|
102
|
+
updatedAt: "2018-01-22T11:43:05.611Z",
|
|
103
|
+
},
|
|
104
|
+
];
|
|
105
|
+
export const tagsToFilter = ["timeSpan_Start"];
|
|
106
|
+
|
|
107
|
+
export const output1 = [
|
|
108
|
+
{
|
|
109
|
+
disabled: false,
|
|
110
|
+
title: "Optout",
|
|
111
|
+
value: "{{optout}}",
|
|
112
|
+
},
|
|
113
|
+
];
|
|
114
|
+
export const output2 = [
|
|
115
|
+
{
|
|
116
|
+
disabled: false,
|
|
117
|
+
title: "Checkin date",
|
|
118
|
+
value: "{{timeSpan_Start}}",
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
disabled: false,
|
|
122
|
+
title: "Optout",
|
|
123
|
+
value: "{{optout}}",
|
|
124
|
+
},
|
|
125
|
+
];
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { FormattedMessage } from 'react-intl';
|
|
3
|
+
import "@testing-library/jest-dom";
|
|
4
|
+
import { filterTags, getTreeStructuredTags, handleInjectedData } from "../common";
|
|
5
|
+
import * as mockdata from "./common.mockdata";
|
|
6
|
+
|
|
7
|
+
describe("getTreeStructuredTags test", () => {
|
|
8
|
+
it("test for getTreeStructuredTags when tagsList is not empty", () => {
|
|
9
|
+
expect(getTreeStructuredTags({tagsList: mockdata.tagsList})).toEqual(mockdata.output1);
|
|
10
|
+
});
|
|
11
|
+
it("test for getTreeStructuredTags when tagsList is empty", () => {
|
|
12
|
+
expect(getTreeStructuredTags({tagsList: []})).toEqual([]);
|
|
13
|
+
});
|
|
14
|
+
it("test for filterTags", () => {
|
|
15
|
+
expect(filterTags(mockdata.tagsToFilter, mockdata.tagsList)).toEqual([mockdata.tagsList[1]]);
|
|
16
|
+
});
|
|
17
|
+
it("test for filterTags when tagsToFilter and tagsList is empty", () => {
|
|
18
|
+
expect(filterTags([], [])).toEqual([]);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe("handleInjectedData", () => {
|
|
23
|
+
it("replaces name with intl key for top level tag", () => {
|
|
24
|
+
const data = {
|
|
25
|
+
tag1: {
|
|
26
|
+
name: "Registration Fields",
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const result = handleInjectedData(data, "scope");
|
|
31
|
+
|
|
32
|
+
expect(result.tag1.name).toEqual(<FormattedMessage defaultMessage="Registration Fields" id="scope.RegistrationFields" values={{}} />);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("adds tagType for Registration custom fields", () => {
|
|
36
|
+
const data = {
|
|
37
|
+
tag1: {
|
|
38
|
+
name: "Registration custom fields",
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const result = handleInjectedData(data, "scope");
|
|
43
|
+
|
|
44
|
+
expect(result.tag1.name).toEqual(
|
|
45
|
+
<FormattedMessage defaultMessage="Registration custom fields" id="scope.Registrationcustomfields_name.CustomTagMessage" values={{}} />
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("adds tagType for Customer extended fields fields", () => {
|
|
50
|
+
const data = {
|
|
51
|
+
tag1: {
|
|
52
|
+
name: "Customer extended fields",
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const result = handleInjectedData(data, "scope");
|
|
57
|
+
|
|
58
|
+
expect(result.tag1.name).toEqual(
|
|
59
|
+
<FormattedMessage defaultMessage="Customer extended fields" id="scope.Customerextendedfields_name.ExtendedTagMessage" values={{}} />
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
it("replaces subtag name with intl key", () => {
|
|
65
|
+
const data = {
|
|
66
|
+
tag1: {
|
|
67
|
+
subtags: {
|
|
68
|
+
subtag1: {
|
|
69
|
+
name: "First Name",
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const result = handleInjectedData(data, "scope");
|
|
76
|
+
|
|
77
|
+
expect(result.tag1.subtags.subtag1.name).toEqual(<FormattedMessage defaultMessage="First Name" id="scope.FirstName" values={{}} />);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("replaces subtag desc with intl key", () => {
|
|
81
|
+
const data = {
|
|
82
|
+
tag1: {
|
|
83
|
+
subtags: {
|
|
84
|
+
subtag1: {
|
|
85
|
+
desc: "Enter your first name",
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const result = handleInjectedData(data, "scope");
|
|
92
|
+
|
|
93
|
+
expect(result.tag1.subtags.subtag1.desc).toEqual(
|
|
94
|
+
<FormattedMessage defaultMessage="Enter your first name" id="scope.Enteryourfirstname" values={{}} />
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import "@testing-library/jest-dom";
|
|
2
|
+
import { getTreeStructuredTags } from "../common";
|
|
3
|
+
import * as mockdata from "./common.mockdata";
|
|
4
|
+
|
|
5
|
+
jest.mock('@capillarytech/cap-ui-utils', () => ({
|
|
6
|
+
Auth: {
|
|
7
|
+
hasAccess: () => jest.fn(() => true),
|
|
8
|
+
hasFeatureAccess: () => jest.fn(() => true),
|
|
9
|
+
authHoc: jest.fn(),
|
|
10
|
+
initialize: jest.fn(),
|
|
11
|
+
},
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
describe("common utils test", () => {
|
|
15
|
+
it("test for getTreeStructuredTags when tagsList is not empty", () => {
|
|
16
|
+
expect(getTreeStructuredTags({tagsList: mockdata.tagsList})).toEqual(mockdata.output2);
|
|
17
|
+
});
|
|
18
|
+
it("test for getTreeStructuredTags when tagsList is empty", () => {
|
|
19
|
+
expect(getTreeStructuredTags({tagsList: []})).toEqual([]);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
import PropTypes from 'prop-types';
|
|
8
8
|
import React from 'react';
|
|
9
9
|
import { connect } from 'react-redux';
|
|
10
|
+
import moment from 'moment';
|
|
11
|
+
import { FONT_COLOR_05 } from '@capillarytech/cap-ui-library/styled/variables';
|
|
10
12
|
// import styled from 'styled-components';
|
|
11
13
|
import { CapSpin, CapModal, CapButton, CapPopover, CapInput, CapTree, CapSelect, CapTooltip, CapLink, CapIcon } from '@capillarytech/cap-ui-library';
|
|
12
14
|
|
|
@@ -15,10 +17,9 @@ import { FormattedMessage, injectIntl, intlShape } from 'react-intl';
|
|
|
15
17
|
import { createStructuredSelector } from 'reselect';
|
|
16
18
|
import messages from './messages';
|
|
17
19
|
import { makeSelectLoyaltyPromotionDisplay } from '../../v2Containers/Cap/selectors';
|
|
18
|
-
import { CARD_RELATED_TAGS } from '../../containers/App/constants';
|
|
19
|
-
import { hasCardBasedScope } from '../../utils/common';
|
|
20
|
-
|
|
21
|
-
import { FONT_COLOR_05 } from '@capillarytech/cap-ui-library/styled/variables';
|
|
20
|
+
import { CARD_RELATED_TAGS, HOSPITALITY_RELATED_TAGS } from '../../containers/App/constants';
|
|
21
|
+
import { hasCardBasedScope, hasHospitalityBasedScope } from '../../utils/common';
|
|
22
|
+
|
|
22
23
|
import {
|
|
23
24
|
GET_TRANSLATION_MAPPED,
|
|
24
25
|
JAPANESE_HELP_TEXT,
|
|
@@ -93,12 +94,12 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
|
|
|
93
94
|
let list = [];
|
|
94
95
|
_.forEach(tags, (val = {}, key) => {
|
|
95
96
|
if (_.has(val, 'subtags')) {
|
|
96
|
-
if ((val.name || '').toLowerCase().indexOf(value.toLowerCase()) !== -1) {
|
|
97
|
+
if (val.name && typeof val.name === 'string' && (val.name || '').toLowerCase().indexOf(value.toLowerCase()) !== -1) {
|
|
97
98
|
list.push(key);
|
|
98
99
|
}
|
|
99
100
|
const temp = this.getSearchedExpandedKeys(val.subtags, value);
|
|
100
101
|
list = list.concat(temp);
|
|
101
|
-
} else if ((val.name || '').toLowerCase().indexOf(value.toLowerCase()) !== -1) {
|
|
102
|
+
} else if (val.name && typeof val.name === 'string' && (val.name || '').toLowerCase().indexOf(value.toLowerCase()) !== -1) {
|
|
102
103
|
list.push(key);
|
|
103
104
|
}
|
|
104
105
|
});
|
|
@@ -181,6 +182,10 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
|
|
|
181
182
|
if (!hasCardBasedScope()) {
|
|
182
183
|
clonedTags = _.omit(clonedTags, CARD_RELATED_TAGS);
|
|
183
184
|
}
|
|
185
|
+
if (!hasHospitalityBasedScope()) {
|
|
186
|
+
//filtering HOSPITALITY_RELATED_TAGS if org does not have HOSPITALITY_BASED_SCOPE feature enabled
|
|
187
|
+
clonedTags = _.omit(clonedTags, HOSPITALITY_RELATED_TAGS);
|
|
188
|
+
}
|
|
184
189
|
_.forEach(clonedTags, (val = '', key) => {
|
|
185
190
|
let supportedTagsString = '';
|
|
186
191
|
_.forEach(val.supportedTags, (supportedTag) => {
|
|
@@ -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
|
+
});
|
|
@@ -30,6 +30,7 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
|
|
|
30
30
|
super(props);
|
|
31
31
|
this.state = {
|
|
32
32
|
loading: false,
|
|
33
|
+
tags: []
|
|
33
34
|
};
|
|
34
35
|
this.renderTags = this.renderTags.bind(this);
|
|
35
36
|
this.populateTags = this.populateTags.bind(this);
|
|
@@ -42,6 +43,34 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
|
|
|
42
43
|
|
|
43
44
|
}
|
|
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);
|
|
72
|
+
}
|
|
73
|
+
|
|
45
74
|
componentWillReceiveProps(nextProps) {
|
|
46
75
|
if (_.isEmpty(this.props.injectedTags) && _.isEmpty(this.props.tags)) {
|
|
47
76
|
this.setState({loading: true});
|
|
@@ -54,6 +83,7 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
|
|
|
54
83
|
|
|
55
84
|
this.setState({loading: false});
|
|
56
85
|
}
|
|
86
|
+
|
|
57
87
|
}
|
|
58
88
|
|
|
59
89
|
onSelect = (selectedKeys) => {
|
|
@@ -225,29 +255,11 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
|
|
|
225
255
|
}
|
|
226
256
|
|
|
227
257
|
render() {
|
|
228
|
-
let tags = {};
|
|
229
|
-
let injectedTags = {};
|
|
230
|
-
if (this.props.injectedTags && !_.isEmpty(this.props.injectedTags)) {
|
|
231
|
-
const formattedInjectedTags = handleInjectedData(
|
|
232
|
-
this.props.injectedTags,
|
|
233
|
-
scope
|
|
234
|
-
);
|
|
235
|
-
injectedTags = this.transformInjectedTags(formattedInjectedTags);
|
|
236
|
-
}
|
|
237
|
-
if (this.props.tags && this.props.tags.length > 0) {
|
|
238
|
-
tags = this.populateTags(this.props.tags);
|
|
239
|
-
console.log('populating tags', Object.keys(tags || {}).length);
|
|
240
|
-
}
|
|
241
|
-
if (this.props.selectedOfferDetails && !_.isEmpty(this.props.selectedOfferDetails) && (tags && tags.coupon)) {
|
|
242
|
-
this.transformCouponTags(this.props.selectedOfferDetails, tags);
|
|
243
|
-
}
|
|
244
|
-
tags = _.merge( {}, tags, injectedTags);
|
|
245
|
-
console.log('merged tags', Object.keys(tags || {}).length);
|
|
246
258
|
return (
|
|
247
259
|
<div className={this.props.className ? this.props.className : ''}>
|
|
248
260
|
<CapTagList
|
|
249
261
|
loading={this.state.loading}
|
|
250
|
-
tags={tags}
|
|
262
|
+
tags={this.state.tags}
|
|
251
263
|
onSelect={this.onSelect}
|
|
252
264
|
label={this.props.label}
|
|
253
265
|
visibleTaglist={this.props.visibleTaglist}
|