@capillarytech/creatives-library 7.16.17 → 7.16.19
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/v2Components/NavigationBar/index.js +10 -5
- package/v2Components/NavigationBar/tests/index.test.js +51 -0
- package/v2Components/NavigationBar/tests/mockData.js +36 -0
- package/v2Components/TopBar/index.js +3 -0
- package/v2Containers/Cap/constants.js +2 -0
- 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/package.json
CHANGED
|
@@ -17,6 +17,7 @@ import { LOYALTY } from '../../v2Containers/App/constants';
|
|
|
17
17
|
import {
|
|
18
18
|
HELP_URL,
|
|
19
19
|
LOYALTY_HELP_URL,
|
|
20
|
+
ENABLE_AI_SUGGESTIONS,
|
|
20
21
|
} from '../../v2Containers/Cap/constants';
|
|
21
22
|
const PRODUCT_MASTERS = 'masters';
|
|
22
23
|
const EMBEDDED = 'embedded';
|
|
@@ -43,7 +44,7 @@ const ComponentWrapper = styled.div`
|
|
|
43
44
|
padding: 24px 0;
|
|
44
45
|
`;
|
|
45
46
|
|
|
46
|
-
class NavigationBar extends React.Component {
|
|
47
|
+
export class NavigationBar extends React.Component {
|
|
47
48
|
constructor(props) {
|
|
48
49
|
super(props);
|
|
49
50
|
this.state = this.initializeSelectedProduct();
|
|
@@ -153,9 +154,9 @@ class NavigationBar extends React.Component {
|
|
|
153
154
|
return productsList;
|
|
154
155
|
};
|
|
155
156
|
|
|
156
|
-
getTopbarIcons = () => {
|
|
157
|
+
getTopbarIcons = (showDocumentationBot = false) => {
|
|
157
158
|
const {settingsIcon} = this.state;
|
|
158
|
-
|
|
159
|
+
const ICONS = [
|
|
159
160
|
{
|
|
160
161
|
iconType: 'help',
|
|
161
162
|
key: 'help',
|
|
@@ -163,7 +164,8 @@ class NavigationBar extends React.Component {
|
|
|
163
164
|
},
|
|
164
165
|
settingsIcon,
|
|
165
166
|
];
|
|
166
|
-
|
|
167
|
+
return showDocumentationBot ? ICONS.slice(1) : ICONS; // If showDocumentationBot is true, help icon will be replaced by Aira icon on UI
|
|
168
|
+
};
|
|
167
169
|
|
|
168
170
|
getDropdownMenu = () => {
|
|
169
171
|
const { formatMessage } = this.props.intl;
|
|
@@ -207,12 +209,14 @@ class NavigationBar extends React.Component {
|
|
|
207
209
|
topbarMenuData,
|
|
208
210
|
loggedIn,
|
|
209
211
|
type,
|
|
212
|
+
userData,
|
|
210
213
|
} = this.props;
|
|
214
|
+
const showDocumentationBot = userData?.currentOrgDetails?.accessibleFeatures?.includes(ENABLE_AI_SUGGESTIONS);
|
|
211
215
|
const productsList = this.getProductsList();
|
|
212
216
|
const proxyOrgList = this.getProxyOrgList();
|
|
213
217
|
const selectedOrg = loadItem('orgID');
|
|
214
218
|
const dropdownMenuProps = this.getDropdownMenu();
|
|
215
|
-
const topbarIcons = this.getTopbarIcons();
|
|
219
|
+
const topbarIcons = this.getTopbarIcons(showDocumentationBot);
|
|
216
220
|
const { selectedProduct } = this.state;
|
|
217
221
|
const customTopBarProps = {};
|
|
218
222
|
return (
|
|
@@ -229,6 +233,7 @@ class NavigationBar extends React.Component {
|
|
|
229
233
|
handleTopbarMenuChange={this.handleTopbarMenuChange}
|
|
230
234
|
dropdownMenuProps={dropdownMenuProps}
|
|
231
235
|
topbarIcons={topbarIcons}
|
|
236
|
+
showDocumentationBot={showDocumentationBot}
|
|
232
237
|
{...customTopBarProps}
|
|
233
238
|
/>
|
|
234
239
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { injectIntl } from 'react-intl';
|
|
3
|
+
import '@testing-library/jest-dom';
|
|
4
|
+
import cloneDeep from 'lodash/cloneDeep';
|
|
5
|
+
import { BrowserRouter as Router } from 'react-router-dom';
|
|
6
|
+
import { render, screen } from '../../../utils/test-utils';
|
|
7
|
+
|
|
8
|
+
import { NavigationBar } from '../index';
|
|
9
|
+
import * as mockdata from './mockData';
|
|
10
|
+
|
|
11
|
+
const { userData } = mockdata;
|
|
12
|
+
|
|
13
|
+
const ComponentToRender = injectIntl(NavigationBar);
|
|
14
|
+
|
|
15
|
+
const renderComponent = (props) => {
|
|
16
|
+
render(
|
|
17
|
+
<Router>
|
|
18
|
+
<ComponentToRender {...props} />
|
|
19
|
+
</Router>,
|
|
20
|
+
);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
describe('NavigationBar', () => {
|
|
24
|
+
const props = {
|
|
25
|
+
topbarMenuData: [{}],
|
|
26
|
+
history: [{}],
|
|
27
|
+
userData,
|
|
28
|
+
loggedIn: true,
|
|
29
|
+
isCreativesAccessible: true,
|
|
30
|
+
campaignOrgV2Status: true,
|
|
31
|
+
location: {
|
|
32
|
+
pathname: "v2",
|
|
33
|
+
key: "g9d1jt",
|
|
34
|
+
basename: "/creatives/ui/",
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
it('Show aria documentation bot icon if showDocumentationBot is true', () => {
|
|
39
|
+
renderComponent(props);
|
|
40
|
+
const ariaBotIcon = screen.getByLabelText('aria bot icon');
|
|
41
|
+
expect(ariaBotIcon).toBeInTheDocument();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('Should not show aria documentation bot icon if showDocumentationBot is false', () => {
|
|
45
|
+
const updatedProps = cloneDeep(props);
|
|
46
|
+
delete updatedProps.userData.currentOrgDetails.accessibleFeatures;
|
|
47
|
+
renderComponent(updatedProps);
|
|
48
|
+
const ariaBotIcon = screen.queryByLabelText('aria bot icon');
|
|
49
|
+
expect(ariaBotIcon).toBeNull();
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export const userData = {
|
|
2
|
+
user: {
|
|
3
|
+
orgName: 'org1',
|
|
4
|
+
orgID: 1001,
|
|
5
|
+
proxyOrgList: [
|
|
6
|
+
{
|
|
7
|
+
orgName: 'org2',
|
|
8
|
+
orgID: 1002,
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
orgName: 'org3',
|
|
12
|
+
orgID: 1003,
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
},
|
|
16
|
+
currentOrgDetails: {
|
|
17
|
+
module_details: [
|
|
18
|
+
{
|
|
19
|
+
code: 'code1',
|
|
20
|
+
name: 'name1',
|
|
21
|
+
url: 'url1',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
code: 'code2',
|
|
25
|
+
name: 'name2',
|
|
26
|
+
url: 'url2',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
code: 'code3',
|
|
30
|
+
name: 'name3',
|
|
31
|
+
url: 'url3',
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
accessibleFeatures: ['ENABLE_AI_SUGGESTIONS'],
|
|
35
|
+
},
|
|
36
|
+
};
|
|
@@ -19,6 +19,7 @@ const TopBar = (props) => {
|
|
|
19
19
|
handleTopbarMenuChange,
|
|
20
20
|
dropdownMenuProps,
|
|
21
21
|
topbarIcons,
|
|
22
|
+
showDocumentationBot,
|
|
22
23
|
intl,
|
|
23
24
|
} = props;
|
|
24
25
|
return (
|
|
@@ -54,6 +55,7 @@ const TopBar = (props) => {
|
|
|
54
55
|
}}
|
|
55
56
|
dropdownMenuProps={dropdownMenuProps}
|
|
56
57
|
topbarIcons={topbarIcons}
|
|
58
|
+
showDocumentationBot={showDocumentationBot}
|
|
57
59
|
/>
|
|
58
60
|
);
|
|
59
61
|
};
|
|
@@ -69,6 +71,7 @@ TopBar.propTypes = {
|
|
|
69
71
|
handleTopbarMenuChange: PropTypes.func,
|
|
70
72
|
topbarIcons: PropTypes.array,
|
|
71
73
|
dropdownMenuProps: PropTypes.array,
|
|
74
|
+
showDocumentationBot: PropTypes.bool,
|
|
72
75
|
intl: intlShape.isRequired,
|
|
73
76
|
};
|
|
74
77
|
export default injectIntl(TopBar);
|
|
@@ -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
|
+
});
|