increasing test coverage to 20 percent 19/66519/1
authorSteven Thomas <steve.thomas@amdocs.com>
Thu, 13 Sep 2018 20:22:40 +0000 (16:22 -0400)
committerSteven Thomas <steve.thomas@amdocs.com>
Thu, 13 Sep 2018 20:23:05 +0000 (16:23 -0400)
Issue-ID: AAI-1599
Change-Id: I345e38d4319e52b56de0a33d7065e02617cc2103
Signed-off-by: Steven Thomas <steve.thomas@amdocs.com>
52 files changed:
.gitignore
package.json
resources/images/icons/reports.png [new file with mode: 0644]
resources/images/icons/reports.svg [new file with mode: 0644]
resources/scss/common/_layout.scss
resources/views/customComponents.json [new file with mode: 0644]
scripts/build/build.sh
src/app/AppStore.js
src/app/MainScreenHeader.jsx
src/app/MainScreenWrapper.jsx
src/app/MainScreenWrapperActionHelper.js
src/app/configurableViews/ConfigurableViewActions.js [new file with mode: 0644]
src/app/configurableViews/ConfigurableViewConstants.js [new file with mode: 0644]
src/app/configurableViews/ConfigurableViewManager.js [new file with mode: 0644]
src/app/configurableViews/ConfigurableViewReducer.js [new file with mode: 0644]
src/app/configurableViews/index.js [new file with mode: 0644]
src/app/networking/NetworkCalls.js
src/app/tierSupport/TierSupportActions.js
src/generic-components/autoCompleteSearchBar/AutoCompleteSearchBar.jsx
src/generic-components/componentManager/ComponentManagerContainer.jsx
src/generic-components/graph/Link.jsx
src/generic-components/graph/Node.jsx
src/generic-components/graph/SVGShape.jsx
src/generic-components/input/ToggleInput.jsx
src/generic-components/input/inputOptions/InputOptions.jsx
src/generic-components/map/TopographicMap.jsx
src/generic-components/notifications/NotificationModal.jsx
src/generic-components/panel/SlidePanel.jsx
src/generic-components/titledContainer/TitledContainer.jsx
src/generic-components/toggleButtonGroup/ToggleButtonGroup.jsx
src/utils/Crypto.js
src/utils/SpinnerContainer.jsx
test/autoCompleteSearchBar/AutoCompleteSearchBar.test.js [new file with mode: 0644]
test/configurableViews/ConfigurableViewActions.test.js [new file with mode: 0644]
test/configurableViews/ConfigurableViewReducer.test.js [new file with mode: 0644]
test/fileMock.js [moved from scripts/test/fileMock.js with 100% similarity]
test/globalAutoCompleteSearchBar/GlobalAutoCompleteSearchBar.test.js [new file with mode: 0644]
test/globalAutoCompleteSearchBar/GlobalAutoCompleteSearchBarReducer.test.js [new file with mode: 0644]
test/globalInlineMessageBar/GlobalInlineMessageBar.test.js [new file with mode: 0644]
test/globalInlineMessageBar/GlobalInlineMessageBarAction.test.js [new file with mode: 0644]
test/globalInlineMessageBar/GlobalInlineMessageBarReducer.test.js [new file with mode: 0644]
test/input/SelectInput.test.js [new file with mode: 0644]
test/input/ToggleInput.test.js [new file with mode: 0644]
test/setupTests.js [moved from scripts/test/setupTests.js with 100% similarity]
test/styleMock.js [moved from scripts/test/styleMock.js with 100% similarity]
test/tierSupport/TierSupportActions.test.js [new file with mode: 0644]
test/tierSupport/TierSupportReducer.test.js [new file with mode: 0644]
test/utils/KeyMirror.test.js [new file with mode: 0644]
test/utils/Routes.test.js [new file with mode: 0644]
test/utils/SpinnerContainer.test.js
webpack.config.js
webpack.devConfig.js

index f759a7f..d5fd50c 100644 (file)
@@ -10,6 +10,3 @@ test/coverage
 npm-debug.log
 .vscode
 scripts/elasticsearch/auditBulkLoad.json
-.classpath
-.project
-.settings/
\ No newline at end of file
index 211d047..898e70e 100644 (file)
@@ -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",
     "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",
       "!**/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
