Updated Sparky to add ECOMP functionality Browse, Specialized Search, BYOQ, and the...
[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 {postAnalyticsData, getStoreAnalyticsPayload} from 'app/analytics/AnalyticsActions.js';
29 import GlobalInlineMessageBar from 'app/globalInlineMessageBar/GlobalInlineMessageBar.jsx';
30 import {getClearGlobalMessageEvent} from 'app/globalInlineMessageBar/GlobalInlineMessageBarActions.js';
31 import {externalUrlRequest, externalMessageRequest, getSubscriptionPayload} from 'app/contextHandler/ContextHandlerActions.js';
32 import { getConfigurableViewConfigs } from 'app/configurableViews/ConfigurableViewActions.js';
33 import {GlobalExtConstants} from 'utils/GlobalExtConstants.js';
34 import axios from 'axios';
35 import {BASE_URL} from 'app/networking/NetworkConstants.js';
36
37 import {
38   filterBarActionTypes,
39   ENVIRONMENT
40 } from 'utils/GlobalConstants.js';
41
42 import {
43   Route,
44   NavLink
45 } from 'react-router-dom';
46
47 import {
48   AAI_TOP_LEFT_HEADER,
49   AAI_HTML_TITLE,
50   AAI_APERTURE_SERVICE,
51   AAI_LOADTEMPLATE_MAX_COUNT
52 } from './MainScreenWrapperConstants.js';
53
54 import {
55   showMainMenu,
56   clearExtensibleViewData,
57   setSecondaryTitle
58 } from './MainScreenWrapperActionHelper.js';
59
60 import {clearSuggestionsTextField} from 'app/globalAutoCompleteSearchBar/GlobalAutoCompleteSearchBarActions.js';
61 import {changeUrlAddress} from 'utils/Routes.js';
62 import defaultViews from 'resources/views/defaultViews.json';
63 import defaultViews_onap from 'resources/views/defaultViews_onap.json';
64 import {getPersonalizationDetails} from 'app/personlaization/PersonalizationActions.js';
65 import {isEmpty} from 'lodash';
66
67 let INVLIST = GlobalExtConstants.INVLIST;
68
69 const mapStateToProps = ({mainWrapper, configurableViews}) => {
70   let {
71     showMenu = false,
72     toggleButtonActive = false,
73     externalRequestFound = {},
74     secondaryTitle = '',
75     subscriptionPayload = {},
76     subscriptionEnabled = false,
77     aaiTopLeftPersonalizedHeader = AAI_TOP_LEFT_HEADER,
78     aaiPersonalizedHtmlDocumentTitle = AAI_HTML_TITLE,
79     aaiPersonalizedApertureService = AAI_APERTURE_SERVICE,
80     aaiPersonalizedLoadTemplateMaxCount = AAI_LOADTEMPLATE_MAX_COUNT
81   } = mainWrapper;
82
83   let {
84     configurableViewsConfig
85   } = configurableViews;
86
87   return {
88     showMenu,
89     toggleButtonActive,
90     externalRequestFound,
91     secondaryTitle,
92     subscriptionPayload,
93     subscriptionEnabled,
94     configurableViewsConfig,
95     aaiTopLeftPersonalizedHeader,
96     aaiPersonalizedHtmlDocumentTitle,
97     aaiPersonalizedApertureService,
98     aaiPersonalizedLoadTemplateMaxCount
99   };
100 };
101
102
103 const mapActionsToProps = (dispatch) => {
104   return {
105     onShowMenu: () => dispatch(showMainMenu(true)),
106     onHideMenu: () => {
107       dispatch(showMainMenu(false));
108     },
109     dispatchAnalyticsData: () => dispatch(
110       postAnalyticsData(getStoreAnalyticsPayload())),
111     onRouteChange: () => {
112       dispatch(getClearGlobalMessageEvent());
113       dispatch(clearSuggestionsTextField());
114       dispatch(clearExtensibleViewData());
115       dispatch(clearFilters(filterBarActionTypes.CLEAR_FILTERS));
116       dispatch(setSecondaryTitle(undefined));
117     },
118     onExternalUrlRequest: (urlParamString) => {
119       dispatch(externalUrlRequest(urlParamString));
120     },
121     onExternalMessageRecieved: (messageJson) => {
122       dispatch(externalMessageRequest(messageJson));
123     },
124     onGetSubscriptionPayload: () => {
125       dispatch(getSubscriptionPayload());
126     },
127     onFetchCustomViews: () => {
128       dispatch(getConfigurableViewConfigs());
129     },
130     onGetPersonalizationValues: () => {
131       dispatch(getPersonalizationDetails());
132     }
133   };
134 };
135
136 class MainScreenHeader extends Component {
137   static propTypes = {
138     showMenu: PropTypes.bool,
139     toggleButtonActive: PropTypes.bool,
140     externalRequestFound: PropTypes.object,
141     secondaryTitle: PropTypes.string,
142     subscriptionPayload: PropTypes.object,
143     aaiTopLeftPersonalizedHeader: PropTypes.string,
144     aaiPersonalizedHtmlDocumentTitle: PropTypes.string,
145     aaiPersonalizedApertureService: PropTypes.bool,
146     aaiPersonalizedLoadTemplateMaxCount: PropTypes.string
147   };
148
149   navigationLinkAndCurrentPathMatch(location, to) {
150     let linkPathElements = to.split('/');
151     let locationElements = location.pathname.split('/');
152
153     // the element arrays above will have the route at index 1 ... need to
154     // verify if the routes match
155     return locationElements[1] === linkPathElements[1];
156   }
157
158   hasRouteChanged(currentPath, nextPath) {
159     let currentPathParts = currentPath.split('/');
160     let nextPathParts = nextPath.split('/');
161
162     if (currentPathParts[1] !== nextPathParts[1]) {
163       return true;
164     } else {
165       return false;
166     }
167   }
168
169   isValidExternalURL(url) {
170     if(decodeURIComponent(url).indexOf('&') > 0 ) {
171       return true;
172     } else {
173       return false;
174     }
175   }
176
177   componentWillMount() {
178     this.props.onGetPersonalizationValues();
179     this.props.onGetSubscriptionPayload();
180     if(this.props.match.params.externalUrl !== undefined &&
181       this.isValidExternalURL(this.props.match.params.externalUrl)) {
182       this.props.onExternalUrlRequest(this.props.match.params.externalUrl);
183     }
184     sessionStorage.setItem(ENVIRONMENT + 'ENABLE_ANALYSIS', !INVLIST.IS_ONAP);
185     var portalInfoPath = BASE_URL + '/portal/info';
186     //portalInfoPath = 'https://localhost:8000/portal/info';
187     axios.get(portalInfoPath).then(res => {
188       console.log('res:' + res.data);
189       var roles = [];
190       if(res.status === 200){
191         if(res.data.status && (res.data.status !== '200')){
192           this.setDefaultCredentials(res.data);
193         }else{
194           sessionStorage.setItem(ENVIRONMENT + 'userId', res.data.attuid);
195           for(var i = 0; i < res.data.role.length; i++){
196             roles.push(res.data.role[i].name);
197           }
198           sessionStorage.setItem(ENVIRONMENT + 'roles', roles);
199         }
200       }else{
201         this.setDefaultCredentials(res.data);
202       }
203     }, error=>{
204       this.setDefaultCredentials(error);
205     }).catch(error => {
206       this.setDefaultCredentials(error);
207     });
208   }
209
210   setDefaultCredentials = (error) =>{
211     console.log('MainScreenHeader.jsx :: Issue retrieving portal info from sparky backend, setting default, details - ' + JSON.stringify(error));
212     sessionStorage.setItem(ENVIRONMENT + 'userId', 'default_uid');
213     var roles = ['ui_view'];
214     sessionStorage.setItem(ENVIRONMENT + 'roles', roles);
215   }
216
217   componentWillReceiveProps(nextProps) {
218     if(!isEmpty(nextProps.aaiPersonalizedHtmlDocumentTitle)) {
219       if(!sessionStorage.getItem(ENVIRONMENT + 'PAGE_TITLE') || sessionStorage.getItem(ENVIRONMENT + 'PAGE_TITLE') !== nextProps.aaiPersonalizedHtmlDocumentTitle) {
220         sessionStorage.setItem(ENVIRONMENT + 'PAGE_TITLE', nextProps.aaiPersonalizedHtmlDocumentTitle);
221       }
222       document.title = nextProps.aaiPersonalizedHtmlDocumentTitle;
223     } else {
224       document.title = AAI_HTML_TITLE;
225     }
226     //Added for APERTURE Service Enable/Disable
227     if(!sessionStorage.getItem(ENVIRONMENT + 'APERTURE_SERVICE') || JSON.parse(sessionStorage.getItem(ENVIRONMENT + 'APERTURE_SERVICE')) !== nextProps.aaiPersonalizedApertureService) {
228       if(JSON.parse(sessionStorage.getItem(ENVIRONMENT + 'APERTURE_SERVICE')) !== nextProps.aaiPersonalizedApertureService){
229         sessionStorage.setItem(ENVIRONMENT + 'APERTURE_SERVICE', Boolean(nextProps.aaiPersonalizedApertureService));
230         if(nextProps.aaiPersonalizedApertureService){
231           sessionStorage.setItem(ENVIRONMENT + 'ENABLE_ANALYSIS', true);
232         }else{
233           sessionStorage.setItem(ENVIRONMENT + 'ENABLE_ANALYSIS', false);
234         }
235       }else{
236         sessionStorage.setItem(ENVIRONMENT + 'APERTURE_SERVICE', Boolean(nextProps.aaiPersonalizedApertureService));
237       }     
238     }
239     if(!sessionStorage.getItem(ENVIRONMENT + 'APERTURE_SERVICE')){
240       sessionStorage.setItem(ENVIRONMENT + 'ENABLE_ANALYSIS', false);
241     }
242     if(!sessionStorage.getItem(ENVIRONMENT + 'LOADTEMPLATE_MAX_COUNT') || sessionStorage.getItem(ENVIRONMENT + 'LOADTEMPLATE_MAX_COUNT') !== nextProps.aaiPersonalizedLoadTemplateMaxCount) {
243       sessionStorage.setItem(ENVIRONMENT + 'LOADTEMPLATE_MAX_COUNT', nextProps.aaiPersonalizedLoadTemplateMaxCount);
244     }
245     if (this.props.location &&
246       this.props.location.pathname !==
247       nextProps.location.pathname) {
248       // update analytics
249       this.props.dispatchAnalyticsData();
250
251       if (this.hasRouteChanged(this.props.location.pathname,
252           nextProps.location.pathname)) {
253         this.props.onRouteChange();
254       }
255     }
256
257     if(nextProps.match.params.externalUrl !== undefined &&
258       nextProps.match.params.externalUrl !== this.props.match.params.externalUrl &&
259       this.isValidExternalURL(nextProps.match.params.externalUrl)) {
260       this.props.onExternalUrlRequest(nextProps.match.params.externalUrl);
261     }
262     /* if the externalURL is not valid, we do not add any message as other proper
263      views will get that messages since the route will be this parameter.*/
264
265     if(this.props.externalRequestFound !== nextProps.externalRequestFound &&
266       nextProps.externalRequestFound !== undefined && nextProps.externalRequestFound.suggestion !== undefined) {
267       changeUrlAddress(nextProps.externalRequestFound.suggestion, nextProps.history);
268     }
269
270     if (nextProps.subscriptionEnabled) {
271       if (nextProps.subscriptionPayload !== this.props.subscriptionPayload &&
272         Object.keys(nextProps.subscriptionPayload).length > 0) {
273         var getWindowUrl = function (url) {
274           var split = url.split('/');
275           return split[0] + '//' + split[2];
276         };
277         window.parent.postMessage(
278           JSON.stringify(nextProps.subscriptionPayload),
279           getWindowUrl(document.referrer));
280       }
281     }
282   }
283
284   receiveMessage(event, $this) {
285     function isJson(str) {
286       try {
287         JSON.parse(str);
288       } catch (e) {
289         return false;
290       }
291       return true;
292     }
293     if(isJson(event.data)) {
294       let messageData = JSON.parse(event.data);
295       if(isJson(messageData.message)) {
296         $this.props.onExternalMessageRecieved(messageData.message);
297       }
298     }
299
300   }
301   componentDidMount() {
302     //TODO Move this logic to the component will receive props.
303     //Check if the event lister is available and if the subscription is
304     // enabled before registering for it
305     if(document.referrer) {
306       var $this = this;
307       window.addEventListener('message', function (e) {
308         $this.receiveMessage(e, $this);
309       }, false);
310     }
311
312     // fetch custom views
313     this.props.onFetchCustomViews();
314   }
315
316   componentWillUnmount() {
317     if(this.props.subscriptionEnabled) {
318       var $this = this;
319       window.removeEventListener('message', function (e) {
320         $this.receiveMessage(e, $this);
321       });
322     }
323   }
324
325   render() {
326     let {
327       showMenu,
328       onShowMenu,
329       onHideMenu,
330       toggleButtonActive,
331       secondaryTitle,
332       configurableViewsConfig,
333       aaiTopLeftPersonalizedHeader
334     } = this.props;
335
336     let menuOptions = [];
337
338     const MenuItem = ({label, iconClass, to}) => (
339       <Route path={to} children={({location}) => (
340         <NavLink to={to} onClick={onHideMenu}>
341           <div className={this.navigationLinkAndCurrentPathMatch(location, to) ? 'main-menu-button-active' : 'main-menu-button'}>
342             <div className={iconClass}/>
343             <div className='button-icon'>{label}</div>
344           </div>
345         </NavLink>
346       )}/>
347     );
348
349     const ConfigurableMenuItem = ({label, to}) => (
350       <Route path={to} children={({location}) => (
351         <NavLink to={to} onClick={onHideMenu}>
352           <div className={this.navigationLinkAndCurrentPathMatch(location, to) ?
353             'main-menu-button-active' : 'main-menu-button'}>
354             <div className='button-icon configurable-view-button-icon'/>
355             <div className='button-icon'>{label}</div>
356           </div>
357         </NavLink>
358       )}/>
359     );
360
361     let dv = defaultViews;
362     if(INVLIST.IS_ONAP){
363         dv = defaultViews_onap;
364     }
365
366     // add all default view menu options
367     for (let view in dv) {
368       let shouldDisplayIcon = false;
369       if(dv[view]['onlyRoute'] === undefined){
370         shouldDisplayIcon = true;
371       } else if(dv[view]['onlyRoute'] === false){
372         shouldDisplayIcon = true;
373       }
374       if(shouldDisplayIcon === true){
375         menuOptions.push(
376           <MenuItem key={dv[view]['viewName'] + 'Menu'} to={'/' + dv[view]['viewName']}
377                     label={dv[view]['displayName']}
378                     iconClass={'button-icon font-awesome-icon fa ' + dv[view]['iconClass']}/>
379         );
380       }
381     }
382
383     if (configurableViewsConfig && configurableViewsConfig.layouts) {
384       for (let configurableView in configurableViewsConfig.layouts) {
385         menuOptions.push(
386           <ConfigurableMenuItem key={configurableViewsConfig.layouts[configurableView]['id'] + 'Menu'} to={'/' + configurableViewsConfig.layouts[configurableView]['id']}
387                                 label={configurableViewsConfig.layouts[configurableView]['title']}/>
388         );
389       }
390     }
391
392     let secondaryTitleClass = 'secondary-header';
393     if (secondaryTitle === undefined || secondaryTitle === '') {
394       secondaryTitleClass = secondaryTitleClass + ' hidden';
395     }
396
397     return (
398       <div className='header'>
399         <div>
400           <Button
401             bsClass={(toggleButtonActive)
402               ? 'toggle-view-button-active'
403               : 'toggle-view-button'}
404             onClick={onShowMenu}>
405             <FontAwesome name='bars'/>
406           </Button>
407           <Modal show={showMenu} onHide={onHideMenu}
408                  dialogClassName='modal-main-menu'>
409             <Modal.Body>
410               {menuOptions}
411             </Modal.Body>
412           </Modal>
413           <span className='application-title'>{aaiTopLeftPersonalizedHeader}</span>
414         </div>
415         <GlobalInlineMessageBar />
416         <div className={secondaryTitleClass}>
417           <span className='secondary-title'>{secondaryTitle}</span>
418         </div>
419       </div>
420     );
421   }
422 }
423
424 export default connect(mapStateToProps, mapActionsToProps)(MainScreenHeader);