diff options
Diffstat (limited to 'src/app')
-rw-r--r-- | src/app/AppStore.js | 5 | ||||
-rw-r--r-- | src/app/MainScreenHeader.jsx | 61 | ||||
-rw-r--r-- | src/app/MainScreenWrapper.jsx | 67 | ||||
-rw-r--r-- | src/app/MainScreenWrapperActionHelper.js | 1 | ||||
-rw-r--r-- | src/app/configurableViews/ConfigurableViewActions.js | 48 | ||||
-rw-r--r-- | src/app/configurableViews/ConfigurableViewConstants.js | 11 | ||||
-rw-r--r-- | src/app/configurableViews/ConfigurableViewManager.js | 27 | ||||
-rw-r--r-- | src/app/configurableViews/ConfigurableViewReducer.js | 26 | ||||
-rw-r--r-- | src/app/configurableViews/index.js | 27 | ||||
-rw-r--r-- | src/app/networking/NetworkCalls.js | 31 | ||||
-rw-r--r-- | src/app/tierSupport/TierSupportActions.js | 121 |
11 files changed, 262 insertions, 163 deletions
diff --git a/src/app/AppStore.js b/src/app/AppStore.js index 9205937..842b683 100644 --- a/src/app/AppStore.js +++ b/src/app/AppStore.js @@ -27,7 +27,7 @@ import InventoryReducer from './inventory/InventoryReducer.js'; import VnfSearchReducer from './vnfSearch/VnfSearchReducer.js'; import GlobalInlineMessageBarReducer from 'app/globalInlineMessageBar/GlobalInlineMessageBarReducer.js'; import ExtensibilityReducer from 'app/extensibility/ExtensibilityReducer.js'; - +import ConfigurableViewReducer from 'app/configurableViews/ConfigurableViewReducer.js'; function createCompose() { @@ -45,7 +45,8 @@ export const storeCreator = (initialState) => createStore( inventoryReducer: InventoryReducer, vnfSearch: VnfSearchReducer, globalInlineMessageBar: GlobalInlineMessageBarReducer, - extensibility: ExtensibilityReducer + extensibility: ExtensibilityReducer, + configurableViews: ConfigurableViewReducer }), initialState, createCompose() diff --git a/src/app/MainScreenHeader.jsx b/src/app/MainScreenHeader.jsx index e485161..8c5e13d 100644 --- a/src/app/MainScreenHeader.jsx +++ b/src/app/MainScreenHeader.jsx @@ -30,6 +30,7 @@ import {postAnalyticsData} from 'app/analytics/AnalyticsActions.js'; import GlobalInlineMessageBar from 'app/globalInlineMessageBar/GlobalInlineMessageBar.jsx'; import {getClearGlobalMessageEvent} from 'app/globalInlineMessageBar/GlobalInlineMessageBarActions.js'; import {externalUrlRequest, externalMessageRequest, getSubscriptionPayload} from 'app/contextHandler/ContextHandlerActions.js'; +import { getConfigurableViewConfigs } from 'app/configurableViews/ConfigurableViewActions.js'; import { filterBarActionTypes } from 'utils/GlobalConstants.js'; @@ -56,7 +57,7 @@ import {changeUrlAddress} from 'utils/Routes.js'; import extensibleViews from 'resources/views/extensibleViews.json'; -const mapStateToProps = ({mainWrapper}) => { +const mapStateToProps = ({mainWrapper, configurableViews}) => { let { showMenu = false, toggleButtonActive = false, @@ -66,13 +67,18 @@ const mapStateToProps = ({mainWrapper}) => { subscriptionEnabled = false } = mainWrapper; + let { + configurableViewsConfig + } = configurableViews; + return { showMenu, toggleButtonActive, externalRequestFound, secondaryTitle, subscriptionPayload, - subscriptionEnabled + subscriptionEnabled, + configurableViewsConfig }; }; @@ -100,6 +106,9 @@ const mapActionsToProps = (dispatch) => { }, onGetSubscriptionPayload: () => { dispatch(getSubscriptionPayload()); + }, + onFetchCustomViews: () => { + dispatch(getConfigurableViewConfigs()); } }; }; @@ -132,7 +141,7 @@ class MainScreenHeader extends Component { return false; } } - + isValidExternalURL(url) { if(decodeURIComponent(url).indexOf('&') > 0 ) { return true; @@ -168,7 +177,7 @@ class MainScreenHeader extends Component { this.props.onExternalUrlRequest(nextProps.match.params.externalUrl); } /* if the externalURL is not valid, we do not add any message as other proper - views will get that messages since the route will be this parameter.*/ + views will get that messages since the route will be this parameter.*/ if(this.props.externalRequestFound !== nextProps.externalRequestFound && nextProps.externalRequestFound !== undefined && nextProps.externalRequestFound.suggestion !== undefined) { @@ -216,13 +225,17 @@ class MainScreenHeader extends Component { $this.receiveMessage(e, $this); }, false); } + + // fetch custom views + this.props.onFetchCustomViews(); } + componentWillUnmount() { if(this.props.subscriptionEnabled) { var $this = this; window.removeEventListener('message', function (e) { - $this.receiveMessage(e, $this); - } + $this.receiveMessage(e, $this); + } ); } } @@ -233,7 +246,8 @@ class MainScreenHeader extends Component { onShowMenu, onHideMenu, toggleButtonActive, - secondaryTitle + secondaryTitle, + configurableViewsConfig } = this.props; let menuOptions = []; @@ -249,6 +263,18 @@ class MainScreenHeader extends Component { )}/> ); + const ConfigurableMenuItem = ({label, to}) => ( + <Route path={to} children={({location}) => ( + <NavLink to={to} onClick={onHideMenu}> + <div className={this.navigationLinkAndCurrentPathMatch(location, to) ? + 'main-menu-button-active' : 'main-menu-button'}> + <div className='button-icon configurable-view-button-icon'/> + <div className='button-icon'>{label}</div> + </div> + </NavLink> + )}/> + ); + // add Tier Support view menuOptions.push( <MenuItem key='schemaMenu' to='/schema' label={MENU_ITEM_TIER_SUPPORT} @@ -258,9 +284,9 @@ class MainScreenHeader extends Component { // add VNF view menuOptions.push( <MenuItem key='vnfSearchMenu' - to='/vnfSearch' - label={MENU_ITEM_VNF_SEARCH} - iconClass='button-icon vnf-search-button-icon'/> + to='/vnfSearch' + label={MENU_ITEM_VNF_SEARCH} + iconClass='button-icon vnf-search-button-icon'/> ); // add all custom view menu options @@ -277,7 +303,16 @@ class MainScreenHeader extends Component { label={extensibleViews[view]['displayName']} iconClass={'button-icon ' + extensibleViews[view]['iconClass']}/> ); - } + } + } + + if (configurableViewsConfig && configurableViewsConfig.layouts) { + for (let configurableView in configurableViewsConfig.layouts) { + menuOptions.push( + <ConfigurableMenuItem key={configurableViewsConfig.layouts[configurableView]['id'] + 'Menu'} to={'/' + configurableViewsConfig.layouts[configurableView]['id']} + label={configurableViewsConfig.layouts[configurableView]['title']}/> + ); + } } let secondaryTitleClass = 'secondary-header'; @@ -290,8 +325,8 @@ class MainScreenHeader extends Component { <div> <Button bsClass={(toggleButtonActive) - ? 'toggle-view-button-active' - : 'toggle-view-button'} + ? 'toggle-view-button-active' + : 'toggle-view-button'} onClick={onShowMenu}> <FontAwesome name='bars'/> </Button> diff --git a/src/app/MainScreenWrapper.jsx b/src/app/MainScreenWrapper.jsx index 6689af9..730ac93 100644 --- a/src/app/MainScreenWrapper.jsx +++ b/src/app/MainScreenWrapper.jsx @@ -25,6 +25,10 @@ import TierSupport from './tierSupport/TierSupport.jsx'; import VnfSearch from './vnfSearch/VnfSearch.jsx'; import MainScreenHeader from './MainScreenHeader.jsx'; import {decryptParamsForView, changeUrlAddress} from 'utils/Routes.js'; +import { + getConfigurableViewConfigs, + setCustomRoutes +} from 'app/configurableViews/ConfigurableViewActions.js'; import {isEmpty} from 'lodash'; import {genericRequest} from 'app/networking/NetworkCalls.js'; import { @@ -42,18 +46,36 @@ import { } from './MainScreenWrapperActionHelper.js'; import extensibleViews from 'resources/views/extensibleViews.json'; +import customComponentConfig from 'resources/views/customComponents.json'; +import { newCustomComponentsEvent } from 'app/configurableViews/ConfigurableViewActions.js'; +import { + getConfigurableRoutes +} from 'app/configurableViews/ConfigurableViewManager.js'; + +import { + getConfiguredComponentList +} from 'app/configurableViews/index.js'; -const mapStateToProps = ({mainWrapper}) => { +const mapStateToProps = ({mainWrapper, configurableViews}) => { let { showMenu = false, toggleButtonActive = false, extensibleViewNetworkCallbackData = {} } = mainWrapper; + let { + configurableViewsConfig = {}, + customComponents = {}, + customRoutes = [] + } = configurableViews; + return { showMenu, toggleButtonActive, - extensibleViewNetworkCallbackData + extensibleViewNetworkCallbackData, + configurableViewsConfig, + customComponents, + customRoutes }; }; @@ -68,6 +90,15 @@ const mapActionsToProps = (dispatch) => { }, onOverlayNetworkCallback: (apiUrl, body, viewName, curViewData, responseEventKey) => { dispatch(overlayNetworkCallback(apiUrl, body, viewName, curViewData, responseEventKey)); + }, + onConfigurableViewsInitialLoad: (components) => { + dispatch(newCustomComponentsEvent(components)); + }, + onFetchCustomViews: () => { + dispatch(getConfigurableViewConfigs()); + }, + onSetCustomRoutes: (routes) => { + dispatch(setCustomRoutes(routes)); } }; }; @@ -82,6 +113,25 @@ class MainScreenWrapper extends Component { } + componentDidMount() { + // fetch custom views + this.props.onFetchCustomViews(); + + // fetch custom components + let components = getConfiguredComponentList(customComponentConfig); + this.props.onConfigurableViewsInitialLoad(components); + } + + componentDidUpdate(prevProps) { + if ((Object.keys(this.props.customComponents).length > 0 && + Object.keys(this.props.configurableViewsConfig).length > 0) && + ((JSON.stringify(prevProps.configurableViewsConfig) !== JSON.stringify(this.props.configurableViewsConfig)) || + (JSON.stringify(prevProps.customComponents) !== JSON.stringify(this.props.customComponents)))) { + // we have both config and components populated and one was just set + let customRoutes = getConfigurableRoutes(this.props.configurableViewsConfig, this.props.customComponents); + this.props.onSetCustomRoutes(customRoutes); + } + } render() { @@ -89,14 +139,14 @@ class MainScreenWrapper extends Component { onExtensibleViewNetworkCallback, extensibleViewNetworkCallbackData, onExtensibleViewMessageCallback, - onOverlayNetworkCallback + onOverlayNetworkCallback, + customRoutes } = this.props; let customViewList = []; extensibleViews.forEach(function(view,key) { - let path = '', - extKey = ''; + let path = '', extKey = ''; if(isEmpty(extensibleViews[key]['viewParams'])){ path = '/' + view.viewName + '/:extensibleViewParams?'; extKey = view.viewName + 'Route'; @@ -140,13 +190,13 @@ class MainScreenWrapper extends Component { if(isEmpty(extensibleViews[key]['isExact']) && !extensibleViews[key]['isExact']){ customViewList.push( <Route key={extKey} path={path} render={renderComponent}/> - ); + ); } else { customViewList.push( <Route key={extKey} exact path={path} render={renderComponent}/> - ); + ); } - + }); return ( @@ -159,6 +209,7 @@ class MainScreenWrapper extends Component { <Route key='TierSupportRoue' path='/schema/:viParam?' component={TierSupport}/> <Route key='VnfSearchRoute' path='/vnfSearch/:filters?' component={VnfSearch}/> {customViewList} + {customRoutes} </div> </Router> ); diff --git a/src/app/MainScreenWrapperActionHelper.js b/src/app/MainScreenWrapperActionHelper.js index 371ea42..dd7450b 100644 --- a/src/app/MainScreenWrapperActionHelper.js +++ b/src/app/MainScreenWrapperActionHelper.js @@ -136,7 +136,6 @@ export function extensibleViewNetworkCallback(urlApi, postBody, paramName, curVi () => fetchRequestObj(BASE_URL + urlApi, POST, POST_HEADER, postBody); - return dispatch => { dispatch(extensibleViewData(dataFetchRequest, paramName, curViewData)); }; diff --git a/src/app/configurableViews/ConfigurableViewActions.js b/src/app/configurableViews/ConfigurableViewActions.js new file mode 100644 index 0000000..7cffacc --- /dev/null +++ b/src/app/configurableViews/ConfigurableViewActions.js @@ -0,0 +1,48 @@ +import { + GET, + POST_HEADER +} from 'app/networking/NetworkConstants.js'; +import { + GET_LAYOUTS_URL, + configurableViewsActionTypes +} from './ConfigurableViewConstants.js'; + +function createConfigReceivedEvent(config) { + return { + type: configurableViewsActionTypes.CONFIGURABLE_VIEWS_CONFIG_RECEIVED, + data: config + }; +} + +export function newCustomComponentsEvent(components) { + return { + type: configurableViewsActionTypes.CUSTOM_COMPONENTS_RECEIVED, + data: components + }; +} + +export function setCustomRoutes(routes) { + return { + type: configurableViewsActionTypes.CUSTOM_ROUTES, + data: routes + }; +} + +export function getConfigurableViewConfigs() { + return dispatch => { + return fetch(GET_LAYOUTS_URL, { + method: GET, + headers: POST_HEADER + }).then( + (response) => response.json() + ).then( + (responseJson) => { + dispatch(createConfigReceivedEvent(responseJson)); + } + ).catch( + (err) => { + console.log(`problems fetching configurable view configs: ${err}`); + } + ); + }; +} diff --git a/src/app/configurableViews/ConfigurableViewConstants.js b/src/app/configurableViews/ConfigurableViewConstants.js new file mode 100644 index 0000000..202dd18 --- /dev/null +++ b/src/app/configurableViews/ConfigurableViewConstants.js @@ -0,0 +1,11 @@ +import keyMirror from 'utils/KeyMirror.js'; +import {BASE_URL} from 'app/networking/NetworkConstants.js'; + +export const configurableViewsActionTypes = keyMirror({ + CONFIGURABLE_VIEWS_CONFIG_RECEIVED: null, + CONFIGURABLE_VIEWS_DATA_RECEIVED: null, + CUSTOM_ROUTES: null, + CUSTOM_COMPONENTS_RECEIVED: null +}); + +export const GET_LAYOUTS_URL = BASE_URL + '/layouts'; diff --git a/src/app/configurableViews/ConfigurableViewManager.js b/src/app/configurableViews/ConfigurableViewManager.js new file mode 100644 index 0000000..71cc6cf --- /dev/null +++ b/src/app/configurableViews/ConfigurableViewManager.js @@ -0,0 +1,27 @@ +import React from 'react'; +import { + Route +} from 'react-router-dom'; +import { fetchConfigurableViewRequest } from 'app/networking/NetworkCalls'; + +export function getConfigurableRoutes(config, components) { + let routes = []; + if (config && Object.keys(config).length > 0 && components && Object.keys(components).length > 0) { + config.layouts.forEach( (viewConfig) => { + let ConfigurableView = components[viewConfig.viewType]; + if (ConfigurableView) { + routes.push( + <Route key={viewConfig.id} path={`/${viewConfig.id}`} render={ () => { + return ( + <ConfigurableView + config={ viewConfig } + networkAPI={ fetchConfigurableViewRequest }/> + ); + }}/> + ); + } + }); + } + + return routes; +} diff --git a/src/app/configurableViews/ConfigurableViewReducer.js b/src/app/configurableViews/ConfigurableViewReducer.js new file mode 100644 index 0000000..9a5eee0 --- /dev/null +++ b/src/app/configurableViews/ConfigurableViewReducer.js @@ -0,0 +1,26 @@ +import { + configurableViewsActionTypes +} from './ConfigurableViewConstants.js'; + +export default (state = {}, action) => { + let data = action.data; + switch (action.type) { + case configurableViewsActionTypes.CONFIGURABLE_VIEWS_CONFIG_RECEIVED: + return { + ...state, + configurableViewsConfig: data + }; + case configurableViewsActionTypes.CUSTOM_COMPONENTS_RECEIVED: + return { + ...state, + customComponents: data + }; + case configurableViewsActionTypes.CUSTOM_ROUTES: + return { + ...state, + customRoutes: data + }; + } + + return state; +}; diff --git a/src/app/configurableViews/index.js b/src/app/configurableViews/index.js new file mode 100644 index 0000000..3490fef --- /dev/null +++ b/src/app/configurableViews/index.js @@ -0,0 +1,27 @@ +/* + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017-2018 Amdocs + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ +// Import section (used as anchor to add extension imports) +export function getConfiguredComponentList() { + let components = {}; + + // Components section (used as an anchor to add extension components) + return components; +} diff --git a/src/app/networking/NetworkCalls.js b/src/app/networking/NetworkCalls.js index 63c08ed..e391391 100644 --- a/src/app/networking/NetworkCalls.js +++ b/src/app/networking/NetworkCalls.js @@ -32,6 +32,16 @@ function fetchRequest(URL, POST, POST_HEADER, BODY) { ); } +const fetchConfigurableViewRequest = (queryData) => { + const URL = `${BASE_URL}${queryData.api}`; + return fetch(URL, { + credentials: 'same-origin', + method: queryData.method, + headers: queryData.headers, + body: JSON.stringify(queryData.componentDataDescriptor) + }); +}; + function fetchRequestObj(URL, POST, POST_HEADER, BODY) { return fetch(URL, { credentials: 'same-origin', @@ -72,25 +82,6 @@ module.exports = { fetchRequest: fetchRequest, fetchRequestObj: fetchRequestObj, getRequest: getRequest, + fetchConfigurableViewRequest: fetchConfigurableViewRequest, genericRequest: genericRequest }; - - - - - - - - - - - - - - - - - - - - diff --git a/src/app/tierSupport/TierSupportActions.js b/src/app/tierSupport/TierSupportActions.js index 08e4e30..afb37be 100644 --- a/src/app/tierSupport/TierSupportActions.js +++ b/src/app/tierSupport/TierSupportActions.js @@ -18,24 +18,13 @@ * limitations under the License. * ============LICENSE_END========================================================= */ -import {tierSupportActionTypes, - TS_BACKEND_SEARCH_SELECTED_NODE_URL} from 'app/tierSupport/TierSupportConstants.js'; import { - POST, - POST_HEADER, - ERROR_RETRIEVING_DATA, - NO_RESULTS_FOUND -} from 'app/networking/NetworkConstants.js'; -import networkCall from 'app/networking/NetworkCalls.js'; + tierSupportActionTypes +} from 'app/tierSupport/TierSupportConstants.js'; import { getSetGlobalMessageEvent, getClearGlobalMessageEvent } from 'app/globalInlineMessageBar/GlobalInlineMessageBarActions.js'; -import { - STATUS_CODE_204_NO_CONTENT, - STATUS_CODE_3XX_REDIRECTION, - STATUS_CODE_5XX_SERVER_ERROR -} from 'utils/GlobalConstants.js'; function createOnNodeDetailsChangeEvent(newDetails) { return { @@ -76,118 +65,12 @@ export function onNodeMenuChange(selectedMenu) { }; } -function createNodeDetailsFoundEvent(nodeDetails) { - return { - type: tierSupportActionTypes.TS_NODE_SEARCH_RESULTS, - data: nodeDetails - }; -} - -function createSelectedNodeDetails(nodeDetails) { - var selectedNodeDetail; - for(let i = 0; i < nodeDetails.nodes.length; i++) { - if(nodeDetails.nodes[i].nodeMeta.className === 'selectedSearchedNodeClass') { - selectedNodeDetail = nodeDetails.nodes[i]; - break; - } - } - return { - type: tierSupportActionTypes.TS_GRAPH_NODE_SELECTED, - data: selectedNodeDetail - }; -} - -function noNodeDetailsFoundEvent(errorText) { - return { - type: tierSupportActionTypes.TS_NODE_SEARCH_NO_RESULTS, - data: {errorMsg: errorText} - }; -} - -function getInvalidSelectedNodeSearchEvent(errorText) { - return { - type: tierSupportActionTypes.TIER_SUPPORT_NETWORK_ERROR, - data: {value: errorText, errorMsg: ERROR_RETRIEVING_DATA} - }; -} - export function clearVIData() { return { type: tierSupportActionTypes.TIER_SUPPORT_CLEAR_DATA }; } -function setBusyFeedback(){ - return { - type: tierSupportActionTypes.TIER_SUPPORT_ACTIVATE_BUSY_FEEDBACK - }; -} - -function disableBusyFeedback(){ - return { - type: tierSupportActionTypes.TIER_SUPPORT_DISABLE_BUSY_FEEDBACK - }; -} - -export function fetchSelectedNodeElement(fetchRequestCallback) { - return dispatch => { - return fetchRequestCallback().then( - (response) => { - if (response.status === STATUS_CODE_204_NO_CONTENT || response.status >= STATUS_CODE_3XX_REDIRECTION) { - return Promise.reject(new Error(response.status)); - } else { - // assume 200 status - return response.json(); - } - } - ).then( - (responseJson) => { - if (responseJson.nodes.length > 0) { - dispatch(createNodeDetailsFoundEvent(responseJson)); - dispatch(createSelectedNodeDetails(responseJson)); - } else { - dispatch(noNodeDetailsFoundEvent(NO_RESULTS_FOUND)); - } - } - ).then( - () => { - dispatch(disableBusyFeedback()); - } - ).catch( - (errorCode) => { - dispatch(disableBusyFeedback()); - if (errorCode.message >= STATUS_CODE_5XX_SERVER_ERROR) { - dispatch(getInvalidSelectedNodeSearchEvent(ERROR_RETRIEVING_DATA)); - } else { - // TODO - assuming 204 status, but should include additional - // statuses in the future with proper messaging in order to return - // better messaging - dispatch(noNodeDetailsFoundEvent(NO_RESULTS_FOUND)); - } - } - ); - }; -} - -export function querySelectedNodeElement( - searchHashId, selectedNodeFetchRequest) { - let payload = { - hashId: searchHashId - }; - - if (selectedNodeFetchRequest === undefined) { - let postBody = JSON.stringify(payload); - selectedNodeFetchRequest = - () => networkCall.fetchRequestObj(TS_BACKEND_SEARCH_SELECTED_NODE_URL, POST, - POST_HEADER, postBody); - } - - return dispatch => { - dispatch(setBusyFeedback()); - dispatch(fetchSelectedNodeElement(selectedNodeFetchRequest)); - }; -} - export function setNotificationText(msgText, msgSeverity) { if (msgText.length > 0) { return dispatch => { |