7808d19430e7f2fc838186463c5382308fe091b7
[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, getStoreAnalyticsPayload} 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_TOP_LEFT_HEADER,
45   AAI_HTML_TITLE,
46   MENU_ITEM_TIER_SUPPORT,
47   MENU_ITEM_VNF_SEARCH
48 } from './MainScreenWrapperConstants.js';
49
50 import {
51   showMainMenu,
52   clearExtensibleViewData,
53   setSecondaryTitle
54 } from './MainScreenWrapperActionHelper.js';
55
56 import {clearSuggestionsTextField} from 'app/globalAutoCompleteSearchBar/GlobalAutoCompleteSearchBarActions.js';
57 import {changeUrlAddress} from 'utils/Routes.js';
58 import extensibleViews from 'resources/views/extensibleViews.json';
59 import {getPersonalizationDetails} from 'app/personlaization/PersonalizationActions.js';
60 import {isEmpty} from 'lodash';
61
62 const mapStateToProps = ({mainWrapper, configurableViews}) => {
63   let {
64     showMenu = false,
65     toggleButtonActive = false,
66     externalRequestFound = {},
67     secondaryTitle = '',
68     subscriptionPayload = {},
69     subscriptionEnabled = false,
70     aaiTopLeftPersonalizedHeader = AAI_TOP_LEFT_HEADER,
71     aaiPersonalizedHtmlDocumentTitle = AAI_HTML_TITLE
72   } = mainWrapper;
73
74   let {
75     configurableViewsConfig
76   } = configurableViews;
77
78   return {
79     showMenu,
80     toggleButtonActive,
81     externalRequestFound,
82     secondaryTitle,
83     subscriptionPayload,
84     subscriptionEnabled,
85     configurableViewsConfig,
86     aaiTopLeftPersonalizedHeader,
87     aaiPersonalizedHtmlDocumentTitle
88   };
89 };
90
91
92 const mapActionsToProps = (dispatch) => {
93   return {
94     onShowMenu: () => dispatch(showMainMenu(true)),
95     onHideMenu: () => {
96       dispatch(showMainMenu(false));
97     },
98     dispatchAnalyticsData: () => dispatch(
99       postAnalyticsData(getStoreAnalyticsPayload())),
100     onRouteChange: () => {
101       dispatch(getClearGlobalMessageEvent());
102       dispatch(clearSuggestionsTextField());
103       dispatch(clearExtensibleViewData());
104       dispatch(clearFilters(filterBarActionTypes.CLEAR_FILTERS));
105       dispatch(setSecondaryTitle(undefined));
106     },
107     onExternalUrlRequest: (urlParamString) => {
108       dispatch(externalUrlRequest(urlParamString));
109     },
110     onExternalMessageRecieved: (messageJson) => {
111       dispatch(externalMessageRequest(messageJson));
112     },
113     onGetSubscriptionPayload: () => {
114       dispatch(getSubscriptionPayload());
115     },
116     onFetchCustomViews: () => {
117       dispatch(getConfigurableViewConfigs());
118     },
119     onGetPersonalizationValues: () => {
120       dispatch(getPersonalizationDetails());
121     }
122   };
123 };
124
125 class MainScreenHeader extends Component {
126   static propTypes = {
127     showMenu: PropTypes.bool,
128     toggleButtonActive: PropTypes.bool,
129     externalRequestFound: PropTypes.object,
130     secondaryTitle: PropTypes.string,
131     subscriptionPayload: PropTypes.object,
132     aaiTopLeftPersonalizedHeader: PropTypes.string,
133     aaiPersonalizedHtmlDocumentTitle: PropTypes.string
134   };
135
136   navigationLinkAndCurrentPathMatch(location, to) {
137     let linkPathElements = to.split('/');
138     let locationElements = location.pathname.split('/');
139
140     // the element arrays above will have the route at index 1 ... need to
141     // verify if the routes match
142     return locationElements[1] === linkPathElements[1];
143   }
144
145   hasRouteChanged(currentPath, nextPath) {
146     let currentPathParts = currentPath.split('/');
147     let nextPathParts = nextPath.split('/');
148
149     if (currentPathParts[1] !== nextPathParts[1]) {
150       return true;
151     } else {
152       return false;
153     }
154   }
155
156   isValidExternalURL(url) {
157     if(decodeURIComponent(url).indexOf('&') > 0 ) {
158       return true;
159     } else {
160       return false;
161     }
162   }
163
164   componentWillMount() {
165     this.props.onGetPersonalizationValues();
166     this.props.onGetSubscriptionPayload();
167     if(this.props.match.params.externalUrl !== undefined &&
168       this.isValidExternalURL(this.props.match.params.externalUrl)) {
169       this.props.onExternalUrlRequest(this.props.match.params.externalUrl);
170     }
171   }
172
173   componentWillReceiveProps(nextProps) {
174     if(!isEmpty(nextProps.aaiPersonalizedHtmlDocumentTitle)) {
175       if(!sessionStorage.getItem('PAGE_TITLE') || sessionStorage.getItem('PAGE_TITLE') !== nextProps.aaiPersonalizedHtmlDocumentTitle) {
176         sessionStorage.setItem('PAGE_TITLE', nextProps.aaiPersonalizedHtmlDocumentTitle);
177       }
178       document.title = nextProps.aaiPersonalizedHtmlDocumentTitle;
179     } else {
180       document.title = AAI_HTML_TITLE;
181     }
182     if (this.props.location &&
183       this.props.location.pathname !==
184       nextProps.location.pathname) {
185       // update analytics
186       this.props.dispatchAnalyticsData();
187
188       if (this.hasRouteChanged(this.props.location.pathname,
189           nextProps.location.pathname)) {
190         this.props.onRouteChange();
191       }
192     }
193
194     if(nextProps.match.params.externalUrl !== undefined &&
195       nextProps.match.params.externalUrl !== this.props.match.params.externalUrl &&
196       this.isValidExternalURL(nextProps.match.params.externalUrl)) {
197       this.props.onExternalUrlRequest(nextProps.match.params.externalUrl);
198     }
199     /* if the externalURL is not valid, we do not add any message as other proper
200      views will get that messages since the route will be this parameter.*/
201
202     if(this.props.externalRequestFound !== nextProps.externalRequestFound &&
203       nextProps.externalRequestFound !== undefined && nextProps.externalRequestFound.suggestion !== undefined) {
204       changeUrlAddress(nextProps.externalRequestFound.suggestion, nextProps.history);
205     }
206
207     if (nextProps.subscriptionEnabled) {
208       if (nextProps.subscriptionPayload !== this.props.subscriptionPayload &&
209         Object.keys(nextProps.subscriptionPayload).length > 0) {
210         var getWindowUrl = function (url) {
211           var split = url.split('/');
212           return split[0] + '//' + split[2];
213         };
214         window.parent.postMessage(
215           JSON.stringify(nextProps.subscriptionPayload),
216           getWindowUrl(document.referrer));
217       }
218     }
219   }
220
221   receiveMessage(event, $this) {
222     function isJson(str) {
223       try {
224         JSON.parse(str);
225       } catch (e) {
226         return false;
227       }
228       return true;
229     }
230     if(isJson(event.data)) {
231       let messageData = JSON.parse(event.data);
232       if(isJson(messageData.message)) {
233         $this.props.onExternalMessageRecieved(messageData.message);
234       }
235     }
236
237   }
238   componentDidMount() {
239     //TODO Move this logic to the component will receive props.
240     //Check if the event lister is available and if the subscription is
241     // enabled before registering for it
242     if(document.referrer) {
243       var $this = this;
244       window.addEventListener('message', function (e) {
245         $this.receiveMessage(e, $this);
246       }, false);
247     }
248
249     // fetch custom views
250     this.props.onFetchCustomViews();
251   }
252
253   componentWillUnmount() {
254     if(this.props.subscriptionEnabled) {
255       var $this = this;
256       window.removeEventListener('message', function (e) {
257         $this.receiveMessage(e, $this);
258       });
259     }
260   }
261
262   render() {
263     let {
264       showMenu,
265       onShowMenu,
266       onHideMenu,
267       toggleButtonActive,
268       secondaryTitle,
269       configurableViewsConfig,
270       aaiTopLeftPersonalizedHeader
271     } = this.props;
272
273     let menuOptions = [];
274
275     const MenuItem = ({label, iconClass, to}) => (
276       <Route path={to} children={({location}) => (
277         <NavLink to={to} onClick={onHideMenu}>
278           <div className={this.navigationLinkAndCurrentPathMatch(location, to) ? 'main-menu-button-active' : 'main-menu-button'}>
279             <div className={iconClass}/>
280             <div className='button-icon'>{label}</div>
281           </div>
282         </NavLink>
283       )}/>
284     );
285
286     const ConfigurableMenuItem = ({label, to}) => (
287       <Route path={to} children={({location}) => (
288         <NavLink to={to} onClick={onHideMenu}>
289           <div className={this.navigationLinkAndCurrentPathMatch(location, to) ?
290             'main-menu-button-active' : 'main-menu-button'}>
291             <div className='button-icon configurable-view-button-icon'/>
292             <div className='button-icon'>{label}</div>
293           </div>
294         </NavLink>
295       )}/>
296     );
297
298     // add Tier Support view
299     menuOptions.push(
300       <MenuItem key='schemaMenu' to='/schema' label={MENU_ITEM_TIER_SUPPORT}
301                 iconClass='button-icon view-inspect-button-icon'/>
302     );
303
304     // add VNF view
305     menuOptions.push(
306       <MenuItem key='vnfSearchMenu'
307                 to='/vnfSearch'
308                 label={MENU_ITEM_VNF_SEARCH}
309                 iconClass='button-icon vnf-search-button-icon'/>
310     );
311
312     // add all custom view menu options
313     for (let view in extensibleViews) {
314       let shouldDisplayIcon = false;
315       if(extensibleViews[view]['onlyRoute'] === undefined){
316         shouldDisplayIcon = true;
317       } else if(extensibleViews[view]['onlyRoute'] === false){
318         shouldDisplayIcon = true;
319       }
320       if(shouldDisplayIcon === true){
321         menuOptions.push(
322           <MenuItem key={extensibleViews[view]['viewName'] + 'Menu'} to={'/' + extensibleViews[view]['viewName']}
323                     label={extensibleViews[view]['displayName']}
324                     iconClass={'button-icon ' + extensibleViews[view]['iconClass']}/>
325         );
326       }
327     }
328
329     if (configurableViewsConfig && configurableViewsConfig.layouts) {
330       for (let configurableView in configurableViewsConfig.layouts) {
331         menuOptions.push(
332           <ConfigurableMenuItem key={configurableViewsConfig.layouts[configurableView]['id'] + 'Menu'} to={'/' + configurableViewsConfig.layouts[configurableView]['id']}
333                                 label={configurableViewsConfig.layouts[configurableView]['title']}/>
334         );
335       }
336     }
337
338     let secondaryTitleClass = 'secondary-header';
339     if (secondaryTitle === undefined || secondaryTitle === '') {
340       secondaryTitleClass = secondaryTitleClass + ' hidden';
341     }
342
343     return (
344       <div className='header'>
345         <div>
346           <Button
347             bsClass={(toggleButtonActive)
348               ? 'toggle-view-button-active'
349               : 'toggle-view-button'}
350             onClick={onShowMenu}>
351             <FontAwesome name='bars'/>
352           </Button>
353           <Modal show={showMenu} onHide={onHideMenu}
354                  dialogClassName='modal-main-menu'>
355             <Modal.Body>
356               {menuOptions}
357             </Modal.Body>
358           </Modal>
359           <span className='application-title'>{aaiTopLeftPersonalizedHeader}</span>
360           <GlobalAutoCompleteSearchBar history={this.props.history}/>
361         </div>
362         <GlobalInlineMessageBar />
363         <div className={secondaryTitleClass}>
364           <span className='secondary-title'>{secondaryTitle}</span>
365         </div>
366       </div>
367     );
368   }
369 }
370
371 export default connect(mapStateToProps, mapActionsToProps)(MainScreenHeader);