new file mode 100644 (file)
index 0000000..b3d3c4c
Binary files /dev/null and b/resources/images/icons/reports.png differ
diff --git a/resources/images/icons/reports.svg b/resources/images/icons/reports.svg
new file mode 100644 (file)
index 0000000..3ca0bf1
--- /dev/null
@@ -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
index 6863baf..11265c5 100644 (file)
@@ -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 (file)
index 0000000..0d4f101
--- /dev/null
@@ -0,0 +1,2 @@
+[
+]
index 9fd9fd2..7af49cd 100644 (file)
@@ -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
index 9205937..842b683 100644 (file)
@@ -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()
index e485161..8c5e13d 100644 (file)
@@ -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>
index 6689af9..730ac93 100644 (file)
@@ -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>
     );
index 371ea42..dd7450b 100644 (file)
@@ -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 (file)
index 0000000..7cffacc
--- /dev/null
@@ -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 (file)
index 0000000..202dd18
--- /dev/null
@@ -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 (file)
index 0000000..71cc6cf
--- /dev/null
@@ -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 (file)
index 0000000..9a5eee0
--- /dev/null
@@ -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 (file)
index 0000000..3490fef
--- /dev/null
@@ -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;
+}
index 63c08ed..e391391 100644 (file)
@@ -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
 };
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
index 08e4e30..afb37be 100644 (file)
  * 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 => {
index b7fca67..4f93125 100644 (file)
@@ -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>
     );
index 02a3eba..cd51d37 100644 (file)
@@ -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) {
index aec33b8..8c87ac5 100644 (file)
@@ -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;
index 6de4715..67d954c 100644 (file)
@@ -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;
index b06c46f..8b33598 100644 (file)
@@ -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;
index 49b0376..f68758a 100644 (file)
@@ -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;
+  }
 }
index bb9d777..bf17df1 100644 (file)
@@ -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;
index fc0fb64..6da9909 100644 (file)
@@ -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';
index 1547da3..0e747d5 100644 (file)
  * ============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);
index 9580837..1550cee 100644 (file)
@@ -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;
index d3d606a..6aa626d 100644 (file)
@@ -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';
 
index 6d57637..0fe8939 100644 (file)
@@ -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);
index c84ca76..91b6799 100644 (file)
@@ -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
 };
index 9bcf769..17e6e58 100644 (file)
@@ -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 (file)
index 0000000..7ba3d11
--- /dev/null
@@ -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 (file)
index 0000000..9d7bc9e
--- /dev/null
@@ -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 (file)
index 0000000..0c5c46e
--- /dev/null
@@ -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
+    });
+  });
+})
similarity index 100%
rename from scripts/test/fileMock.js
rename to test/fileMock.js
diff --git a/test/globalAutoCompleteSearchBar/GlobalAutoCompleteSearchBar.test.js b/test/globalAutoCompleteSearchBar/GlobalAutoCompleteSearchBar.test.js
new file mode 100644 (file)
index 0000000..25676b7
--- /dev/null
@@ -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 (file)
index 0000000..1078df6
--- /dev/null
@@ -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 (file)
index 0000000..9dc2a28
--- /dev/null
@@ -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 (file)
index 0000000..4def5ac
--- /dev/null
@@ -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 (file)
index 0000000..62389b4
--- /dev/null
@@ -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 (file)
index 0000000..a669361
--- /dev/null
@@ -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 (file)
index 0000000..80f0345
--- /dev/null
@@ -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
+  });
+})
similarity index 100%
rename from scripts/test/styleMock.js
rename to test/styleMock.js
diff --git a/test/tierSupport/TierSupportActions.test.js b/test/tierSupport/TierSupportActions.test.js
new file mode 100644 (file)
index 0000000..62485ee
--- /dev/null
@@ -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 (file)
index 0000000..9825a06
--- /dev/null
@@ -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 (file)
index 0000000..22f6ff5
--- /dev/null
@@ -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 (file)
index 0000000..12d318c
--- /dev/null
@@ -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));
+  });
+})
index 90c7cf5..f9c01e2 100644 (file)
@@ -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);
   });
 })
index 5d99160..f038d4d 100644 (file)
@@ -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()
+  ]
 };
index b7c570b..e1e8876 100644 (file)
@@ -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()
+  ]
 };