diff options
52 files changed, 2268 insertions, 918 deletions
@@ -10,6 +10,3 @@ test/coverage npm-debug.log .vscode scripts/elasticsearch/auditBulkLoad.json -.classpath -.project -.settings/
\ No newline at end of file diff --git a/package.json b/package.json index 211d047..898e70e 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,6 @@ "license": "Apache-2.0", "dependencies": { "collapsible-sliding-panel": "1.0.0", - "configurable-interactive-layout": "^2.2.14", "core-js": "^2.4.0", "crypto-js": "^3.1.9-1", "d3": "^4.12.0", @@ -48,13 +47,11 @@ "redux": "^3.3.1", "redux-form": "^6.2.1", "redux-thunk": "^2.1.0", - "spinner-container": "^1.0.27", "topojson": "^2.2.0", "uuid-js": "^0.7.5", "validator": "^4.3.0", "velocity-react": "^1.4.1", - "vertical-filter-bar": "^1.0.7", - "wolfy87-eventemitter": "^5.2.5" + "vertical-filter-bar": "^1.0.7" }, "devDependencies": { "babel-core": "^6.25.0", @@ -134,6 +131,10 @@ "!**/resources/**", "!**/dist/**", "!**/scripts/**", + "!**/gulpfile.js/**", + "!**/karma.conf.js/**", + "!**/webpack.config.js/**", + "!**/webpack.devConfig.js/**", "!**/tools/**" ] }, diff --git a/resources/images/icons/reports.png b/resources/images/icons/reports.png Binary files differnew file mode 100644 index 0000000..b3d3c4c --- /dev/null +++ b/resources/images/icons/reports.png diff --git a/resources/images/icons/reports.svg b/resources/images/icons/reports.svg new file mode 100644 index 0000000..3ca0bf1 --- /dev/null +++ b/resources/images/icons/reports.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 23 22.89"><title>Asset 5</title><g id="ec3f4fde-b9c3-41a2-ac22-ee6c286fa00e" data-name="Layer 2"><g id="07b86c20-b47a-4f9d-8616-1a3add252348" data-name="Layer 1"><path d="M9.9,3.74A9.25,9.25,0,1,0,19.15,13H9.9Z" fill="none" stroke="#06afd1" stroke-linejoin="round" stroke-width="1.3"/><path d="M13.1.65V9.9h9.25A9.25,9.25,0,0,0,13.1.65Z" fill="none" stroke="#06afd1" stroke-linejoin="round" stroke-width="1.3"/></g></g></svg>
\ No newline at end of file diff --git a/resources/scss/common/_layout.scss b/resources/scss/common/_layout.scss index 6863baf..11265c5 100644 --- a/resources/scss/common/_layout.scss +++ b/resources/scss/common/_layout.scss @@ -38,7 +38,7 @@ .showContainer { } -.spinner-container { +.spin-container { overflow: hidden; position: relative; } @@ -50,6 +50,6 @@ top: 49%; } -.spinner-content { +.spin-content { opacity: 0.2; } diff --git a/resources/views/customComponents.json b/resources/views/customComponents.json new file mode 100644 index 0000000..0d4f101 --- /dev/null +++ b/resources/views/customComponents.json @@ -0,0 +1,2 @@ +[ +] diff --git a/scripts/build/build.sh b/scripts/build/build.sh index 9fd9fd2..7af49cd 100644 --- a/scripts/build/build.sh +++ b/scripts/build/build.sh @@ -4,10 +4,10 @@ # # UpdateFEwithExtensions # -# Description: Update files in the FE code based on the content of the -# extensions. For this to work, extensions are required to fill +# Description: Update files in the FE code based on the content of the +# extensions. For this to work, extensions are required to fill # some files. The current script does not care too much about -# their locations (other than being in the component +# their locations (other than being in the component # nodes-modules) but the file name must match. # ############################################################################### @@ -39,7 +39,7 @@ UpdateFEwithExtensions(){ # Get extensible json additions echo "build.sh --- Appending to resources/views/extensibleViews.json" extConfig=`find node_modules/ -name "extensibleViews.json"` - if [ ! -z "$extConfig" ]; then + if [ ! -z "$extConfig" ]; then jq -n 'input | . +=[inputs]' resources/views/extensibleViews.json $extConfig > tmp mv tmp resources/views/extensibleViews.json fi @@ -58,7 +58,7 @@ UpdateFEwithExtensions(){ # Get overlay json additions echo "build.sh --- Appending to resources/overlays/overlaysDetails.json" extConfig=`find node_modules/ -name "overlaysDetails.json"` - if [ ! -z "$extConfig" ]; then + if [ ! -z "$extConfig" ]; then jq -n 'input | . +=[inputs]' resources/overlays/overlaysDetails.json $extConfig > tmp mv tmp resources/overlays/overlaysDetails.json fi @@ -67,11 +67,11 @@ UpdateFEwithExtensions(){ echo "build.sh --- Appending to resources/scss/customViews.scss" touch resources/scss/customViews.scss extSCSS=`find -name "extensibility.scss"` - for i in $extSCSS; do + for i in $extSCSS; do cat $i >> resources/scss/customViews.scss done - - + + } updateStyle() @@ -101,8 +101,8 @@ updateStyle() if [ -f tmp.style.imports ]; then sed -i /"import 'resources\/scss\/style\.scss';"/d src/app/main.app.jsx fi - - # Update bootstrap + + # Update bootstrap echo "build.sh --- Update resources/scss/bootstrap.scss" bootImports=`find extStyle/ -name "bootstrap.scss"` for i in $bootImports; do @@ -115,6 +115,19 @@ updateStyle() fi } +UpdateFEWithCustomViews(){ + # Append statements to src/app/configurableViews/index.js + echo "build.sh --- Appending to src/app/configurableViews/index.js" + custViewImports=`find -name "confViewIndex"` + for i in $custViewImports; do + cat $i | grep import > tmp.imports + cat $i | grep 'components\[' > tmp.components + + sed -i '/Import section/ r tmp.imports' src/app/configurableViews/index.js + sed -i '/Components section/ r tmp.components' src/app/configurableViews/index.js + done +} + ############################################################################### # # Main @@ -143,4 +156,4 @@ updateStyle # npm run build echo "build.sh --- Running npm run build" -npm run build
\ No newline at end of file +npm run build 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 => { diff --git a/src/generic-components/autoCompleteSearchBar/AutoCompleteSearchBar.jsx b/src/generic-components/autoCompleteSearchBar/AutoCompleteSearchBar.jsx index b7fca67..4f93125 100644 --- a/src/generic-components/autoCompleteSearchBar/AutoCompleteSearchBar.jsx +++ b/src/generic-components/autoCompleteSearchBar/AutoCompleteSearchBar.jsx @@ -94,13 +94,13 @@ export default class AutoCompleteSearchBar extends Component { render() { const { - value, suggestions, - suggestionName, cachedSuggestions, - onInputChange, onInvalidSearch, - onClearSuggestionsTextFieldRequested, - onSuggestionsClearRequested, - dispatchAnalytics - } = this.props; + value, suggestions, + suggestionName, cachedSuggestions, + onInputChange, onInvalidSearch, + onClearSuggestionsTextFieldRequested, + onSuggestionsClearRequested, + dispatchAnalytics + } = this.props; const inputProps = { placeholder: SEARCH_PLACEHOLDER_TEXT, value, @@ -201,7 +201,7 @@ export default class AutoCompleteSearchBar extends Component { rest.className = 'react-autosuggest__suggestions-containerCopy'; } return ( - <div {...rest}> + <div {...rest.containerProps} {...rest}> {children} </div> ); diff --git a/src/generic-components/componentManager/ComponentManagerContainer.jsx b/src/generic-components/componentManager/ComponentManagerContainer.jsx index 02a3eba..cd51d37 100644 --- a/src/generic-components/componentManager/ComponentManagerContainer.jsx +++ b/src/generic-components/componentManager/ComponentManagerContainer.jsx @@ -18,7 +18,7 @@ * limitations under the License. * ============LICENSE_END========================================================= */ -import React, {Component, PropTypes} from 'react'; +import React, {Component} from 'react'; import { PropTypes } from 'prop-types'; import ButtonGroup from 'react-bootstrap/lib/ButtonGroup'; import Button from 'react-bootstrap/lib/Button'; @@ -53,13 +53,13 @@ export default class ComponentManagerContainer extends Component { render() { let { - title, - actions, - children, - showHeader, - showTitle, - showBorder - } = this.props; + title, + actions, + children, + showHeader, + showTitle, + showBorder + } = this.props; let buttons = []; actions.forEach((action) => { switch (action.type) { diff --git a/src/generic-components/graph/Link.jsx b/src/generic-components/graph/Link.jsx index aec33b8..8c87ac5 100644 --- a/src/generic-components/graph/Link.jsx +++ b/src/generic-components/graph/Link.jsx @@ -24,39 +24,39 @@ import { PropTypes } from 'prop-types'; import TempCreateAttributes from './TempCreateAttributes.js'; class Link extends Component { - - static propTypes = { - x1: PropTypes.number, - y1: PropTypes.number, - x2: PropTypes.number, - y2: PropTypes.number, - linkAttributes: PropTypes.object - }; - - static defaultProps = { - x1: 0, - y1: 0, - x2: 0, - y2: 0, - linkAttributes: {} - }; - - render() { - let {x1, y1, x2, y2, linkAttributes} = this.props; - - let combinedAttributes = { - ...linkAttributes, - x1: x1, - y1: y1, - x2: x2, - y2: y2 - }; - - return ( - <line {...combinedAttributes} - style={TempCreateAttributes.createLineStyle()}/> - ); - } + + static propTypes = { + x1: PropTypes.number, + y1: PropTypes.number, + x2: PropTypes.number, + y2: PropTypes.number, + linkAttributes: PropTypes.object + }; + + static defaultProps = { + x1: 0, + y1: 0, + x2: 0, + y2: 0, + linkAttributes: {} + }; + + render() { + let {x1, y1, x2, y2, linkAttributes} = this.props; + + let combinedAttributes = { + ...linkAttributes, + x1: x1, + y1: y1, + x2: x2, + y2: y2 + }; + + return ( + <line {...combinedAttributes} + style={TempCreateAttributes.createLineStyle()}/> + ); + } } export default Link; diff --git a/src/generic-components/graph/Node.jsx b/src/generic-components/graph/Node.jsx index 6de4715..67d954c 100644 --- a/src/generic-components/graph/Node.jsx +++ b/src/generic-components/graph/Node.jsx @@ -22,33 +22,33 @@ import React, {Component} from 'react'; import { PropTypes } from 'prop-types'; class Node extends Component { - - static propTypes = { - x: PropTypes.number, - y: PropTypes.number, - nodeClass: PropTypes.string, - visualElements: PropTypes.array, - meta: PropTypes.object - }; - - static defaultProps = { - x: 0, - y: 0, - nodeClass: '', - visualElements: [], - meta: {} - }; - - render() { - let {x, y, nodeClass, visualElements} = this.props; - let translate = `translate(${x}, ${y})`; - - return ( - <g className={nodeClass} transform={translate}> - {visualElements} - </g> - ); - } + + static propTypes = { + x: PropTypes.number, + y: PropTypes.number, + nodeClass: PropTypes.string, + visualElements: PropTypes.array, + meta: PropTypes.object + }; + + static defaultProps = { + x: 0, + y: 0, + nodeClass: '', + visualElements: [], + meta: {} + }; + + render() { + let {x, y, nodeClass, visualElements} = this.props; + let translate = `translate(${x}, ${y})`; + + return ( + <g className={nodeClass} transform={translate}> + {visualElements} + </g> + ); + } } export default Node; diff --git a/src/generic-components/graph/SVGShape.jsx b/src/generic-components/graph/SVGShape.jsx index b06c46f..8b33598 100644 --- a/src/generic-components/graph/SVGShape.jsx +++ b/src/generic-components/graph/SVGShape.jsx @@ -23,39 +23,39 @@ import { PropTypes } from 'prop-types'; import NodeVisualElementConstants from './NodeVisualElementConstants'; class SVGShape extends Component { - - static propTypes = { - shapeType: PropTypes.string.isRequired, - shapeAttributes: PropTypes.object.isRequired, - shapeClass: PropTypes.object.isRequired, - textValue: PropTypes.string - }; - - static defaultProps = { - shapeType: '', - shapeAttributes: {}, - shapeClass: {}, - textValue: '' - }; - - render() { - let {shapeType, shapeAttributes, shapeClass, textValue} = this.props; - - switch (shapeType) { - case NodeVisualElementConstants.SVG_CIRCLE: - return <circle {...shapeAttributes} className={shapeClass}/>; - - case NodeVisualElementConstants.SVG_LINELINE: - return <line {...shapeAttributes} className={shapeClass}/>; - - case NodeVisualElementConstants.TEXT: - return <text {...shapeAttributes} - className={shapeClass}>{textValue}</text>; - - default: - return undefined; - } - } + + static propTypes = { + shapeType: PropTypes.string.isRequired, + shapeAttributes: PropTypes.object.isRequired, + shapeClass: PropTypes.object.isRequired, + textValue: PropTypes.string + }; + + static defaultProps = { + shapeType: '', + shapeAttributes: {}, + shapeClass: {}, + textValue: '' + }; + + render() { + let {shapeType, shapeAttributes, shapeClass, textValue} = this.props; + + switch (shapeType) { + case NodeVisualElementConstants.SVG_CIRCLE: + return <circle {...shapeAttributes} className={shapeClass}/>; + + case NodeVisualElementConstants.SVG_LINELINE: + return <line {...shapeAttributes} className={shapeClass}/>; + + case NodeVisualElementConstants.TEXT: + return <text {...shapeAttributes} + className={shapeClass}>{textValue}</text>; + + default: + return undefined; + } + } } export default SVGShape; diff --git a/src/generic-components/input/ToggleInput.jsx b/src/generic-components/input/ToggleInput.jsx index 49b0376..f68758a 100644 --- a/src/generic-components/input/ToggleInput.jsx +++ b/src/generic-components/input/ToggleInput.jsx @@ -23,54 +23,54 @@ import { PropTypes } from 'prop-types'; export default class ToggleInput extends React.Component { - - static propTypes = { - label: PropTypes.node, - value: PropTypes.bool, - onChange: PropTypes.func, - disabled: PropTypes.bool - } - - static defaultProps = { - value: false, - label: '' - } - - state = { - value: this.props.value - } - - status() { - return this.state.value ? 'on' : 'off'; - } - - render() { - let {label, disabled} = this.props; - let checked = this.status() === 'on'; - return ( - <div className='toggle-input-wrapper form-group' - onClick={!disabled && this.click}> - <div className='toggle-input-label'>{label}</div> - <div className='toggle-switch'> - <input className='toggle toggle-round-flat' type='checkbox' - checked={checked} readOnly/> - <label></label> - </div> - </div> - ); - } - - click = () => { - let value = !this.state.value; - this.setState({value}); - - let onChange = this.props.onChange; - if (onChange) { - onChange(value); - } - } - - getValue() { - return this.state.value; - } + + static propTypes = { + label: PropTypes.node, + value: PropTypes.bool, + onChange: PropTypes.func, + disabled: PropTypes.bool + } + + static defaultProps = { + value: false, + label: '' + } + + state = { + value: this.props.value + } + + status() { + return this.state.value ? 'on' : 'off'; + } + + render() { + let {label, disabled} = this.props; + let checked = this.status() === 'on'; + return ( + <div className='toggle-input-wrapper form-group' + onClick={!disabled && this.click}> + <div className='toggle-input-label'>{label}</div> + <div className='toggle-switch'> + <input className='toggle toggle-round-flat' type='checkbox' + checked={checked} readOnly/> + <label></label> + </div> + </div> + ); + } + + click = () => { + let value = !this.state.value; + this.setState({value}); + + let onChange = this.props.onChange; + if (onChange) { + onChange(value); + } + } + + getValue() { + return this.state.value; + } } diff --git a/src/generic-components/input/inputOptions/InputOptions.jsx b/src/generic-components/input/inputOptions/InputOptions.jsx index bb9d777..bf17df1 100644 --- a/src/generic-components/input/inputOptions/InputOptions.jsx +++ b/src/generic-components/input/inputOptions/InputOptions.jsx @@ -27,215 +27,215 @@ import Select from 'generic-components/input/SelectInput.jsx'; export const other = {OTHER: 'Other'}; class InputOptions extends React.Component { - - static propTypes = { - values: PropTypes.arrayOf(PropTypes.shape({ - enum: PropTypes.string, - title: PropTypes.string - })), - isEnabledOther: PropTypes.bool, - title: PropTypes.string, - selectedValue: PropTypes.string, - multiSelectedEnum: PropTypes.array, - selectedEnum: PropTypes.string, - otherValue: PropTypes.string, - onEnumChange: PropTypes.func, - onOtherChange: PropTypes.func, - isRequired: PropTypes.bool, - isMultiSelect: PropTypes.bool - }; - - - static contextTypes = { - isReadOnlyMode: PropTypes.bool - }; - - state = { - otherInputDisabled: !this.props.otherValue - }; - - oldProps = { - selectedEnum: '', - otherValue: '', - multiSelectedEnum: [] - }; - - render() { - let {label, isRequired, values, otherValue, onOtherChange, isMultiSelect, onBlur, multiSelectedEnum, selectedEnum, hasError, validations, children} = this.props; - - let currentMultiSelectedEnum = []; - let currentSelectedEnum = ''; - let {otherInputDisabled} = this.state; - if (isMultiSelect) { - currentMultiSelectedEnum = multiSelectedEnum; - if (!otherInputDisabled) { - currentSelectedEnum = - multiSelectedEnum ? multiSelectedEnum.toString() : undefined; - } - } - else { - currentSelectedEnum = selectedEnum; - } - - let isReadOnlyMode = this.context.isReadOnlyMode; - - return ( - <div - className={classNames('form-group', {'required' : validations.required , 'has-error' : hasError})}> - <label className='control-label'>{label}</label> - {isMultiSelect && otherInputDisabled ? - <Select - ref='_myInput' - value={currentMultiSelectedEnum} - className='options-input' - clearable={false} - required={isRequired} - disabled={isReadOnlyMode || Boolean(this.props.disabled)} - onBlur={() => onBlur()} - onMultiSelectChanged={value => this.multiSelectEnumChanged(value)} - options={this.renderMultiSelectOptions(values)} - multi/> : - <div className={classNames('input-options',{'has-error' : hasError})}> - <select - ref={'_myInput'} - label={label} - className='form-control input-options-select' - value={currentSelectedEnum} - style={{'width' : otherInputDisabled ? '100%' : '95px'}} - onBlur={() => onBlur()} - disabled={isReadOnlyMode || Boolean(this.props.disabled)} - onChange={ value => this.enumChanged(value)} - type='select'> - {values && - values.length && - values.map(val => this.renderOptions(val))} - {onOtherChange && <option key='other' - value={other.OTHER}>{i18n( - other.OTHER)}</option>} - {children} - </select> - - {!otherInputDisabled && <div className='input-options-separator'/>} - <input - className='form-control input-options-other' - placeholder={i18n('other')} - ref='_otherValue' - style={{'display' : otherInputDisabled ? 'none' : 'block'}} - disabled={isReadOnlyMode || Boolean(this.props.disabled)} - value={otherValue || ''} - onBlur={() => onBlur()} - onChange={() => this.changedOtherInput()}/> - </div> - } - </div> - ); - } - - renderOptions(val) { - return ( - <option key={val.enum} value={val.enum}>{val.title}</option> - ); - } - - - renderMultiSelectOptions(values) { - let {onOtherChange} = this.props; - let optionsList = []; - if (onOtherChange) { - optionsList = values.map(option => { - return { - label: option.title, - value: option.enum, - }; - }).concat([{ - label: i18n(other.OTHER), - value: i18n(other.OTHER), - }]); - } - else { - optionsList = values.map(option => { - return { - label: option.title, - value: option.enum, - }; - }); - } - if (optionsList.length > 0 && optionsList[0].value === '') { - optionsList.shift(); - } - return optionsList; - } - - getValue() { - let res = ''; - let {isMultiSelect} = this.props; - let {otherInputDisabled} = this.state; - - if (otherInputDisabled) { - res = - isMultiSelect - ? this.refs._myInput.getValue() - : this.refs._myInput.value; - } else { - res = this.refs._otherValue.value; - } - return res; - } - - enumChanged() { - let enumValue = this.refs._myInput.value; - let {onEnumChange, isMultiSelect, onChange} = this.props; - this.setState({ - otherInputDisabled: enumValue !== other.OTHER - }); - if (onEnumChange) { - onEnumChange(isMultiSelect ? [enumValue] : enumValue); - } - - if (onChange) { - onChange(enumValue); - } - - } - - multiSelectEnumChanged(enumValue) { - let {onEnumChange} = this.props; - let selectedValues = enumValue.map(enumVal => { - return enumVal.value; - }); - - if (this.state.otherInputDisabled === false) { - selectedValues.shift(); - } - else if (selectedValues.includes(i18n(other.OTHER))) { - selectedValues = [i18n(other.OTHER)]; - } - - this.setState({ - otherInputDisabled: !selectedValues.includes(i18n(other.OTHER)) - }); - onEnumChange(selectedValues); - } - - changedOtherInput() { - let {onOtherChange} = this.props; - onOtherChange(this.refs._otherValue.value); - } - - componentDidUpdate() { - let {otherValue, selectedEnum, onInputChange, multiSelectedEnum} = this.props; - if (this.oldProps.otherValue !== otherValue - || this.oldProps.selectedEnum !== selectedEnum - || this.oldProps.multiSelectedEnum !== multiSelectedEnum) { - this.oldProps = { - otherValue, - selectedEnum, - multiSelectedEnum - }; - onInputChange(); - } - } - + + static propTypes = { + values: PropTypes.arrayOf(PropTypes.shape({ + enum: PropTypes.string, + title: PropTypes.string + })), + isEnabledOther: PropTypes.bool, + title: PropTypes.string, + selectedValue: PropTypes.string, + multiSelectedEnum: PropTypes.array, + selectedEnum: PropTypes.string, + otherValue: PropTypes.string, + onEnumChange: PropTypes.func, + onOtherChange: PropTypes.func, + isRequired: PropTypes.bool, + isMultiSelect: PropTypes.bool + }; + + + static contextTypes = { + isReadOnlyMode: PropTypes.bool + }; + + state = { + otherInputDisabled: !this.props.otherValue + }; + + oldProps = { + selectedEnum: '', + otherValue: '', + multiSelectedEnum: [] + }; + + render() { + let {label, isRequired, values, otherValue, onOtherChange, isMultiSelect, onBlur, multiSelectedEnum, selectedEnum, hasError, validations, children} = this.props; + + let currentMultiSelectedEnum = []; + let currentSelectedEnum = ''; + let {otherInputDisabled} = this.state; + if (isMultiSelect) { + currentMultiSelectedEnum = multiSelectedEnum; + if (!otherInputDisabled) { + currentSelectedEnum = + multiSelectedEnum ? multiSelectedEnum.toString() : undefined; + } + } + else { + currentSelectedEnum = selectedEnum; + } + + let isReadOnlyMode = this.context.isReadOnlyMode; + + return ( + <div + className={classNames('form-group', {'required' : validations.required , 'has-error' : hasError})}> + <label className='control-label'>{label}</label> + {isMultiSelect && otherInputDisabled ? + <Select + ref='_myInput' + value={currentMultiSelectedEnum} + className='options-input' + clearable={false} + required={isRequired} + disabled={isReadOnlyMode || Boolean(this.props.disabled)} + onBlur={() => onBlur()} + onMultiSelectChanged={value => this.multiSelectEnumChanged(value)} + options={this.renderMultiSelectOptions(values)} + multi/> : + <div className={classNames('input-options',{'has-error' : hasError})}> + <select + ref={'_myInput'} + label={label} + className='form-control input-options-select' + value={currentSelectedEnum} + style={{'width' : otherInputDisabled ? '100%' : '95px'}} + onBlur={() => onBlur()} + disabled={isReadOnlyMode || Boolean(this.props.disabled)} + onChange={ value => this.enumChanged(value)} + type='select'> + {values && + values.length && + values.map(val => this.renderOptions(val))} + {onOtherChange && <option key='other' + value={other.OTHER}>{i18n( + other.OTHER)}</option>} + {children} + </select> + + {!otherInputDisabled && <div className='input-options-separator'/>} + <input + className='form-control input-options-other' + placeholder={i18n('other')} + ref='_otherValue' + style={{'display' : otherInputDisabled ? 'none' : 'block'}} + disabled={isReadOnlyMode || Boolean(this.props.disabled)} + value={otherValue || ''} + onBlur={() => onBlur()} + onChange={() => this.changedOtherInput()}/> + </div> + } + </div> + ); + } + + renderOptions(val) { + return ( + <option key={val.enum} value={val.enum}>{val.title}</option> + ); + } + + + renderMultiSelectOptions(values) { + let {onOtherChange} = this.props; + let optionsList = []; + if (onOtherChange) { + optionsList = values.map(option => { + return { + label: option.title, + value: option.enum, + }; + }).concat([{ + label: i18n(other.OTHER), + value: i18n(other.OTHER), + }]); + } + else { + optionsList = values.map(option => { + return { + label: option.title, + value: option.enum, + }; + }); + } + if (optionsList.length > 0 && optionsList[0].value === '') { + optionsList.shift(); + } + return optionsList; + } + + getValue() { + let res = ''; + let {isMultiSelect} = this.props; + let {otherInputDisabled} = this.state; + + if (otherInputDisabled) { + res = + isMultiSelect + ? this.refs._myInput.getValue() + : this.refs._myInput.value; + } else { + res = this.refs._otherValue.value; + } + return res; + } + + enumChanged() { + let enumValue = this.refs._myInput.value; + let {onEnumChange, isMultiSelect, onChange} = this.props; + this.setState({ + otherInputDisabled: enumValue !== other.OTHER + }); + if (onEnumChange) { + onEnumChange(isMultiSelect ? [enumValue] : enumValue); + } + + if (onChange) { + onChange(enumValue); + } + + } + + multiSelectEnumChanged(enumValue) { + let {onEnumChange} = this.props; + let selectedValues = enumValue.map(enumVal => { + return enumVal.value; + }); + + if (this.state.otherInputDisabled === false) { + selectedValues.shift(); + } + else if (selectedValues.includes(i18n(other.OTHER))) { + selectedValues = [i18n(other.OTHER)]; + } + + this.setState({ + otherInputDisabled: !selectedValues.includes(i18n(other.OTHER)) + }); + onEnumChange(selectedValues); + } + + changedOtherInput() { + let {onOtherChange} = this.props; + onOtherChange(this.refs._otherValue.value); + } + + componentDidUpdate() { + let {otherValue, selectedEnum, onInputChange, multiSelectedEnum} = this.props; + if (this.oldProps.otherValue !== otherValue + || this.oldProps.selectedEnum !== selectedEnum + || this.oldProps.multiSelectedEnum !== multiSelectedEnum) { + this.oldProps = { + otherValue, + selectedEnum, + multiSelectedEnum + }; + onInputChange(); + } + } + } export default InputOptions; diff --git a/src/generic-components/map/TopographicMap.jsx b/src/generic-components/map/TopographicMap.jsx index fc0fb64..6da9909 100644 --- a/src/generic-components/map/TopographicMap.jsx +++ b/src/generic-components/map/TopographicMap.jsx @@ -18,7 +18,7 @@ * limitations under the License. * ============LICENSE_END========================================================= */ -import React, {Component, PropTypes} from 'react'; +import React, {Component} from 'react'; import { PropTypes } from 'prop-types'; import {geoAlbersUsa, geoEquirectangular, geoPath} from 'd3-geo'; import {feature, mesh} from 'topojson'; diff --git a/src/generic-components/notifications/NotificationModal.jsx b/src/generic-components/notifications/NotificationModal.jsx index 1547da3..0e747d5 100644 --- a/src/generic-components/notifications/NotificationModal.jsx +++ b/src/generic-components/notifications/NotificationModal.jsx @@ -19,17 +19,17 @@ * ============LICENSE_END========================================================= */ /** - * NotificationModal options: - * - * show: whether to show notification or not, - * type: the type of the notification. valid values are: 'default', 'error', - * 'warning', 'success' msg: the notification content. could be a string or - * node (React component) title: the notification title timeout: timeout for - * the notification to fade out. if timeout == 0 then the notification is - * rendered until the user closes it - * - */ -import React, {Component, PropTypes} from 'react'; + * NotificationModal options: + * + * show: whether to show notification or not, + * type: the type of the notification. valid values are: 'default', 'error', + * 'warning', 'success' msg: the notification content. could be a string or + * node (React component) title: the notification title timeout: timeout for + * the notification to fade out. if timeout == 0 then the notification is + * rendered until the user closes it + * + */ +import React, {Component} from 'react'; import { PropTypes } from 'prop-types'; import {connect} from 'react-redux'; import Button from 'react-bootstrap/lib/Button.js'; @@ -39,83 +39,83 @@ import Modal from 'generic-components/modal/Modal.jsx'; import NotificationConstants from './NotificationConstants.js'; let typeClass = { - 'default': 'primary', - error: 'danger', - warning: 'warning', - success: 'success' + 'default': 'primary', + error: 'danger', + warning: 'warning', + success: 'success' }; const mapActionsToProps = (dispatch) => { - return { - onCloseClick: () => dispatch({type: NotificationConstants.NOTIFY_CLOSE}) - }; + return { + onCloseClick: () => dispatch({type: NotificationConstants.NOTIFY_CLOSE}) + }; }; const mapStateToProps = ({notification}) => { - - let show = notification !== null && notification.title !== 'Conflict'; - let mapResult = {show}; - if (show) { - mapResult = {show, ...notification}; - } - - return mapResult; + + let show = notification !== null && notification.title !== 'Conflict'; + let mapResult = {show}; + if (show) { + mapResult = {show, ...notification}; + } + + return mapResult; }; class NotificationModal extends Component { - - static propTypes = { - show: PropTypes.bool, - type: PropTypes.oneOf(['default', 'error', 'warning', 'success']), - msg: PropTypes.node, - title: PropTypes.string, - timeout: PropTypes.number - }; - - static defaultProps = { - show: false, - type: 'default', - title: '', - msg: '', - timeout: 0 - }; - - state = {type: undefined}; - - componentWillReceiveProps(nextProps) { - if (this.props.show !== nextProps.show && nextProps.show === false) { - this.setState({type: this.props.type}); - } - else { - this.setState({type: undefined}); - } - } - - componentDidUpdate() { - if (this.props.timeout) { - setTimeout(this.props.onCloseClick, this.props.timeout); - } - } - - render() { - let {title, type, msg, show} = this.props; - if (!show) { - type = this.state.type; - } - return ( - <Modal show={this.props.show} - className={`notification-modal ${typeClass[type]}`}> - <Modal.Header> - <Modal.Title>{title}</Modal.Title> - </Modal.Header> - <Modal.Body>{msg}</Modal.Body> - <Modal.Footer> - <Button bsStyle={typeClass[type]} - onClick={this.props.onCloseClick}>{i18n('OK')}</Button> - </Modal.Footer> - </Modal> - ); - } + + static propTypes = { + show: PropTypes.bool, + type: PropTypes.oneOf(['default', 'error', 'warning', 'success']), + msg: PropTypes.node, + title: PropTypes.string, + timeout: PropTypes.number + }; + + static defaultProps = { + show: false, + type: 'default', + title: '', + msg: '', + timeout: 0 + }; + + state = {type: undefined}; + + componentWillReceiveProps(nextProps) { + if (this.props.show !== nextProps.show && nextProps.show === false) { + this.setState({type: this.props.type}); + } + else { + this.setState({type: undefined}); + } + } + + componentDidUpdate() { + if (this.props.timeout) { + setTimeout(this.props.onCloseClick, this.props.timeout); + } + } + + render() { + let {title, type, msg, show} = this.props; + if (!show) { + type = this.state.type; + } + return ( + <Modal show={this.props.show} + className={`notification-modal ${typeClass[type]}`}> + <Modal.Header> + <Modal.Title>{title}</Modal.Title> + </Modal.Header> + <Modal.Body>{msg}</Modal.Body> + <Modal.Footer> + <Button bsStyle={typeClass[type]} + onClick={this.props.onCloseClick}>{i18n('OK')}</Button> + </Modal.Footer> + </Modal> + ); + } } export default connect(mapStateToProps, mapActionsToProps)(NotificationModal); diff --git a/src/generic-components/panel/SlidePanel.jsx b/src/generic-components/panel/SlidePanel.jsx index 9580837..1550cee 100644 --- a/src/generic-components/panel/SlidePanel.jsx +++ b/src/generic-components/panel/SlidePanel.jsx @@ -24,114 +24,114 @@ import FontAwesome from 'react-fontawesome'; import ReactDOM from 'react-dom'; class SlidePanel extends React.Component { - - static PropTypes = { - direction: PropTypes.string.isRequired, - className: PropTypes.string, - title: PropTypes.string, - isOpen: PropTypes.bool - }; - - static defaultProps = { - title: '', - className: '', - isOpen: true - }; - - state = { - isOpen: this.props.isOpen, - direction: this.props.direction, - width: 0, - arrowWidth: 0 - }; - - componentDidMount() { - this.setSliderPosition(); - } - - componentDidUpdate() { - this.setSliderPosition(); - } - - render() { - - let {children, className} = this.props; - let {isOpen} = this.state; - - return ( - <div className={ `slide-panel ${className}`}> - {this.renderHeader(isOpen)} - <div - className={'slide-panel-content ' + (isOpen ? 'opened' : 'closed')}>{children}</div> - </div> - ); - } - - renderHeader(isOpen) { - let {direction: initialDirection, title} = this.props; - let {direction: currentDirection} = this.state; - - let iconName = currentDirection === - 'right' - ? 'angle-double-right collapse-double-icon' - : 'angle-double-left collapse-double-icon'; - - let awestyle = {padding: '5px'}; - - if (!isOpen && initialDirection === 'right') { - awestyle.marginLeft = '-1px'; - } - return ( - <div className='slide-panel-header'> - { initialDirection === 'left' && - <span className='slide-panel-header-title'>{title}</span>} - <FontAwesome - ref='arrowIcon' - style={awestyle} - onClick={this.handleClick} - className='pull-right' - name={iconName} - size='2x'/> - { initialDirection === 'right' && - <span className='slide-panel-header-title'>{title}</span>} - </div> - ); - } - - handleClick = () => { - this.setState({ - isOpen: !this.state.isOpen, - direction: this.state.direction === 'left' ? 'right' : 'left' - }); - } - - setSliderPosition = () => { - - let el = ReactDOM.findDOMNode(this); - let {style} = el; - - let {direction: initialDirection} = this.props; - let arrowIconSize = Math.floor(ReactDOM.findDOMNode(this.refs.arrowIcon) - .getBoundingClientRect().width) * 2; - if (!this.state.isOpen) { - if (this.props.direction === 'left') { - style.left = arrowIconSize - el.getBoundingClientRect().width + 'px'; - } - if (initialDirection === 'right') { - style.right = arrowIconSize - el.getBoundingClientRect().width + 'px'; - } - } - else { - if (initialDirection === 'left') { - style.left = '0px'; - } - - if (this.props.direction === 'right') { - style.right = '0px'; - } - } - } - + + static PropTypes = { + direction: PropTypes.string.isRequired, + className: PropTypes.string, + title: PropTypes.string, + isOpen: PropTypes.bool + }; + + static defaultProps = { + title: '', + className: '', + isOpen: true + }; + + state = { + isOpen: this.props.isOpen, + direction: this.props.direction, + width: 0, + arrowWidth: 0 + }; + + componentDidMount() { + this.setSliderPosition(); + } + + componentDidUpdate() { + this.setSliderPosition(); + } + + render() { + + let {children, className} = this.props; + let {isOpen} = this.state; + + return ( + <div className={ `slide-panel ${className}`}> + {this.renderHeader(isOpen)} + <div + className={'slide-panel-content ' + (isOpen ? 'opened' : 'closed')}>{children}</div> + </div> + ); + } + + renderHeader(isOpen) { + let {direction: initialDirection, title} = this.props; + let {direction: currentDirection} = this.state; + + let iconName = currentDirection === + 'right' + ? 'angle-double-right collapse-double-icon' + : 'angle-double-left collapse-double-icon'; + + let awestyle = {padding: '5px'}; + + if (!isOpen && initialDirection === 'right') { + awestyle.marginLeft = '-1px'; + } + return ( + <div className='slide-panel-header'> + { initialDirection === 'left' && + <span className='slide-panel-header-title'>{title}</span>} + <FontAwesome + ref='arrowIcon' + style={awestyle} + onClick={this.handleClick} + className='pull-right' + name={iconName} + size='2x'/> + { initialDirection === 'right' && + <span className='slide-panel-header-title'>{title}</span>} + </div> + ); + } + + handleClick = () => { + this.setState({ + isOpen: !this.state.isOpen, + direction: this.state.direction === 'left' ? 'right' : 'left' + }); + } + + setSliderPosition = () => { + + let el = ReactDOM.findDOMNode(this); + let {style} = el; + + let {direction: initialDirection} = this.props; + let arrowIconSize = Math.floor(ReactDOM.findDOMNode(this.refs.arrowIcon) + .getBoundingClientRect().width) * 2; + if (!this.state.isOpen) { + if (this.props.direction === 'left') { + style.left = arrowIconSize - el.getBoundingClientRect().width + 'px'; + } + if (initialDirection === 'right') { + style.right = arrowIconSize - el.getBoundingClientRect().width + 'px'; + } + } + else { + if (initialDirection === 'left') { + style.left = '0px'; + } + + if (this.props.direction === 'right') { + style.right = '0px'; + } + } + } + } export default SlidePanel; diff --git a/src/generic-components/titledContainer/TitledContainer.jsx b/src/generic-components/titledContainer/TitledContainer.jsx index d3d606a..6aa626d 100644 --- a/src/generic-components/titledContainer/TitledContainer.jsx +++ b/src/generic-components/titledContainer/TitledContainer.jsx @@ -18,7 +18,7 @@ * limitations under the License. * ============LICENSE_END========================================================= */ -import React, {Component, PropTypes} from 'react'; +import React, {Component} from 'react'; import { PropTypes } from 'prop-types'; import Button from 'react-bootstrap/lib/Button'; diff --git a/src/generic-components/toggleButtonGroup/ToggleButtonGroup.jsx b/src/generic-components/toggleButtonGroup/ToggleButtonGroup.jsx index 6d57637..0fe8939 100644 --- a/src/generic-components/toggleButtonGroup/ToggleButtonGroup.jsx +++ b/src/generic-components/toggleButtonGroup/ToggleButtonGroup.jsx @@ -18,7 +18,7 @@ * limitations under the License. * ============LICENSE_END========================================================= */ -import React, {Component, PropTypes} from 'react'; +import React, {Component} from 'react'; import { PropTypes } from 'prop-types'; import {connect} from 'react-redux'; @@ -28,49 +28,49 @@ import Button from 'react-bootstrap/lib/Button.js'; import ToggleButtonGroupActions from 'generic-components/toggleButtonGroup/ToggleButtonGroupActions.js'; let mapActionToProps = (dispatch) => { - return { - onButtonToggle: (buttonName) => { - dispatch(ToggleButtonGroupActions.onToggle({button: buttonName})); - } - }; + return { + onButtonToggle: (buttonName) => { + dispatch(ToggleButtonGroupActions.onToggle({button: buttonName})); + } + }; }; let mapStateToProps = ({toggleButtonGroupData}) => { - - let {selectedButton} = toggleButtonGroupData; - - return { - selectedButton - }; + + let {selectedButton} = toggleButtonGroupData; + + return { + selectedButton + }; }; class ToggleButtonGroup extends Component { - - static propTypes = { - buttonDefinitions: PropTypes.object.isRequired - }; - - onButtonSelect(buttonName) { - this.props.onButtonToggle(buttonName); - } - - render() { - let {selectedButton, buttonDefinitions} = this.props; - let buttonListElements = []; - Object.keys(buttonDefinitions).map(function (item) { - buttonListElements.push( - <Button id={item} active={selectedButton === item ? true : false} - onClick={() => this.onButtonSelect(item)}> - <i className={buttonDefinitions[item]} aria-hidden='true'></i> - </Button> - ); - }.bind(this)); - - return ( - <ButtonGroup bsClass='btn-group displayOptionButtons'> - {buttonListElements} - </ButtonGroup> - ); - } + + static propTypes = { + buttonDefinitions: PropTypes.object.isRequired + }; + + onButtonSelect(buttonName) { + this.props.onButtonToggle(buttonName); + } + + render() { + let {selectedButton, buttonDefinitions} = this.props; + let buttonListElements = []; + Object.keys(buttonDefinitions).map(function (item) { + buttonListElements.push( + <Button id={item} active={selectedButton === item ? true : false} + onClick={() => this.onButtonSelect(item)}> + <i className={buttonDefinitions[item]} aria-hidden='true'></i> + </Button> + ); + }.bind(this)); + + return ( + <ButtonGroup bsClass='btn-group displayOptionButtons'> + {buttonListElements} + </ButtonGroup> + ); + } } export default connect(mapStateToProps, mapActionToProps)(ToggleButtonGroup); diff --git a/src/utils/Crypto.js b/src/utils/Crypto.js index c84ca76..91b6799 100644 --- a/src/utils/Crypto.js +++ b/src/utils/Crypto.js @@ -23,7 +23,7 @@ */ import CryptoJS from 'crypto-js'; -var key = 'key2017'; +const key = 'key2017'; function encrypt(text) { var encrypted = CryptoJS.AES.encrypt(text, key); @@ -35,8 +35,17 @@ function decrypt(text) { return decrypted.toString(CryptoJS.enc.Utf8); } +function encode(phrase) { + return CryptoJS.enc.Utf16.parse(phrase); +} + +function decode(encodedPhrase) { + return CryptoJS.enc.Utf16.stringify(encodedPhrase); +} module.exports = { encrypt: encrypt, - decrypt: decrypt + decrypt: decrypt, + encode: encode, + decode: decode }; diff --git a/src/utils/SpinnerContainer.jsx b/src/utils/SpinnerContainer.jsx index 9bcf769..17e6e58 100644 --- a/src/utils/SpinnerContainer.jsx +++ b/src/utils/SpinnerContainer.jsx @@ -26,9 +26,9 @@ import {COLOR_BLUE} from 'utils/GlobalConstants.js'; class SpinnerContainer extends Component { render() { // if loading, show content as busy (ex: grey out) - const spinnerContentClass = this.props.loading ? 'spinner-content' : ''; + const spinnerContentClass = this.props.loading ? 'spin-content' : ''; return ( - <div className='spinner-container'> + <div className='spin-container'> <div className='spinner'> <ClipLoader color={COLOR_BLUE} loading={this.props.loading} /> </div> diff --git a/test/autoCompleteSearchBar/AutoCompleteSearchBar.test.js b/test/autoCompleteSearchBar/AutoCompleteSearchBar.test.js new file mode 100644 index 0000000..7ba3d11 --- /dev/null +++ b/test/autoCompleteSearchBar/AutoCompleteSearchBar.test.js @@ -0,0 +1,40 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import {Provider} from 'react-redux' +import configureStore from 'redux-mock-store'; + +import AutoCompleteSearchBar from 'generic-components/autoCompleteSearchBar/AutoCompleteSearchBar.jsx'; + +describe('AutoCompleteSearchBarTests', () => { + const suggestions = [ + { + text: 'Apple' + }, + { + text: 'Orange' + }, + { + text: 'Banana' + } + ]; + const initialState = { + globalAutoCompleteSearchBarReducer: { + value: '', + suggestions: [], + cachedSuggestions: [], + suggestionName: '' + } + }; + const mockStore = configureStore(); + let store, wrapper; + + beforeEach( () => { + store = mockStore(initialState); + wrapper = shallow(<Provider store={store}><AutoCompleteSearchBar /></Provider>); + }) + + it('render search bar - visible', () => { + expect(wrapper).toHaveLength(1); // ensure the message bar is mounted + expect(wrapper.find(AutoCompleteSearchBar)).toHaveLength(1); // ensure the InlineMessage is mounted + }); +}) diff --git a/test/configurableViews/ConfigurableViewActions.test.js b/test/configurableViews/ConfigurableViewActions.test.js new file mode 100644 index 0000000..9d7bc9e --- /dev/null +++ b/test/configurableViews/ConfigurableViewActions.test.js @@ -0,0 +1,208 @@ +import configureStore from 'redux-mock-store'; +import thunk from 'redux-thunk' +import fetchMock from 'fetch-mock'; +import { + configurableViewsActionTypes +} from 'app/configurableViews/ConfigurableViewConstants.js'; +import { + newCustomComponentsEvent, + setCustomRoutes, + getConfigurableViewConfigs +} from 'app/configurableViews/ConfigurableViewActions.js' + + +describe('ConfigurableViewActionTests', () => { + const sampleConfig = { + "id": "aggregateReport", + "title": "Aggregate Report", + "iconURL": "resources/images/sampleAggReportIcon.svg", + "iconHoverURL": "resources/images/sampleAggReportIconHover.svg", + "viewType": "ConfigurableCardView", + "layout": { + "draggable": true, + "resizable": true, + "rowHeight": 100, + "cardMargin": [ + 20, + 20 + ], + "cardPadding": [ + 20, + 20 + ], + "breakpoints": [ + { + "id": "lg", + "col": 12, + "width": 1400 + }, + { + "id": "md", + "col": 8, + "width": 1200 + }, + { + "id": "sm", + "col": 6, + "width": 1024 + } + ] + }, + "components": [ + { + "id": "visualization1", + "title": "Total VNFs", + "queryData": { + "eventId": "visualization1", + "api": "/get-component-data", + "method": "POST", + "headers": { + "accept": "application/json" + }, + "componentDataDescriptor": { + "index": "aggregate_generic-vnf_index", + "queryType": "aggregation", + "query": { + "filter": {}, + "queries": [], + "aggregations": [ + { + "name": "prov-status", + "aggregation": { + "group-by": { + "field": "prov-status", + "size": 0 + } + } + }, + { + "name": "orchestration-status", + "aggregation": { + "group-by": { + "field": "orchestration-status", + "size": 0 + } + } + }, + { + "name": "nf-type", + "aggregation": { + "group-by": { + "field": "nf-type", + "size": 0 + } + } + }, + { + "name": "nf-role", + "aggregation": { + "group-by": { + "field": "nf-role", + "size": 0 + } + } + } + ] + }, + "responseTransformation": { + "type": "count", + "spec": { + "countType": "total", + "countResponseLabel": "display" + } + } + } + }, + "containerData": { + "containerType": "NetworkQueryCard", + "visualizationType": "text", + "visualizationProp": { + "display": "", + "style": { + "textAlign": "center", + "fontSize": "50px", + "fontWeight": "bold", + "paddingTop": "50px" + } + }, + "breakpoints": { + "lg": { + "w": 2, + "h": 2, + "x": 0, + "y": 0 + }, + "md": { + "w": 2, + "h": 2, + "x": 0, + "y": 0 + }, + "sm": { + "w": 6, + "h": 2, + "x": 0, + "y": 0 + } + } + } + } + ] + }; + + it('newCustomComponentsEvent', () => { + const components = [ + { + compId: 'someId', + compName: 'Some Name' + } + ]; + const middlewares = [thunk]; + const mockStore = configureStore(middlewares); + const store = mockStore({}); + store.dispatch(newCustomComponentsEvent(components)); + const actions = store.getActions(); + expect(actions).toEqual([{ + type: configurableViewsActionTypes.CUSTOM_COMPONENTS_RECEIVED, + data: components + }]); + }); + + it('setCustomRoutes', () => { + const routes = [ + { + routeName: 'Some Custom Route', + path: 'some/route/path' + } + ]; + const middlewares = [thunk]; + const mockStore = configureStore(middlewares); + const store = mockStore({}); + store.dispatch(setCustomRoutes(routes)); + const actions = store.getActions(); + expect(actions).toEqual([{ + type: configurableViewsActionTypes.CUSTOM_ROUTES, + data: routes + }]); + }); + + it('getConfigurableViewConfigs', () => { + const middlewares = [thunk]; + const mockStore = configureStore(middlewares); + const store = mockStore({}); + const expectedActions = [ + { + type: configurableViewsActionTypes.CONFIGURABLE_VIEWS_CONFIG_RECEIVED, + data: sampleConfig + } + ]; + fetchMock.mock('*', sampleConfig); + + return store.dispatch(getConfigurableViewConfigs()) + .then( () => { + const actions = store.getActions(); + expect(actions).toEqual(expectedActions); + fetchMock.restore(); + }); + }); +}) diff --git a/test/configurableViews/ConfigurableViewReducer.test.js b/test/configurableViews/ConfigurableViewReducer.test.js new file mode 100644 index 0000000..0c5c46e --- /dev/null +++ b/test/configurableViews/ConfigurableViewReducer.test.js @@ -0,0 +1,54 @@ +import { + configurableViewsActionTypes +} from 'app/configurableViews/ConfigurableViewConstants.js'; +import ConfigurableViewReducer from 'app/configurableViews/ConfigurableViewReducer.js' +describe('ConfigurableViewsReducerTests', () => { + it('Action Type: CONFIGURABLE_VIEWS_CONFIG_RECEIVED', () => { + const data = { + viewId: 'someViewId', + viewName: 'Some View Name', + viewRoute: 'some/view/route' + }; + const action = { + type: configurableViewsActionTypes.CONFIGURABLE_VIEWS_CONFIG_RECEIVED, + data: data + }; + let state = {}; + state = ConfigurableViewReducer(state, action); + expect(state).toEqual({ + configurableViewsConfig: data + }); + }); + + it('Action Type: CUSTOM_COMPONENTS_RECEIVED', () => { + const data = { + componentName: 'someComponentName', + componentData: { + blah: 'blah', + filler: 'filler' + } + }; + const action = { + type: configurableViewsActionTypes.CUSTOM_COMPONENTS_RECEIVED, + data: data + }; + let state = {}; + state = ConfigurableViewReducer(state, action); + expect(state).toEqual({ + customComponents: data + }); + }); + + it('Action Type: CUSTOM_ROUTES', () => { + const data = 'some/custom/route'; + const action = { + type: configurableViewsActionTypes.CUSTOM_ROUTES, + data: data + }; + let state = {}; + state = ConfigurableViewReducer(state, action); + expect(state).toEqual({ + customRoutes: data + }); + }); +}) diff --git a/scripts/test/fileMock.js b/test/fileMock.js index 86059f3..86059f3 100644 --- a/scripts/test/fileMock.js +++ b/test/fileMock.js diff --git a/test/globalAutoCompleteSearchBar/GlobalAutoCompleteSearchBar.test.js b/test/globalAutoCompleteSearchBar/GlobalAutoCompleteSearchBar.test.js new file mode 100644 index 0000000..25676b7 --- /dev/null +++ b/test/globalAutoCompleteSearchBar/GlobalAutoCompleteSearchBar.test.js @@ -0,0 +1,35 @@ +import React from 'react'; +import { mount } from 'enzyme'; +import {Provider} from 'react-redux' +import configureStore from 'redux-mock-store'; + +import GlobalAutoCompleteSearchBar from 'app/globalAutoCompleteSearchBar/GlobalAutoCompleteSearchBar.jsx' +import AutoCompleteSearchBar from 'generic-components/autoCompleteSearchBar/AutoCompleteSearchBar.jsx'; +import { + globalAutoCompleteSearchBarActionTypes +} from 'app/globalAutoCompleteSearchBar/GlobalAutoCompleteSearchBarConstants.js'; + +describe('GlobalAutoCompleteSearchBarTests', () => { + const initValue = 'some random search text'; + const initialState = { + globalAutoCompleteSearchBarReducer: { + value: initValue + } + }; + const mockStore = configureStore(); + let store, wrapper; + + beforeEach( () => { + store = mockStore(initialState); + wrapper = mount(<Provider store={store}><GlobalAutoCompleteSearchBar /></Provider>); + }) + + it('render search bar - visible', () => { + expect(wrapper).toHaveLength(1); // ensure the message bar is mounted + expect(wrapper.find(AutoCompleteSearchBar)).toHaveLength(1); // ensure the InlineMessage is mounted + }); + + it('props assigned properly', () => { + expect(wrapper.find(AutoCompleteSearchBar).props().value).toEqual(initValue); // check that the props match + }) +}) diff --git a/test/globalAutoCompleteSearchBar/GlobalAutoCompleteSearchBarReducer.test.js b/test/globalAutoCompleteSearchBar/GlobalAutoCompleteSearchBarReducer.test.js new file mode 100644 index 0000000..1078df6 --- /dev/null +++ b/test/globalAutoCompleteSearchBar/GlobalAutoCompleteSearchBarReducer.test.js @@ -0,0 +1,154 @@ +import i18n from 'utils/i18n/i18n'; +import GlobalAutoCompleteSearchBarReducer from 'app/globalAutoCompleteSearchBar/GlobalAutoCompleteSearchBarReducer.js'; +import { + globalAutoCompleteSearchBarActionTypes, + NO_MATCHES_FOUND +} from 'app/globalAutoCompleteSearchBar/GlobalAutoCompleteSearchBarConstants.js'; +import { + MESSAGE_LEVEL_WARNING, + MESSAGE_LEVEL_DANGER +} from 'utils/GlobalConstants.js'; + +describe('GlobalAutoCompleteSearchBarReducerTests', () => { + it('Action Type: SUGGESTION_FOUND', () => { + const suggestions = [ + { + entityType: 'some entity type', + value: 'selected value' + }, + { + entityType: 'some entity type', + value: 'other selected value' + } + ]; + const errMsg = 'some error message'; + const action = { + type: globalAutoCompleteSearchBarActionTypes.SUGGESTION_FOUND, + data: { + suggestions: suggestions, + errorMsg: errMsg + } + }; + let state = {}; + state = GlobalAutoCompleteSearchBarReducer(state, action); + expect(state).toEqual({ + suggestions: suggestions, + cachedSuggestions: suggestions, + feedbackMsgText: errMsg, + feedbackMsgSeverity: MESSAGE_LEVEL_DANGER + }); + }); + + it('Action Type: SUGGESTION_NOT_FOUND', () => { + const action = { + type: globalAutoCompleteSearchBarActionTypes.SUGGESTION_NOT_FOUND, + }; + let state = {}; + state = GlobalAutoCompleteSearchBarReducer(state, action); + expect(state).toEqual({ + suggestions: [{ text: i18n(NO_MATCHES_FOUND)}], + cachedSuggestions: [{ entityType: i18n(NO_MATCHES_FOUND)}], + feedbackMsgText: '', + feedbackMsgSeverity: '' + }); + }); + + it('Action Type: CLEAR_SUGGESTIONS_TEXT_FIELD', () => { + const action = { + type: globalAutoCompleteSearchBarActionTypes.CLEAR_SUGGESTIONS_TEXT_FIELD, + }; + let state = {}; + state = GlobalAutoCompleteSearchBarReducer(state, action); + expect(state).toEqual({ + suggestions: [], + cachedSuggestions: [], + value: '', + feedbackMsgText: '', + feedbackMsgSeverity: '', + clearSearchText: false + }); + }); + + it('Action Type: CLEAR_SUGGESTIONS', () => { + const action = { + type: globalAutoCompleteSearchBarActionTypes.CLEAR_SUGGESTIONS, + }; + let state = {}; + state = GlobalAutoCompleteSearchBarReducer(state, action); + expect(state).toEqual({ + suggestions: [] + }); + }); + + it('Action Type: SUGGESTION_CHANGED', () => { + const suggestionText = 'some suggestion text'; + const action = { + type: globalAutoCompleteSearchBarActionTypes.SUGGESTION_CHANGED, + data: suggestionText + }; + let state = {}; + state = GlobalAutoCompleteSearchBarReducer(state, action); + expect(state).toEqual({ + value: suggestionText, + feedbackMsgText: '', + feedbackMsgSeverity: '' + }); + }); + + it('Action Type: SUGGESTION_CLICKED', () => { + const suggestion = { + entityType: 'some entity type', + value: 'selected value' + }; + const action = { + type: globalAutoCompleteSearchBarActionTypes.SUGGESTION_CLICKED, + data: { + selectedSuggestion: suggestion + } + }; + let state = {}; + state = GlobalAutoCompleteSearchBarReducer(state, action); + expect(state).toEqual({ + selectedSuggestion: suggestion, + performPrepareVisualization: true, + feedbackMsgText: '', + feedbackMsgSeverity: '' + }); + }); + + it('Action Type: NETWORK_ERROR', () => { + const errMsg = 'some error message'; + const action = { + type: globalAutoCompleteSearchBarActionTypes.NETWORK_ERROR, + data: { + errorMsg: errMsg + } + }; + let state = {}; + state = GlobalAutoCompleteSearchBarReducer(state, action); + expect(state).toEqual({ + suggestions: [], + cachedSuggestions: [], + feedbackMsgText: errMsg, + feedbackMsgSeverity: MESSAGE_LEVEL_DANGER + }); + }); + + it('Action Type: SEARCH_WARNING_EVENT', () => { + const errMsg = 'some error message'; + const action = { + type: globalAutoCompleteSearchBarActionTypes.SEARCH_WARNING_EVENT, + data: { + errorMsg: errMsg + } + }; + let state = {}; + state = GlobalAutoCompleteSearchBarReducer(state, action); + expect(state).toEqual({ + suggestions: [], + cachedSuggestions: [], + feedbackMsgText: errMsg, + feedbackMsgSeverity: MESSAGE_LEVEL_WARNING + }); + }); +}) diff --git a/test/globalInlineMessageBar/GlobalInlineMessageBar.test.js b/test/globalInlineMessageBar/GlobalInlineMessageBar.test.js new file mode 100644 index 0000000..9dc2a28 --- /dev/null +++ b/test/globalInlineMessageBar/GlobalInlineMessageBar.test.js @@ -0,0 +1,37 @@ +import React from 'react'; +import { mount } from 'enzyme'; +import {Provider} from 'react-redux' +import configureStore from 'redux-mock-store'; + +import GlobalInlineMessageBar from 'app/globalInlineMessageBar/GlobalInlineMessageBar.jsx' +import { + MESSAGE_LEVEL_WARNING +} from 'utils/GlobalConstants.js' +import InlineMessage from 'generic-components/InlineMessage/InlineMessage.jsx'; + +describe('GlobalInlineMessageBarTests', () => { + const errMsg = 'some random message'; + const initialState = { + globalInlineMessageBar: { + feedbackMsgText: errMsg, + feedbackMsgSeverity: MESSAGE_LEVEL_WARNING + } + }; + const mockStore = configureStore(); + let store, wrapper; + + beforeEach( () => { + store = mockStore(initialState); + wrapper = mount(<Provider store={store}><GlobalInlineMessageBar /></Provider>); + }) + + it('render message bar - visible', () => { + expect(wrapper).toHaveLength(1); // ensure the message bar is mounted + expect(wrapper.find(InlineMessage)).toHaveLength(1); // ensure the InlineMessage is mounted + }); + + it('props assigned properly', () => { + expect(wrapper.find(InlineMessage).props().level).toEqual(MESSAGE_LEVEL_WARNING); // check that the props match + expect(wrapper.find(InlineMessage).props().messageTxt).toEqual(errMsg); // check that the props match + }) +}) diff --git a/test/globalInlineMessageBar/GlobalInlineMessageBarAction.test.js b/test/globalInlineMessageBar/GlobalInlineMessageBarAction.test.js new file mode 100644 index 0000000..4def5ac --- /dev/null +++ b/test/globalInlineMessageBar/GlobalInlineMessageBarAction.test.js @@ -0,0 +1,42 @@ +import configureStore from 'redux-mock-store'; +import thunk from 'redux-thunk'; + +import { + getSetGlobalMessageEvent, + getClearGlobalMessageEvent +} from 'app/globalInlineMessageBar/GlobalInlineMessageBarActions.js'; +import { + globalInlineMessageBarActionTypes +} from 'app/globalInlineMessageBar/GlobalInlineMessageBarConstants.js'; +import { + MESSAGE_LEVEL_WARNING +} from 'utils/GlobalConstants.js' + +describe('GlobalInlineMessageBarActionTests', () => { + it('getSetGlobalMessageEvent', () => { + const middlewares = [thunk]; + const mockStore = configureStore(middlewares); + const store = mockStore({}); + const msgText = 'some test msg'; + store.dispatch(getSetGlobalMessageEvent(msgText, MESSAGE_LEVEL_WARNING)); + const actions = store.getActions(); + expect(actions).toEqual([{ + type: globalInlineMessageBarActionTypes.SET_GLOBAL_MESSAGE, + data: { + msgText: msgText, + msgSeverity: MESSAGE_LEVEL_WARNING + } + }]); + }); + + it('getClearGlobalMessageEvent', () => { + const middlewares = [thunk]; + const mockStore = configureStore(middlewares); + const store = mockStore({}); + store.dispatch(getClearGlobalMessageEvent()); + const actions = store.getActions(); + expect(actions).toEqual([{ + type: globalInlineMessageBarActionTypes.CLEAR_GLOBAL_MESSAGE + }]); + }); +}) diff --git a/test/globalInlineMessageBar/GlobalInlineMessageBarReducer.test.js b/test/globalInlineMessageBar/GlobalInlineMessageBarReducer.test.js new file mode 100644 index 0000000..62389b4 --- /dev/null +++ b/test/globalInlineMessageBar/GlobalInlineMessageBarReducer.test.js @@ -0,0 +1,40 @@ +import GlobalInlineMessageBarReducer from 'app/globalInlineMessageBar/GlobalInlineMessageBarReducer.js'; +import { + globalInlineMessageBarActionTypes +} from 'app/globalInlineMessageBar/GlobalInlineMessageBarConstants.js'; +import { + MESSAGE_LEVEL_WARNING +} from 'utils/GlobalConstants.js' + +describe('GlobalInlineMessageBarReducerTests', () => { + it('Action Type: SET_GLOBAL_MESSAGE', () => { + const action = { + type: globalInlineMessageBarActionTypes.SET_GLOBAL_MESSAGE, + data: { + msgText: 'some error message here', + msgSeverity: MESSAGE_LEVEL_WARNING + } + }; + let state = {}; + state = GlobalInlineMessageBarReducer(state, action); + expect(state).toEqual({ + feedbackMsgText: action.data.msgText, + feedbackMsgSeverity: action.data.msgSeverity + }); + }); + + it('Action Type: CLEAR_GLOBAL_MESSAGE', () => { + const action = { + type: globalInlineMessageBarActionTypes.CLEAR_GLOBAL_MESSAGE + }; + let state = { + feedbackMsgText: 'some error message here', + feedbackMsgSeverity: MESSAGE_LEVEL_WARNING + }; + state = GlobalInlineMessageBarReducer(state, action); + expect(state).toEqual({ + feedbackMsgText: '', + feedbackMsgSeverity: '' + }); + }); +}) diff --git a/test/input/SelectInput.test.js b/test/input/SelectInput.test.js new file mode 100644 index 0000000..a669361 --- /dev/null +++ b/test/input/SelectInput.test.js @@ -0,0 +1,13 @@ +import React from 'react'; +import { mount } from 'enzyme'; +import Select from 'react-select'; + +import SelectInput from 'generic-components/input/SelectInput.jsx'; + +describe('SelectInput Tests', () => { + it('render select input - visible', () => { + const select = mount( <SelectInput /> ); + expect(select).toHaveLength(1); // ensure the message bar is mounted + expect(select.find(Select)).toHaveLength(1); // ensure the InlineMessage is mounted + }); +}) diff --git a/test/input/ToggleInput.test.js b/test/input/ToggleInput.test.js new file mode 100644 index 0000000..80f0345 --- /dev/null +++ b/test/input/ToggleInput.test.js @@ -0,0 +1,12 @@ +import React from 'react'; +import { mount } from 'enzyme'; + +import ToggleInput from 'generic-components/input/ToggleInput.jsx'; + +describe('ToggleInput Tests', () => { + it('render toggle input - visible', () => { + const toggle = mount( <ToggleInput /> ); + expect(toggle).toHaveLength(1); // ensure the message bar is mounted + expect(toggle.find('input')).toHaveLength(1); // ensure the InlineMessage is mounted + }); +}) diff --git a/scripts/test/setupTests.js b/test/setupTests.js index 2318628..2318628 100644 --- a/scripts/test/setupTests.js +++ b/test/setupTests.js diff --git a/scripts/test/styleMock.js b/test/styleMock.js index f053ebf..f053ebf 100644 --- a/scripts/test/styleMock.js +++ b/test/styleMock.js diff --git a/test/tierSupport/TierSupportActions.test.js b/test/tierSupport/TierSupportActions.test.js new file mode 100644 index 0000000..62485ee --- /dev/null +++ b/test/tierSupport/TierSupportActions.test.js @@ -0,0 +1,177 @@ +import configureStore from 'redux-mock-store'; +import thunk from 'redux-thunk' +import { + onNodeDetailsChange, + splitPaneResize, + onNodeMenuChange, + clearVIData, + setNotificationText +} from 'app/tierSupport/TierSupportActions.js'; +import { + tierSupportActionTypes +} from 'app/tierSupport/TierSupportConstants.js'; +import { + MESSAGE_LEVEL_WARNING +} from 'utils/GlobalConstants.js'; +import { + globalInlineMessageBarActionTypes +} from 'app/globalInlineMessageBar/GlobalInlineMessageBarConstants.js'; + +describe('TierSupportActionTests', () => { + it('onNodeDetailsChange', () => { + const newDetails = { + id: '7352312c7bfa814c3071a803d98c5b670952765974876e55ef954e0f8a930b1c', + itemType: 'complex', + nodeMeta: { + nodeLabel1: 'Artic', + nodeValidated: false, + nodeLocation: 'bottom' + }, + rootNode: false, + index: 2, + }; + const middlewares = [thunk]; + const mockStore = configureStore(middlewares); + const store = mockStore({ tierSupportReducer: {} }); + store.dispatch(onNodeDetailsChange(newDetails)); + const actions = store.getActions(); + expect(actions).toEqual([{ + type: tierSupportActionTypes.TS_GRAPH_NODE_SELECTED, + data: newDetails + }]); + }); + + it('splitPaneResize', () => { + const initialLoad = { + test: 'message' + }; + const middlewares = [thunk]; + const mockStore = configureStore(middlewares); + const store = mockStore({ tierSupportReducer: {} }); + store.dispatch(splitPaneResize(initialLoad)); + const actions = store.getActions(); + expect(actions).toEqual([{ + type: tierSupportActionTypes.SPLIT_PANE_RESIZE, + data: initialLoad + }]); + }); + + it('onNodeMenuChange', () => { + const selectedMenu = { + test: 'menuData' + }; + const middlewares = [thunk]; + const mockStore = configureStore(middlewares); + const store = mockStore({ tierSupportReducer: {} }); + store.dispatch(onNodeMenuChange(selectedMenu)); + const actions = store.getActions(); + expect(actions).toEqual([{ + type: tierSupportActionTypes.TS_GRAPH_NODE_MENU_SELECTED, + data: selectedMenu + }]); + }); + + it('clearVIData', () => { + const middlewares = [thunk]; + const mockStore = configureStore(middlewares); + const store = mockStore({ tierSupportReducer: {} }); + store.dispatch(clearVIData()); + const actions = store.getActions(); + expect(actions).toEqual([{ + type: tierSupportActionTypes.TIER_SUPPORT_CLEAR_DATA + }]); + }); + // + // it('fetchSelectedNodeElement - no results', () => { + // const middlewares = [thunk]; + // const mockStore = configureStore(middlewares); + // const store = mockStore({ tierSupportReducer: {} }); + // const nodes = [ + // { + // id: '7352312c7bfa814c3071a803d98c5b670952765974876e55ef954e0f8a930b1c', + // itemType: 'complex', + // nodeMeta: { + // className: 'selectedSearchedNodeClass', + // nodeLabel1: 'Artic', + // nodeValidated: false, + // nodeLocation: 'bottom' + // }, + // rootNode: false, + // index: 2 + // }, + // { + // id: '3899453d98c5b670952765974876e55ef954e0f8a930b1c', + // itemType: 'generic-vnf', + // nodeMeta: { + // className: 'someOtherClassName', + // nodeLabel1: 'Artic', + // nodeValidated: false, + // nodeLocation: 'bottom' + // }, + // rootNode: false, + // index: 1 + // } + // ]; + // const expectedActions = [ + // { + // type: tierSupportActionTypes.TS_NODE_SEARCH_RESULTS, + // data: { + // nodes: nodes + // } + // }, + // { + // type: tierSupportActionTypes.TS_GRAPH_NODE_SELECTED, + // data: nodes[0] + // } + // ]; + // + // console.log(nodes); + // + // let fetchRequestCallback = () => { + // const results = { + // nodes: nodes + // }; + // let init = { status: 200 }; + // let myBlob = new Blob(); + // let response = new Response(); + // return new Promise((resolve, reject) => { + // resolve(response); + // }); + // }; + // return store.dispatch(fetchSelectedNodeElement(fetchRequestCallback)) + // .then( () => { + // const actions = store.getActions(); + // expect(actions).toEqual(expectedActions); + // }); + // }); + + it('setNotificationText', () => { + const middlewares = [thunk]; + const mockStore = configureStore(middlewares); + const store = mockStore({ tierSupportReducer: {} }); + const msgText = 'some test text'; + const msgSeverity = MESSAGE_LEVEL_WARNING; + store.dispatch(setNotificationText(msgText, msgSeverity)); + const actions = store.getActions(); + expect(actions).toEqual([{ + type: globalInlineMessageBarActionTypes.SET_GLOBAL_MESSAGE, + data: { + msgText: msgText, + msgSeverity: msgSeverity + } + }]); + }); + + it('Clear notification text with setNotificationText', () => { + const middlewares = [thunk]; + const mockStore = configureStore(middlewares); + const store = mockStore({ tierSupportReducer: {} }); + const msgText = ''; + const msgSeverity = MESSAGE_LEVEL_WARNING; + store.dispatch(setNotificationText(msgText, msgSeverity)); + const actions = store.getActions(); + expect(actions).toEqual([{ + type: globalInlineMessageBarActionTypes.CLEAR_GLOBAL_MESSAGE + }]); + }); +}) diff --git a/test/tierSupport/TierSupportReducer.test.js b/test/tierSupport/TierSupportReducer.test.js new file mode 100644 index 0000000..9825a06 --- /dev/null +++ b/test/tierSupport/TierSupportReducer.test.js @@ -0,0 +1,206 @@ +import TierSupportReducer from 'app/tierSupport/TierSupportReducer.js'; +import ForceDirectedGraph from 'generic-components/graph/ForceDirectedGraph.jsx'; +import { + tierSupportActionTypes, + TSUI_GRAPH_MENU_NODE_DETAILS +} from 'app/tierSupport/TierSupportConstants.js'; +import { + MESSAGE_LEVEL_WARNING, + MESSAGE_LEVEL_DANGER +} from 'utils/GlobalConstants.js'; +import { + globalAutoCompleteSearchBarActionTypes +} from 'app/globalAutoCompleteSearchBar/GlobalAutoCompleteSearchBarConstants.js'; + +describe('TierSupportReducerTests', () => { + it('Action Type: TS_NODE_SEARCH_RESULTS', () => { + ForceDirectedGraph.graphCounter = 0; // ensuring counter is at zero after previous tests + const action = { + type: tierSupportActionTypes.TS_NODE_SEARCH_RESULTS, + data: { + nodes: [ + { + nodeMeta: { + searchTarget: true + }, + itemProperties: 'someProperty' + } + ], + links: ['link', 'information'], + graphMeta: { graph: 'meta' } + } + }; + let graphData = ForceDirectedGraph.generateNewProps(action.data.nodes, action.data.links, + action.data.graphMeta); + ForceDirectedGraph.graphCounter = 0; // ensuring counter is at zero after previous statement + let state = {}; + state = TierSupportReducer(state, action); + expect(state.tierSupportReducer).toEqual({ + forceDirectedGraphRawData: graphData, + feedbackMsgText: '', + feedbackMsgSeverity: '' + }); + }); + + it('Action Type: TS_GRAPH_NODE_MENU_SELECTED', () => { + const action = { + type: tierSupportActionTypes.TS_GRAPH_NODE_MENU_SELECTED, + data: { + attr1: 'someValue', + attr2: 'someOterValue' + } + }; + let state = {}; + state = TierSupportReducer(state, action); + expect(state.tierSupportReducer).toEqual({ + graphNodeSelectedMenu: action.data + }); + }); + + it('Action Type: TS_NODE_SEARCH_NO_RESULTS', () => { + ForceDirectedGraph.graphCounter = 0; // ensuring counter is at zero after previous tests + let emptyNodesAndLinksNoResults = { + graphCounter: 1, + graphMeta: {}, + linkDataArray: [], + nodeDataArray: [] + }; + const action = { + type: tierSupportActionTypes.TS_NODE_SEARCH_NO_RESULTS, + data: { + errorMsg: 'some error message' + } + }; + let state = {}; + state = TierSupportReducer(state, action); + expect(state.tierSupportReducer).toEqual({ + forceDirectedGraphRawData: emptyNodesAndLinksNoResults, + graphNodeSelectedMenu: TSUI_GRAPH_MENU_NODE_DETAILS, + feedbackMsgText: action.data.errorMsg, + feedbackMsgSeverity: MESSAGE_LEVEL_WARNING + }); + }); + + it('Action Type: TIER_SUPPORT_NETWORK_ERROR', () => { + ForceDirectedGraph.graphCounter = 0; // ensuring counter is at zero after previous tests + let emptyNodesAndLinksNoResults = { + graphCounter: 1, + graphMeta: {}, + linkDataArray: [], + nodeDataArray: [] + }; + const action = { + type: tierSupportActionTypes.TIER_SUPPORT_NETWORK_ERROR, + data: { + errorMsg: 'some error message' + } + }; + let state = {}; + state = TierSupportReducer(state, action); + expect(state.tierSupportReducer).toEqual({ + forceDirectedGraphRawData: emptyNodesAndLinksNoResults, + graphNodeSelectedMenu: TSUI_GRAPH_MENU_NODE_DETAILS, + feedbackMsgText: action.data.errorMsg, + feedbackMsgSeverity: MESSAGE_LEVEL_DANGER + }); + }); + + it('Action Type: TIER_SUPPORT_CLEAR_DATA', () => { + ForceDirectedGraph.graphCounter = 0; // ensuring counter is at zero after previous tests + let emptyNodesAndLinksNoResults = { + graphCounter: 1, + graphMeta: {}, + linkDataArray: [], + nodeDataArray: [] + }; + const action = { + type: tierSupportActionTypes.TIER_SUPPORT_CLEAR_DATA + }; + let state = {}; + state = TierSupportReducer(state, action); + expect(state.tierSupportReducer).toEqual({ + forceDirectedGraphRawData: emptyNodesAndLinksNoResults, + graphNodeSelectedMenu: TSUI_GRAPH_MENU_NODE_DETAILS, + feedbackMsgText: '', + feedbackMsgSeverity: '' + }); + }); + + it('Action Type: TS_GRAPH_NODE_SELECTED', () => { + const action = { + type: tierSupportActionTypes.TS_GRAPH_NODE_SELECTED, + data: 'some action data' + }; + let state = {}; + state = TierSupportReducer(state, action); + expect(state.tierSupportReducer).toEqual({ + nodeData: action.data + }); + }); + + it('Action Type: TIER_SUPPORT_ACTIVATE_BUSY_FEEDBACK', () => { + const action = { + type: tierSupportActionTypes.TIER_SUPPORT_ACTIVATE_BUSY_FEEDBACK, + }; + let state = {}; + state = TierSupportReducer(state, action); + expect(state.tierSupportReducer).toEqual({ + enableBusyFeedback: true + }); + }); + + it('Action Type: TIER_SUPPORT_DISABLE_BUSY_FEEDBACK', () => { + const action = { + type: tierSupportActionTypes.TIER_SUPPORT_DISABLE_BUSY_FEEDBACK, + }; + let state = {}; + state = TierSupportReducer(state, action); + expect(state.tierSupportReducer).toEqual({ + enableBusyFeedback: false + }); + }); + + it('Action Type: SEARCH_WARNING_EVENT', () => { + ForceDirectedGraph.graphCounter = 0; // ensuring counter is at zero after previous tests + let emptyNodesAndLinksNoResults = { + graphCounter: 1, + graphMeta: {}, + linkDataArray: [], + nodeDataArray: [] + }; + const action = { + type: globalAutoCompleteSearchBarActionTypes.SEARCH_WARNING_EVENT, + data: { + errorMsg: 'some warning msg' + } + }; + let state = {}; + state = TierSupportReducer(state, action); + expect(state.tierSupportReducer).toEqual({ + forceDirectedGraphRawData: emptyNodesAndLinksNoResults, + graphNodeSelectedMenu: TSUI_GRAPH_MENU_NODE_DETAILS + }); + }); + + it('Action Type: TS_OVERLAY_NETWORK_CALLBACK_RESPONSE_RECEIVED', () => { + const action = { + type: tierSupportActionTypes.TS_OVERLAY_NETWORK_CALLBACK_RESPONSE_RECEIVED, + data: { + curData: { + attr1: 'value1', + attr2: 'value2' + }, + paramName: 'attr2', + overlayData: 'someValue2' + } + }; + let state = {}; + state = TierSupportReducer(state, action); + expect(state.tierSupportReducer).toEqual({ + nodeData: { + attr1: 'value1', + attr2: 'someValue2' + } + }); + }); +}) diff --git a/test/utils/KeyMirror.test.js b/test/utils/KeyMirror.test.js new file mode 100644 index 0000000..22f6ff5 --- /dev/null +++ b/test/utils/KeyMirror.test.js @@ -0,0 +1,79 @@ +import keyMirror from 'utils/KeyMirror.js'; + +describe('KeyMirror', () => { + it('valid key mirror with nulls', () => { + const obj = { + TIER_SUPPORT: null, + INVENTORY: null, + VNF_SEARCH: null + } + const mirror = keyMirror(obj); + + for (let key in obj) { + expect(mirror).toHaveProperty(key); + expect(JSON.stringify(mirror[key])).toBe(JSON.stringify(Symbol(key))); + } + }); + + it('valid key mirror with undefined', () => { + const obj = { + TIER_SUPPORT: undefined, + INVENTORY: undefined, + VNF_SEARCH: undefined + } + const mirror = keyMirror(obj); + + for (let key in obj) { + expect(mirror).toHaveProperty(key); + expect(JSON.stringify(mirror[key])).toBe(JSON.stringify(Symbol(key))); + } + }); + + it('valid key mirror with values', () => { + let preMirrorList = { + TIER_SUPPORT: 'tier support', + INVENTORY: 'inventory', + VNF_SEARCH: 'vnf search' + }; + const mirror = keyMirror(preMirrorList); + + for (let key in preMirrorList) { + expect(mirror).toHaveProperty(key); + expect(JSON.stringify(mirror[key])).toBe(JSON.stringify(preMirrorList[key])); + } + }); + + it('valid key mirror with objects', () => { + let preMirrorList = { + TIER_SUPPORT: { + name: 'tier support' + }, + INVENTORY: { + name: 'inventory' + }, + VNF_SEARCH: { + name: 'vnf search' + } + }; + const mirror = keyMirror(preMirrorList); + + for (let key in preMirrorList) { + expect(mirror).toHaveProperty(key); + expect(JSON.stringify(mirror[key])).toBe(JSON.stringify(preMirrorList[key])); + } + }); + + it('invalid key mirror', () => { + let preMirrorList = [ + 'tier support', + 'inventory', + 'vnf search' + ] + const mirror = () => { + keyMirror(preMirrorList); + } + + expect(mirror).toThrow(Error); + expect(mirror).toThrowError('keyMirror(...): Argument must be an object.'); + }); +}) diff --git a/test/utils/Routes.test.js b/test/utils/Routes.test.js new file mode 100644 index 0000000..12d318c --- /dev/null +++ b/test/utils/Routes.test.js @@ -0,0 +1,129 @@ +import { + buildRouteObjWithHash, + decryptParamsForView, + buildRouteObjWithFilters, + changeUrlAddress +} from 'utils/Routes.js'; +import { + encrypt +} from 'utils/Crypto.js'; + +describe('Routes', () => { + it('build route with hash', () => { + const expectedResult = { + route: '/vnfSearch', + hashId: 'someCrazyHashHere' + }; + + const result = buildRouteObjWithHash(expectedResult.route, expectedResult.hashId); + + expect(JSON.stringify(result)).toBe(JSON.stringify(expectedResult)); + }); + + it('decrypt params for view', () => { + const stringToEncrypt = 'someCrazyStringHere'; + const encryptedString = encrypt(stringToEncrypt); + const result = decryptParamsForView(encryptedString); + + expect(JSON.stringify(result)).toBe(JSON.stringify({})); + }); + + it('decrypt params for view with obj', () => { + const objToEncrypt = [{id: 'someCrazyParamHere'}, {id: 'anotherCrazyParam'}]; + const encryptedObj = encrypt(JSON.stringify(objToEncrypt)); + const result = decryptParamsForView(encryptedObj); + + expect(JSON.stringify(result)).toBe(JSON.stringify(objToEncrypt)); + }); + + it('build routes with filters', () => { + const objToEncrypt = [{id: 'someCrazyParamHere'}, {id: 'anotherCrazyParam'}]; + const encryptedObj = encrypt(JSON.stringify(objToEncrypt)); + const result = decryptParamsForView(encryptedObj); + + expect(JSON.stringify(result)).toBe(JSON.stringify(objToEncrypt)); + const filterObj = { + filter1: 'value1', + filter2: undefined, + filter3: 'anotherValue' + }; + const routePath = '/vnfSearch'; + const expectedResults = { + route: routePath, + filterValues: [ + { + filterId: 'filter1', + filterValue: 'value1' + }, + { + filterId: 'filter2', + filterValue: '' + }, + { + filterId: 'filter3', + filterValue: 'anotherValue' + } + ] + } + + const routeWithFilters = buildRouteObjWithFilters(routePath, filterObj); + + expect(JSON.stringify(routeWithFilters)).toBe(JSON.stringify(expectedResults)); + }); + + it('change URL address for well known paths', () => { + const pathObj = { + route: 'schema', + filterValues: [ + { + filterId: 'filter1', + filterValue: 'value1' + }, + { + filterId: 'filter2', + filterValue: undefined + }, + { + filterId: 'filter3', + filterValue: 'anotherValue' + } + ] + }; + let historyObj = []; + const filterList = [ + 'filter1=value1', + 'filter2=', + 'filter3=anotherValue' + ]; + const toGo = '/' + pathObj.route + '/' + filterList.toString(); + const expectedResult = [ + toGo, + { + lastRoute: pathObj.route + } + ]; + + changeUrlAddress(pathObj, historyObj); + + expect(JSON.stringify(historyObj)).toBe(JSON.stringify(expectedResult)); + }); + + it('change URL address for well known paths with hash id', () => { + const pathObj = { + route: 'schema', + hashId: 'someCrazyHashIdHere' + }; + let historyObj = []; + const toGo = '/' + pathObj.route + '/' + pathObj.hashId; + const expectedResult = [ + toGo, + { + lastRoute: pathObj.route + } + ]; + + changeUrlAddress(pathObj, historyObj); + + expect(JSON.stringify(historyObj)).toBe(JSON.stringify(expectedResult)); + }); +}) diff --git a/test/utils/SpinnerContainer.test.js b/test/utils/SpinnerContainer.test.js index 90c7cf5..f9c01e2 100644 --- a/test/utils/SpinnerContainer.test.js +++ b/test/utils/SpinnerContainer.test.js @@ -17,8 +17,8 @@ describe('SpinnerContainer', () => { expect(spinner.find(ClipLoader)).toHaveLength(1); // ensure the ClipLoader is mounted expect(spinner.find(ClipLoader).props().color).toEqual(COLOR_BLUE); // ensure spinner is blue expect(spinner.find(ClipLoader).props().loading).toEqual(true); // ensure spinner is showing - expect(spinner.find('div.spinner-content')).toHaveLength(1); // ensure the children are grayed out - expect(spinner.find('div.spinner-content').children()).toHaveLength(2); // ensure number of children is accurate + expect(spinner.find('div.spin-content')).toHaveLength(1); // ensure the children are grayed out + expect(spinner.find('div.spin-content').children()).toHaveLength(2); // ensure number of children is accurate }); it('render spinner - not visible', () => { @@ -30,6 +30,6 @@ describe('SpinnerContainer', () => { expect(spinner.props().loading).toEqual(false); expect(spinner.find(ClipLoader)).toHaveLength(1); expect(spinner.find(ClipLoader).props().loading).toEqual(false); // ensure spinner is not showing - expect(spinner.find('div.spinner-content')).toHaveLength(0); + expect(spinner.find('div.spin-content')).toHaveLength(0); }); }) diff --git a/webpack.config.js b/webpack.config.js index 5d99160..f038d4d 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -25,70 +25,71 @@ var webpack = require('webpack'); var devPort = process.env.PORT || 8001; module.exports = { - devtool: 'eval-source-map', - entry: { - bundle: [ - 'app/main.app.jsx', - 'webpack/hot/only-dev-server' - ], - 'editAttributes/editAttributesBundle': [ - 'editAttributes/main.app.jsx', - 'webpack/hot/only-dev-server' - ] - }, - output: { - path: path.join(__dirname, 'dist'), - publicPath: `http://localhost:${devPort}/services/aai/webapp`, - filename: '[name].js' - }, - resolve: { - root: [path.resolve('.')], - alias: { - app: 'src/app', - 'generic-components': 'src/generic-components', - utils: 'src/utils', - images: 'resources/images', - editAttributes: 'src/editAttributes' - } - }, - devServer: { - port: devPort, - historyApiFallback: true, - publicPath: `http://localhost:${devPort}/`, - contentBase: path.join(__dirname, 'dist'), - hot: true, - progress: true, - inline: true, - debug: true, - stats: { - colors: true - } - }, - module: { - preLoaders: [{ - test: /\.(js|jsx)$/, - loader: 'source-map-loader' - }], - loaders: [ - {test: /\.(js|jsx)$/, loaders: ['babel-loader', 'eslint-loader'], exclude: /node_modules/}, - {test: /\.(css|scss)$/, loaders: ['style', 'css?sourceMap', 'sass?sourceMap']}, - // required for font icons - {test: /\.(woff|woff2)(\?.*)?$/, loader: 'url-loader?limit=16384&mimetype=application/font-woff'}, - {test: /\.(ttf|eot|otf)(\?.*)?$/, loader: 'file-loader'}, - {test: /\.(png|jpg|svg)(\?.*)?$/, loader: 'url-loader?limit=16384'}, - {test: /\.json$/, loaders: ['json']} - ] - }, - eslint: { - configFile: './.eslintrc', - emitError: true, - emitWarning: true - }, - plugins: [ - new webpack.DefinePlugin({ - DEBUG: true - }), + devtool: 'eval-source-map', + entry: { + bundle: [ + 'app/main.app.jsx', + 'webpack/hot/only-dev-server' + ], + 'editAttributes/editAttributesBundle': [ + 'editAttributes/main.app.jsx', + 'webpack/hot/only-dev-server' + ] + }, + output: { + path: path.join(__dirname, 'dist'), + publicPath: `http://localhost:${devPort}/services/aai/webapp`, + filename: '[name].js' + }, + resolve: { + root: [path.resolve('.')], + alias: { + app: 'src/app', + 'generic-components': 'src/generic-components', + utils: 'src/utils', + images: 'resources/images', + editAttributes: 'src/editAttributes' + }, + extensions: ["", ".webpack.js", ".web.js", ".js", ".json", ".jsx"] + }, + devServer: { + port: devPort, + historyApiFallback: true, + publicPath: `http://localhost:${devPort}/`, + contentBase: path.join(__dirname, 'dist'), + hot: true, + progress: true, + inline: true, + debug: true, + stats: { + colors: true + } + }, + module: { + preLoaders: [{ + test: /\.(js|jsx)$/, + loader: 'source-map-loader' + }], + loaders: [ + {test: /\.(js|jsx)$/, loaders: ['babel-loader', 'eslint-loader'], exclude: /node_modules/}, + {test: /\.(css|scss)$/, loaders: ['style', 'css?sourceMap', 'sass?sourceMap']}, + // required for font icons + {test: /\.(woff|woff2)(\?.*)?$/, loader: 'url-loader?limit=16384&mimetype=application/font-woff'}, + {test: /\.(ttf|eot|otf)(\?.*)?$/, loader: 'file-loader'}, + {test: /\.(png|jpg|svg)(\?.*)?$/, loader: 'url-loader?limit=16384'}, + {test: /\.json$/, loaders: ['json']} + ] + }, + eslint: { + configFile: './.eslintrc', + emitError: true, + emitWarning: true + }, + plugins: [ + new webpack.DefinePlugin({ + DEBUG: true + }), - new webpack.HotModuleReplacementPlugin() - ] + new webpack.HotModuleReplacementPlugin() + ] }; diff --git a/webpack.devConfig.js b/webpack.devConfig.js index b7c570b..e1e8876 100644 --- a/webpack.devConfig.js +++ b/webpack.devConfig.js @@ -25,72 +25,73 @@ var webpack = require('webpack'); var devPort = process.env.PORT || 8001; module.exports = { - devtool: 'eval-source-map', - entry: { - 'aai/bundle': [ - 'app/main.app.jsx', - `webpack-dev-server/client?https://localhost:${devPort}`, - 'webpack/hot/only-dev-server' - ], - 'editAttributes/editAttributesBundle': [ - 'editAttributes/main.app.jsx', - `webpack-dev-server/client?https://localhost:${devPort}`, - 'webpack/hot/only-dev-server' - ] - }, - output: { - path: path.join(__dirname, 'dist'), - publicPath: `https://localhost:${devPort}/`, - filename: '[name].js' - }, - resolve: { - root: [path.resolve('.')], - alias: { - app: 'src/app', - 'generic-components': 'src/generic-components', - utils: 'src/utils', - images: 'resources/images', - editAttributes: 'src/editAttributes' - } - }, - devServer: { - port: devPort, - historyApiFallback: true, - publicPath: `https://localhost:${devPort}/`, - contentBase: path.join(__dirname, 'dist'), - hot: true, - progress: true, - inline: true, - debug: true, - https: true, - stats: { - colors: true - } - }, - module: { - preLoaders: [ - {test: /\.(js|jsx)$/, loader: 'source-map-loader'} - ], - loaders: [ - {test: /\.(js|jsx)$/, loaders: ['babel-loader', 'eslint-loader'], exclude: /node_modules/}, - {test: /\.(css|scss)$/, loaders: ['style', 'css?sourceMap', 'sass?sourceMap']}, - // required for font icons - {test: /\.(woff|woff2)(\?.*)?$/, loader: 'url-loader?limit=16384&mimetype=application/font-woff'}, - {test: /\.(ttf|eot|otf)(\?.*)?$/, loader: 'file-loader'}, - {test: /\.(png|jpg|svg)(\?.*)?$/, loader: 'url-loader?limit=16384'}, - {test: /\.json$/, loaders: ['json']} - ] - }, - eslint: { - configFile: './.eslintrc', - emitError: true, - emitWarning: true - }, - plugins: [ - new webpack.DefinePlugin({ - DEBUG: true - }), + devtool: 'eval-source-map', + entry: { + 'aai/bundle': [ + 'app/main.app.jsx', + `webpack-dev-server/client?https://localhost:${devPort}`, + 'webpack/hot/only-dev-server' + ], + 'editAttributes/editAttributesBundle': [ + 'editAttributes/main.app.jsx', + `webpack-dev-server/client?https://localhost:${devPort}`, + 'webpack/hot/only-dev-server' + ] + }, + output: { + path: path.join(__dirname, 'dist'), + publicPath: `https://localhost:${devPort}/`, + filename: '[name].js' + }, + resolve: { + root: [path.resolve('.')], + alias: { + app: 'src/app', + 'generic-components': 'src/generic-components', + utils: 'src/utils', + images: 'resources/images', + editAttributes: 'src/editAttributes' + }, + extensions: ["", ".webpack.js", ".web.js", ".js", ".json", ".jsx"] + }, + devServer: { + port: devPort, + historyApiFallback: true, + publicPath: `https://localhost:${devPort}/`, + contentBase: path.join(__dirname, 'dist'), + hot: true, + progress: true, + inline: true, + debug: true, + https: true, + stats: { + colors: true + } + }, + module: { + preLoaders: [ + {test: /\.(js|jsx)$/, loader: 'source-map-loader'} + ], + loaders: [ + {test: /\.(js|jsx)$/, loaders: ['babel-loader', 'eslint-loader'], exclude: /node_modules/}, + {test: /\.(css|scss)$/, loaders: ['style', 'css?sourceMap', 'sass?sourceMap']}, + // required for font icons + {test: /\.(woff|woff2)(\?.*)?$/, loader: 'url-loader?limit=16384&mimetype=application/font-woff'}, + {test: /\.(ttf|eot|otf)(\?.*)?$/, loader: 'file-loader'}, + {test: /\.(png|jpg|svg)(\?.*)?$/, loader: 'url-loader?limit=16384'}, + {test: /\.json$/, loaders: ['json']} + ] + }, + eslint: { + configFile: './.eslintrc', + emitError: true, + emitWarning: true + }, + plugins: [ + new webpack.DefinePlugin({ + DEBUG: true + }), - new webpack.HotModuleReplacementPlugin() - ] + new webpack.HotModuleReplacementPlugin() + ] }; |