1a39bc46956586b4efa8284914079369b75a2189
[aai/sparky-fe.git] / src / app / MainScreenHeader.jsx
1 /*
2  * ============LICENSE_START=======================================================
3  * org.onap.aai
4  * ================================================================================
5  * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
6  * Copyright © 2017-2018 Amdocs
7  * ================================================================================
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  *       http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  * ============LICENSE_END=========================================================
20  */
21 import React, {Component} from 'react';
22 import { PropTypes } from 'prop-types';
23 import {connect} from 'react-redux';
24 import FontAwesome from 'react-fontawesome';
25 import {clearFilters} from 'filter-bar-utils';
26 import Button from 'react-bootstrap/lib/Button.js';
27 import Modal from 'react-bootstrap/lib/Modal.js';
28 import GlobalAutoCompleteSearchBar from 'app/globalAutoCompleteSearchBar/GlobalAutoCompleteSearchBar.jsx';
29 import {postAnalyticsData} from 'app/analytics/AnalyticsActions.js';
30 import GlobalInlineMessageBar from 'app/globalInlineMessageBar/GlobalInlineMessageBar.jsx';
31 import {getClearGlobalMessageEvent} from 'app/globalInlineMessageBar/GlobalInlineMessageBarActions.js';
32 import {externalUrlRequest, externalMessageRequest, getSubscriptionPayload} from 'app/contextHandler/ContextHandlerActions.js';
33 import { getConfigurableViewConfigs } from 'app/configurableViews/ConfigurableViewActions.js';
34 import {
35   filterBarActionTypes
36 } from 'utils/GlobalConstants.js';
37
38 import {
39   Route,
40   NavLink
41 } from 'react-router-dom';
42
43 import {
44   AAI_TITLE,
45   MENU_ITEM_TIER_SUPPORT,
46   MENU_ITEM_VNF_SEARCH
47 } from './MainScreenWrapperConstants.js';
48
49 import {
50   showMainMenu,
51   clearExtensibleViewData,
52   setSecondaryTitle
53 } from './MainScreenWrapperActionHelper.js';
54
55 import {clearSuggestionsTextField} from 'app/globalAutoCompleteSearchBar/GlobalAutoCompleteSearchBarActions.js';
56 import {changeUrlAddress} from 'utils/Routes.js';
57 import extensibleViews from 'resources/views/extensibleViews.json';
58
59
60 const mapStateToProps = ({mainWrapper, configurableViews}) => {
61   let {
62     showMenu = false,
63     toggleButtonActive = false,
64     externalRequestFound = {},
65     secondaryTitle = '',
66     subscriptionPayload = {},
67     subscriptionEnabled = false
68   } = mainWrapper;
69
70   let {
71     configurableViewsConfig
72   } = configurableViews;
73
74   return {
75     showMenu,
76     toggleButtonActive,
77     externalRequestFound,
78     secondaryTitle,
79     subscriptionPayload,
80     subscriptionEnabled,
81     configurableViewsConfig
82   };
83 };
84
85
86 const mapActionsToProps = (dispatch) => {
87   return {
88     onShowMenu: () => dispatch(showMainMenu(true)),
89     onHideMenu: () => {
90       dispatch(showMainMenu(false));
91     },
92     dispatchAnalyticsData: () => dispatch(
93       postAnalyticsData(document.documentElement.outerHTML.replace('\s+', ''))),
94     onRouteChange: () => {
95       dispatch(getClearGlobalMessageEvent());
96       dispatch(clearSuggestionsTextField());
97       dispatch(clearExtensibleViewData());
98       dispatch(clearFilters(filterBarActionTypes.CLEAR_FILTERS));
99       dispatch(setSecondaryTitle(undefined));
100     },
101     onExternalUrlRequest: (urlParamString) => {
102       dispatch(externalUrlRequest(urlParamString));
103     },
104     onExternalMessageRecieved: (messageJson) => {
105       dispatch(externalMessageRequest(messageJson));
106     },
107     onGetSubscriptionPayload: () => {
108       dispatch(getSubscriptionPayload());
109     },
110     onFetchCustomViews: () => {
111       dispatch(getConfigurableViewConfigs());
112     }
113   };
114 };
115
116 class MainScreenHeader extends Component {
117   static propTypes = {
118     showMenu: PropTypes.bool,
119     toggleButtonActive: PropTypes.bool,
120     externalRequestFound: PropTypes.object,
121     secondaryTitle: PropTypes.string,
122     subscriptionPayload: PropTypes.object
123   };
124
125   navigationLinkAndCurrentPathMatch(location, to) {
126     let linkPathElements = to.split('/');
127     let locationElements = location.pathname.split('/');
128
129     // the element arrays above will have the route at index 1 ... need to
130     // verify if the routes match
131     return locationElements[1] === linkPathElements[1];
132   }
133
134   hasRouteChanged(currentPath, nextPath) {
135     let currentPathParts = currentPath.split('/');
136     let nextPathParts = nextPath.split('/');
137
138     if (currentPathParts[1] !== nextPathParts[1]) {
139       return true;
140     } else {
141       return false;
142     }
143   }
144
145   isValidExternalURL(url) {
146     if(decodeURIComponent(url).indexOf('&') > 0 ) {
147       return true;
148     } else {
149       return false;
150     }
151   }
152
153   componentWillMount() {
154     this.props.onGetSubscriptionPayload();
155     if(this.props.match.params.externalUrl !== undefined &&
156       this.isValidExternalURL(this.props.match.params.externalUrl)) {
157       this.props.onExternalUrlRequest(this.props.match.params.externalUrl);
158     }
159   }
160
161   componentWillReceiveProps(nextProps) {
162     if (this.props.location &&
163       this.props.location.pathname !==
164       nextProps.location.pathname) {
165       // update analytics
166       this.props.dispatchAnalyticsData();
167
168       if (this.hasRouteChanged(this.props.location.pathname,
169           nextProps.location.pathname)) {
170         this.props.onRouteChange();
171       }
172     }
173
174     if(nextProps.match.params.externalUrl !== undefined &&
175       nextProps.match.params.externalUrl !== this.props.match.params.externalUrl &&
176       this.isValidExternalURL(nextProps.match.params.externalUrl)) {
177       this.props.onExternalUrlRequest(nextProps.match.params.externalUrl);
178     }
179     /* if the externalURL is not valid, we do not add any message as other proper
180      views will get that messages since the route will be this parameter.*/
181
182     if(this.props.externalRequestFound !== nextProps.externalRequestFound &&
183       nextProps.externalRequestFound !== undefined && nextProps.externalRequestFound.suggestion !== undefined) {
184       changeUrlAddress(nextProps.externalRequestFound.suggestion, nextProps.history);
185     }
186
187     if (nextProps.subscriptionEnabled) {
188       if (nextProps.subscriptionPayload !== this.props.subscriptionPayload &&
189         Object.keys(nextProps.subscriptionPayload).length > 0) {
190         var getWindowUrl = function (url) {
191           var split = url.split('/');
192           return split[0] + '//' + split[2];
193         };
194         window.parent.postMessage(
195           JSON.stringify(nextProps.subscriptionPayload),
196           getWindowUrl(document.referrer));
197       }
198     }
199   }
200
201   receiveMessage(event, $this) {
202     function isJson(str) {
203       try {
204         JSON.parse(str);
205       } catch (e) {
206         return false;
207       }
208       return true;
209     }
210     if(isJson(event.data)) {
211       let messageData = JSON.parse(event.data);
212       if(isJson(messageData.message)) {
213         $this.props.onExternalMessageRecieved(messageData.message);
214       }
215     }
216
217   }
218   componentDidMount() {
219     //TODO Move this logic to the component will receive props.
220     //Check if the event lister is available and if the subscription is
221     // enabled before registering for it
222     if(document.referrer) {
223       var $this = this;
224       window.addEventListener('message', function (e) {
225         $this.receiveMessage(e, $this);
226       }, false);
227     }
228
229     // fetch custom views
230     this.props.onFetchCustomViews();
231   }
232
233   componentWillUnmount() {
234     if(this.props.subscriptionEnabled) {
235       var $this = this;
236       window.removeEventListener('message', function (e) {
237         $this.receiveMessage(e, $this);
238       });
239     }
240   }
241
242   render() {
243     let {
244       showMenu,
245       onShowMenu,
246       onHideMenu,
247       toggleButtonActive,
248       secondaryTitle,
249       configurableViewsConfig
250     } = this.props;
251
252     let menuOptions = [];
253
254     const MenuItem = ({label, iconClass, to}) => (
255       <Route path={to} children={({location}) => (
256         <NavLink to={to} onClick={onHideMenu}>
257           <div className={this.navigationLinkAndCurrentPathMatch(location, to) ? 'main-menu-button-active' : 'main-menu-button'}>
258             <div className={iconClass}/>
259             <div className='button-icon'>{label}</div>
260           </div>
261         </NavLink>
262       )}/>
263     );
264
265     const ConfigurableMenuItem = ({label, to}) => (
266       <Route path={to} children={({location}) => (
267         <NavLink to={to} onClick={onHideMenu}>
268           <div className={this.navigationLinkAndCurrentPathMatch(location, to) ?
269             'main-menu-button-active' : 'main-menu-button'}>
270             <div className='button-icon configurable-view-button-icon'/>
271             <div className='button-icon'>{label}</div>
272           </div>
273         </NavLink>
274       )}/>
275     );
276
277     // add Tier Support view
278     menuOptions.push(
279       <MenuItem key='schemaMenu' to='/schema' label={MENU_ITEM_TIER_SUPPORT}
280                 iconClass='button-icon view-inspect-button-icon'/>
281     );
282
283     // add VNF view
284     menuOptions.push(
285       <MenuItem key='vnfSearchMenu'
286                 to='/vnfSearch'
287                 label={MENU_ITEM_VNF_SEARCH}
288                 iconClass='button-icon vnf-search-button-icon'/>
289     );
290
291     // add all custom view menu options
292     for (let view in extensibleViews) {
293       let shouldDisplayIcon = false;
294       if(extensibleViews[view]['onlyRoute'] === undefined){
295         shouldDisplayIcon = true;
296       } else if(extensibleViews[view]['onlyRoute'] === false){
297         shouldDisplayIcon = true;
298       }
299       if(shouldDisplayIcon === true){
300         menuOptions.push(
301           <MenuItem key={extensibleViews[view]['viewName'] + 'Menu'} to={'/' + extensibleViews[view]['viewName']}
302                     label={extensibleViews[view]['displayName']}
303                     iconClass={'button-icon ' + extensibleViews[view]['iconClass']}/>
304         );
305       }
306     }
307
308     if (configurableViewsConfig && configurableViewsConfig.layouts) {
309       for (let configurableView in configurableViewsConfig.layouts) {
310         menuOptions.push(
311           <ConfigurableMenuItem key={configurableViewsConfig.layouts[configurableView]['id'] + 'Menu'} to={'/' + configurableViewsConfig.layouts[configurableView]['id']}
312                                 label={configurableViewsConfig.layouts[configurableView]['title']}/>
313         );
314       }
315     }
316
317     let secondaryTitleClass = 'secondary-header';
318     if (secondaryTitle === undefined || secondaryTitle === '') {
319       secondaryTitleClass = secondaryTitleClass + ' hidden';
320     }
321
322     return (
323       <div className='header'>
324         <div>
325           <Button
326             bsClass={(toggleButtonActive)
327               ? 'toggle-view-button-active'
328               : 'toggle-view-button'}
329             onClick={onShowMenu}>
330             <FontAwesome name='bars'/>
331           </Button>
332           <Modal show={showMenu} onHide={onHideMenu}
333                  dialogClassName='modal-main-menu'>
334             <Modal.Body>
335               {menuOptions}
336             </Modal.Body>
337           </Modal>
338           <span className='application-title'>{AAI_TITLE}</span>
339           <GlobalAutoCompleteSearchBar history={this.props.history}/>
340         </div>
341         <GlobalInlineMessageBar />
342         <div className={secondaryTitleClass}>
343           <span className='secondary-title'>{secondaryTitle}</span>
344         </div>
345       </div>
346     );
347   }
348 }
349
350 export default connect(mapStateToProps, mapActionsToProps)(MainScreenHeader);