313adcde5aecfb7c203a7b8f71d5a8af17d12213
[aai/sparky-fe.git] / src / app / MainScreenHeader.jsx
1 /*
2  * ============LICENSE_START=======================================================
3  * org.onap.aai
4  * ================================================================================
5  * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
6  * Copyright © 2017 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  * ECOMP is a trademark and service mark of AT&T Intellectual Property.
22  */
23 import React, {Component} from 'react';
24 import {connect} from 'react-redux';
25 import FontAwesome from 'react-fontawesome';
26 import {clearFilters} from 'filter-bar-utils';
27 import Button from 'react-bootstrap/lib/Button.js';
28 import Modal from 'react-bootstrap/lib/Modal.js';
29 import GlobalAutoCompleteSearchBar from 'app/globalAutoCompleteSearchBar/GlobalAutoCompleteSearchBar.jsx';
30 import {postAnalyticsData} from 'app/analytics/AnalyticsActions.js';
31 import GlobalInlineMessageBar from 'app/globalInlineMessageBar/GlobalInlineMessageBar.jsx';
32 import {getClearGlobalMessageEvent} from 'app/globalInlineMessageBar/GlobalInlineMessageBarActions.js';
33 import {externalUrlRequest, externalMessageRequest} from 'app/contextHandler/ContextHandlerActions.js';
34
35 import {
36   filterBarActionTypes
37 } from 'utils/GlobalConstants.js';
38
39 import {
40   Route,
41   NavLink
42 } from 'react-router-dom';
43
44 import {
45   AAI_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
60
61 const mapStateToProps = ({mainWrapper}) => {
62   let {
63     showMenu = false,
64     toggleButtonActive = false,
65     externalRequestFound = {},
66     secondaryTitle = ''
67   } = mainWrapper;
68
69   return {
70     showMenu,
71     toggleButtonActive,
72     externalRequestFound,
73     secondaryTitle
74   };
75 };
76
77
78 const mapActionsToProps = (dispatch) => {
79   return {
80     onShowMenu: () => dispatch(showMainMenu(true)),
81     onHideMenu: () => {
82       dispatch(showMainMenu(false));
83     },
84     dispatchAnalyticsData: () => dispatch(
85       postAnalyticsData(document.documentElement.outerHTML.replace('\s+', ''))),
86     onRouteChange: () => {
87       dispatch(getClearGlobalMessageEvent());
88       dispatch(clearSuggestionsTextField());
89       dispatch(clearExtensibleViewData());
90       dispatch(clearFilters(filterBarActionTypes.CLEAR_FILTERS));
91       dispatch(setSecondaryTitle(undefined));
92     },
93     onExternalUrlRequest: (urlParamString) => {
94       dispatch(externalUrlRequest(urlParamString));
95     },
96     onExternalMessageRecieved: (messageJson) => {
97       dispatch(externalMessageRequest(messageJson));
98     }
99   };
100 };
101
102 class MainScreenHeader extends Component {
103   static propTypes = {
104     showMenu: React.PropTypes.bool,
105     toggleButtonActive: React.PropTypes.bool,
106     externalRequestFound: React.PropTypes.object,
107     secondaryTitle: React.PropTypes.string
108   };
109
110   navigationLinkAndCurrentPathMatch(location, to) {
111     let linkPathElements = to.split('/');
112     let locationElements = location.pathname.split('/');
113
114     // the element arrays above will have the route at index 1 ... need to
115     // verify if the routes match
116     return locationElements[1] === linkPathElements[1];
117   }
118
119   hasRouteChanged(currentPath, nextPath) {
120     let currentPathParts = currentPath.split('/');
121     let nextPathParts = nextPath.split('/');
122
123     if (currentPathParts[1] !== nextPathParts[1]) {
124       return true;
125     } else {
126       return false;
127     }
128   }
129   isValidExternalURL(url) {
130     if(decodeURIComponent(url).indexOf('&') > 0 ) {
131       return true;
132     } else {
133       return false;
134     }
135   }
136   componentWillMount() {
137     if(this.props.match.params.externalUrl !== undefined &&
138       this.isValidExternalURL(this.props.match.params.externalUrl)) {
139       this.props.onExternalUrlRequest(this.props.match.params.externalUrl);
140     }
141   }
142   componentWillReceiveProps(nextProps) {
143     if (this.props.location &&
144       this.props.location.pathname !==
145       nextProps.location.pathname) {
146       // update analytics
147       this.props.dispatchAnalyticsData();
148
149       if (this.hasRouteChanged(this.props.location.pathname,
150           nextProps.location.pathname)) {
151         this.props.onRouteChange();
152       }
153     }
154
155     if(nextProps.match.params.externalUrl !== undefined &&
156       nextProps.match.params.externalUrl !== this.props.match.params.externalUrl &&
157       this.isValidExternalURL(nextProps.match.params.externalUrl)) {
158       this.props.onExternalUrlRequest(nextProps.match.params.externalUrl);
159     }
160     /* if the externalURL is not valid, we do not add any message as other proper
161     views will get that messages since the route will be this parameter.*/
162
163     if(this.props.externalRequestFound !== nextProps.externalRequestFound &&
164       nextProps.externalRequestFound !== undefined && nextProps.externalRequestFound.suggestion !== undefined) {
165       changeUrlAddress(nextProps.externalRequestFound.suggestion, nextProps.history);
166     }
167   }
168
169   receiveMessage(event) {
170     function isJson(str) {
171       try {
172         JSON.parse(str);
173       } catch (e) {
174         return false;
175       }
176       return true;
177     }
178     let messageData = event.data.message;
179     if(isJson(messageData)) {
180       this.props.onExternalMessageRecieved(JSON.parse(messageData));
181     }
182   }
183   componentDidMount() {
184     window.addEventListener('message', this.receiveMessage, false);
185   }
186   componentWillUnmount() {
187     window.removeEventListener('message', this.receiveMessage);
188   }
189
190   render() {
191     let {
192       showMenu,
193       onShowMenu,
194       onHideMenu,
195       toggleButtonActive,
196       secondaryTitle
197     } = this.props;
198
199     let menuOptions = [];
200
201     const MenuItem = ({label, iconClass, to}) => (
202       <Route path={to} children={({location}) => (
203         <NavLink to={to} onClick={onHideMenu}>
204           <div className={this.navigationLinkAndCurrentPathMatch(location, to) ? 'main-menu-button-active' : 'main-menu-button'}>
205             <div className={iconClass}/>
206             <div className='button-icon'>{label}</div>
207           </div>
208         </NavLink>
209       )}/>
210     );
211
212     // add Tier Support view
213     menuOptions.push(
214       <MenuItem key='schemaMenu' to='/schema' label={MENU_ITEM_TIER_SUPPORT}
215                 iconClass='button-icon view-inspect-button-icon'/>
216     );
217
218     // add VNF view
219     menuOptions.push(
220       <MenuItem key='vnfSearchMenu'
221         to='/vnfSearch'
222         label={MENU_ITEM_VNF_SEARCH}
223         iconClass='button-icon vnf-search-button-icon'/>
224     );
225
226     // add all custom view menu options
227     for (let view in extensibleViews) {
228       menuOptions.push(
229         <MenuItem key={extensibleViews[view]['viewName'] + 'Menu'} to={'/' + extensibleViews[view]['viewName']}
230                   label={extensibleViews[view]['displayName']}
231                   iconClass={'button-icon ' + extensibleViews[view]['iconClass']}/>
232       );
233     }
234
235     let secondaryTitleClass = 'secondary-header';
236     if (secondaryTitle === undefined || secondaryTitle === '') {
237       secondaryTitleClass = secondaryTitleClass + ' hidden';
238     }
239
240     return (
241       <div className='header'>
242         <div>
243           <Button
244             bsClass={(toggleButtonActive)
245             ? 'toggle-view-button-active'
246             : 'toggle-view-button'}
247             onClick={onShowMenu}>
248             <FontAwesome name='bars'/>
249           </Button>
250           <Modal show={showMenu} onHide={onHideMenu}
251                  dialogClassName='modal-main-menu'>
252             <Modal.Body>
253               {menuOptions}
254             </Modal.Body>
255           </Modal>
256           <span className='application-title'>{AAI_TITLE}</span>
257           <GlobalAutoCompleteSearchBar history={this.props.history}/>
258         </div>
259         <GlobalInlineMessageBar />
260         <div className={secondaryTitleClass}>
261           <span className='secondary-title'>{secondaryTitle}</span>
262         </div>
263       </div>
264     );
265   }
266 }
267
268 export default connect(mapStateToProps, mapActionsToProps)(MainScreenHeader);