diff options
22 files changed, 1067 insertions, 125 deletions
diff --git a/.gitignore b/.gitignore index b6d49c75b..fbdcf4af5 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,6 @@ target ui-react/node_modules ui-react/build **/package-lock.json -**/logs/ **/.evosuite/ **/debug-logs/ *.log diff --git a/ui-react/package.json b/ui-react/package.json index f1ae2bc9d..de7cb26d1 100644 --- a/ui-react/package.json +++ b/ui-react/package.json @@ -28,5 +28,9 @@ "not dead", "not ie <= 11", "not op_mini all" - ] + ], + "devDependencies": { + "enzyme": "3.10.0", + "enzyme-adapter-react-16": "1.14.0" + } } diff --git a/ui-react/src/LoopUI.js b/ui-react/src/LoopUI.js index a1aff3d61..7d8fcb5cf 100644 --- a/ui-react/src/LoopUI.js +++ b/ui-react/src/LoopUI.js @@ -34,10 +34,12 @@ import LoopStatus from './components/loop_viewer/status/LoopStatus'; import UserService from './api/UserService'; import LoopCache from './api/LoopCache'; -import { Route } from 'react-router-dom' +import { Route, Redirect } from 'react-router-dom' import OpenLoopModal from './components/dialogs/OpenLoop/OpenLoopModal'; import OperationalPolicyModal from './components/dialogs/OperationalPolicy/OperationalPolicyModal'; import ConfigurationPolicyModal from './components/dialogs/ConfigurationPolicy/ConfigurationPolicyModal'; +import LoopProperties from './components/dialogs/LoopProperties'; +import UserInfo from './components/dialogs/UserInfo'; const ProjectNameStyled = styled.a` vertical-align: middle; @@ -46,7 +48,7 @@ const ProjectNameStyled = styled.a` ` const LoopViewDivStyled = styled.div` - height: 90vh; + height: 100%; overflow: hidden; margin-left: 10px; margin-right: 10px; @@ -78,9 +80,11 @@ const LoopViewLoopNameSpanStyled = styled.span` export default class LoopUI extends React.Component { + static defaultLoopName="Empty (NO loop loaded yet)"; + state = { userName: null, - loopName: "Empty (NO loop loaded yet)", + loopName: LoopUI.defaultLoopName, loopCache: new LoopCache({}), }; @@ -90,7 +94,7 @@ export default class LoopUI extends React.Component { this.updateLoopCache = this.updateLoopCache.bind(this); } - componentDidMount() { + componentWillMount() { this.getUser(); } @@ -102,7 +106,7 @@ export default class LoopUI extends React.Component { renderMenuNavBar() { return ( - <MenuBar /> + <MenuBar loopCache={this.state.loopCache}/> ); } @@ -136,7 +140,7 @@ export default class LoopUI extends React.Component { renderLoopViewHeader() { return ( <LoopViewHeaderDivStyled> - Loop Viewer - <LoopViewLoopNameSpanStyled id="loop_name">{this.state.loopName}</LoopViewLoopNameSpanStyled> + Loop Viewer - {this.state.loopName} </LoopViewHeaderDivStyled> ); } @@ -145,12 +149,16 @@ export default class LoopUI extends React.Component { return ( <LoopViewBodyDivStyled> <LoopSvg loopCache={this.state.loopCache} /> - <LoopLogs /> - <LoopStatus /> + <LoopStatus loopCache={this.state.loopCache}/> + <LoopLogs loopCache={this.state.loopCache} /> </LoopViewBodyDivStyled> ); } + getLoopCache() { + return this.state.loopCache; + + } renderLoopViewer() { return ( <LoopViewDivStyled> @@ -162,6 +170,7 @@ export default class LoopUI extends React.Component { updateLoopCache(loopJson) { this.setState({ loopCache: new LoopCache(loopJson) }); + this.setState({ loopName: this.state.loopCache.getLoopName() }); } render() { @@ -172,8 +181,11 @@ export default class LoopUI extends React.Component { {this.renderLoopViewer()} <Route path="/operationalPolicyModal" render={(routeProps) => (<OperationalPolicyModal {...routeProps} loopCache={this.state.loopCache} />)} /> - <Route path="/configurationPolicyModal" render={(routeProps) => (<ConfigurationPolicyModal {...routeProps} loopCache={this.state.loopCache} />)} /> + <Route path="/configurationPolicyModal/:componentName" render={(routeProps) => (<ConfigurationPolicyModal {...routeProps} loopCache={this.state.loopCache} />)} /> <Route path="/openLoop" render={(routeProps) => (<OpenLoopModal {...routeProps} updateLoopCacheFunction={this.updateLoopCache} />)} /> + <Route path="/loopProperties" render={(routeProps) => (<LoopProperties {...routeProps} loopCache={this.getLoopCache()} />)} /> + <Route path="/userInfo" render={(routeProps) => (<UserInfo {...routeProps} />)} /> + <Route path="/closeLoop" render={(routeProps) => (<Redirect to='/'/>)} /> </div> ); } diff --git a/ui-react/src/OnapClamp.js b/ui-react/src/OnapClamp.js index a8cc2154a..506f6e09d 100644 --- a/ui-react/src/OnapClamp.js +++ b/ui-react/src/OnapClamp.js @@ -29,7 +29,7 @@ import { DefaultClampTheme } from './theme/globalStyle.js'; export default class OnapClamp extends LoopUI { render() { - console.log("Onap Clamp UI starting"); + console.info("Onap Clamp UI starting"); return ( <ThemeProvider theme={DefaultClampTheme}> {super.render()} diff --git a/ui-react/src/__test__/LoopCache.test.js b/ui-react/src/__test__/LoopCache.test.js new file mode 100644 index 000000000..e5b50259d --- /dev/null +++ b/ui-react/src/__test__/LoopCache.test.js @@ -0,0 +1,217 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP CLAMP + * ================================================================================ + * Copyright (C) 2019 AT&T Intellectual Property. All rights + * reserved. + * ================================================================================ + * 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 React from 'react'; +import LoopCache from '../api/LoopCache'; + +const json = require('./LoopCache_mokeLoopJsonCache.json'); + +describe('Verify LoopCache functions', () => { + const loopCache = new LoopCache(json); + it('getLoopName', () => { + expect(loopCache.getLoopName()).toBe("LOOP_Jbv1z_v1_0_ResourceInstanceName1_tca"); + }); + + it('getOperationalPolicyConfigurationJson', () => { + const opPolicyConfig = { + "guard_policies": {}, + "operational_policy": { + "controlLoop": {}, + "policies": [] + } + }; + expect(loopCache.getOperationalPolicyConfigurationJson()).toStrictEqual(opPolicyConfig); + }); + + it('getOperationalPolicies', () => { + const opPolicy = [{ + "name": "OPERATIONAL_h2NMX_v1_0_ResourceInstanceName1_tca", + "configurationsJson": { + "guard_policies": {}, + "operational_policy": { + "controlLoop": {}, + "policies": [] + + } + } + }]; + expect(loopCache.getOperationalPolicies()).toStrictEqual(opPolicy); + }); + + it('getGlobalProperties', () => { + const globelProp = { + "dcaeDeployParameters": { + "location_id": "", + "service_id": "", + "policy_id": "TCA_h2NMX_v1_0_ResourceInstanceName1_tca" + } + }; + expect(loopCache.getGlobalProperties()).toStrictEqual(globelProp); + }); + + it('getDcaeDeploymentProperties', () => { + const deploymentProp = { + "location_id": "", + "service_id": "", + "policy_id": "TCA_h2NMX_v1_0_ResourceInstanceName1_tca" + }; + expect(loopCache.getDcaeDeploymentProperties()).toStrictEqual(deploymentProp); + }); + + it('getMicroServicesJsonForType', () => { + const msJson = { + "name": "TCA_h2NMX_v1_0_ResourceInstanceName1_tca", + "modelType": "onap.policies.monitoring.cdap.tca.hi.lo.app", + "properties": {"domain": "measurementsForVfScaling"}, + "shared": false, + "jsonRepresentation": {"schema": {}} + }; + expect(loopCache.getMicroServicesJsonForType("TCA_h2NMX_v1_0_ResourceInstanceName1_tca")).toStrictEqual(msJson); + expect(loopCache.getMicroServicesJsonForType("TCA_h2NMX_v1_0_ResourceInstanceName1_tca_2")).toBeNull(); + }); + + it('getMicroServiceProperties', () => { + const msProp = {"domain": "measurementsForVfScaling"}; + expect(loopCache.getMicroServiceProperties("TCA_h2NMX_v1_0_ResourceInstanceName1_tca")).toStrictEqual(msProp); + expect(loopCache.getMicroServiceProperties("TCA_h2NMX_v1_0_ResourceInstanceName1_tca_2")).toBeNull(); + }); + + it('getMicroServiceJsonRepresentationForType', () => { + const msJsonRepresentation = {"schema": {}}; + expect(loopCache.getMicroServiceJsonRepresentationForType("TCA_h2NMX_v1_0_ResourceInstanceName1_tca")).toStrictEqual(msJsonRepresentation); + }); + + it('getMicroServiceJsonRepresentationForType', () => { + const msJsonRepresentation = {"schema": {}}; + expect(loopCache.getMicroServiceJsonRepresentationForType("TCA_h2NMX_v1_0_ResourceInstanceName1_tca")).toStrictEqual(msJsonRepresentation); + }); + + it('getResourceDetailsVfProperty', () => { + const resourceVF = { + "vLoadBalancerMS 0": { + "resourceVendor": "Test", + "resourceVendorModelNumber": "", + "name": "vLoadBalancerMS", + "description": "vLBMS", + "invariantUUID": "1a31b9f2-e50d-43b7-89b3-a040250cf506", + "subcategory": "Load Balancer", + "category": "Application L4+", + "type": "VF", + "UUID": "b4c4f3d7-929e-4b6d-a1cd-57e952ddc3e6", + "version": "1.0", + "resourceVendorRelease": "1.0", + "customizationUUID": "465246dc-7748-45f4-a013-308d92922552" + } + }; + expect(loopCache.getResourceDetailsVfProperty()).toStrictEqual(resourceVF); + }); + + it('getResourceDetailsVfModuleProperty', () => { + const vfModule = { + "Vloadbalancerms..vpkg..module-1": { + "vfModuleModelInvariantUUID": "ca052563-eb92-4b5b-ad41-9111768ce043", + "vfModuleModelVersion": "1", + "vfModuleModelName": "Vloadbalancerms..vpkg..module-1", + "vfModuleModelUUID": "1e725ccc-b823-4f67-82b9-4f4367070dbc", + "vfModuleModelCustomizationUUID": "1bffdc31-a37d-4dee-b65c-dde623a76e52", + "min_vf_module_instances": 0, + "vf_module_label": "vpkg", + "max_vf_module_instances": 1, + "vf_module_type": "Expansion", + "isBase": false, + "initial_count": 0, + "volume_group": false + } + }; + expect(loopCache.getResourceDetailsVfModuleProperty()).toStrictEqual(vfModule); + }); + + it('getLoopLogsArray', () => { + const logs = [ + { + "id": 1, + "logType": "INFO", + "logComponent": "CLAMP", + "message": "Operational and Guard policies UPDATED", + "logInstant": "2019-07-08T09:44:37Z" + } + ]; + expect(loopCache.getLoopLogsArray()).toStrictEqual(logs); + }); + + it('getComponentStates', () => { + const component = { + "POLICY": { + "componentState": { + "stateName": "NOT_SENT", + "description": "The policies defined have NOT yet been created on the policy engine" + } + }, + "DCAE": { + "componentState": { + "stateName": "BLUEPRINT_DEPLOYED", + "description": "The DCAE blueprint has been found in the DCAE inventory but not yet instancianted for this loop" + } + } + }; + expect(loopCache.getComponentStates()).toStrictEqual(component); + }); + + it('updateGlobalProperties', () => { + const newGlobalProps = { + "dcaeDeployParameters": { + "location_id": "newLocation", + "service_id": "newServiceId", + "policy_id": "TCA_h2NMX_v1_0_ResourceInstanceName1_tca_2" + } + }; + loopCache.updateGlobalProperties(newGlobalProps); + expect(loopCache.getGlobalProperties()).toStrictEqual(newGlobalProps); + }); + + it('updateOperationalPolicyProperties', () => { + const newOpPolicy = [{ + "name": "OPERATIONAL_h2NMX_v1_0_ResourceInstanceName1_tca_new", + "configurationsJson": { + "guard_policies": {}, + "operational_policy": { + "controlLoop": {}, + "policies": [] + } + } + }]; + loopCache.updateOperationalPolicyProperties(newOpPolicy); + expect(loopCache.getOperationalPolicies()).toStrictEqual(newOpPolicy); + }); + + it('updateMicroServiceProperties', () => { + const newMsPolicy = { + "name": "TCA_h2NMX_v1_0_ResourceInstanceName1_tca", + "modelType": "onap.policies.monitoring.cdap.tca.hi.lo.app", + "properties": {"domain": "measurementsForVfScalingNew"}, + "shared": true, + "jsonRepresentation": {"schema": {}} + };; + loopCache.updateMicroServiceProperties("TCA_h2NMX_v1_0_ResourceInstanceName1_tca", newMsPolicy); + expect(loopCache.getMicroServicesJsonForType("TCA_h2NMX_v1_0_ResourceInstanceName1_tca")).toStrictEqual(newMsPolicy); + }); + }); diff --git a/ui-react/src/__test__/LoopCache_mokeLoopJsonCache.json b/ui-react/src/__test__/LoopCache_mokeLoopJsonCache.json new file mode 100644 index 000000000..184eaf7cd --- /dev/null +++ b/ui-react/src/__test__/LoopCache_mokeLoopJsonCache.json @@ -0,0 +1,117 @@ +{ + "name": "LOOP_Jbv1z_v1_0_ResourceInstanceName1_tca", + "dcaeBlueprintId": "typeId-3a942643-a8f7-4e54-b2c1-eea8daba2b17", + "globalPropertiesJson": { + "dcaeDeployParameters": { + "location_id": "", + "service_id": "", + "policy_id": "TCA_h2NMX_v1_0_ResourceInstanceName1_tca" + } + }, + "modelPropertiesJson": { + "serviceDetails": { + "serviceType": "", + "namingPolicy": "", + "environmentContext": "General_Revenue-Bearing", + "serviceEcompNaming": "true", + "serviceRole": "", + "name": "vLoadBalancerMS", + "description": "vLBMS", + "invariantUUID": "30ec5b59-4799-48d8-ac5f-1058a6b0e48f", + "ecompGeneratedNaming": "true", + "category": "Network L4+", + "type": "Service", + "UUID": "63cac700-ab9a-4115-a74f-7eac85e3fce0", + "instantiationType": "A-la-carte" + }, + "resourceDetails": { + "CP": {}, + "VL": {}, + "VF": { + "vLoadBalancerMS 0": { + "resourceVendor": "Test", + "resourceVendorModelNumber": "", + "name": "vLoadBalancerMS", + "description": "vLBMS", + "invariantUUID": "1a31b9f2-e50d-43b7-89b3-a040250cf506", + "subcategory": "Load Balancer", + "category": "Application L4+", + "type": "VF", + "UUID": "b4c4f3d7-929e-4b6d-a1cd-57e952ddc3e6", + "version": "1.0", + "resourceVendorRelease": "1.0", + "customizationUUID": "465246dc-7748-45f4-a013-308d92922552" + } + }, + "CR": {}, + "VFC": {}, + "PNF": {}, + "Service": {}, + "CVFC": {}, + "Service Proxy": {}, + "Configuration": {}, + "AllottedResource": {}, + "VFModule": { + "Vloadbalancerms..vpkg..module-1": { + "vfModuleModelInvariantUUID": "ca052563-eb92-4b5b-ad41-9111768ce043", + "vfModuleModelVersion": "1", + "vfModuleModelName": "Vloadbalancerms..vpkg..module-1", + "vfModuleModelUUID": "1e725ccc-b823-4f67-82b9-4f4367070dbc", + "vfModuleModelCustomizationUUID": "1bffdc31-a37d-4dee-b65c-dde623a76e52", + "min_vf_module_instances": 0, + "vf_module_label": "vpkg", + "max_vf_module_instances": 1, + "vf_module_type": "Expansion", + "isBase": false, + "initial_count": 0, + "volume_group": false + } + } + } + }, + "lastComputedState": "DESIGN", + "components": { + "POLICY": { + "componentState": { + "stateName": "NOT_SENT", + "description": "The policies defined have NOT yet been created on the policy engine" + } + }, + "DCAE": { + "componentState": { + "stateName": "BLUEPRINT_DEPLOYED", + "description": "The DCAE blueprint has been found in the DCAE inventory but not yet instancianted for this loop" + } + } + }, + "operationalPolicies": [ + { + "name": "OPERATIONAL_h2NMX_v1_0_ResourceInstanceName1_tca", + "configurationsJson": { + "guard_policies": {}, + "operational_policy": { + "controlLoop": {}, + "policies": [] + } + } + } + ], + "microServicePolicies": [ + { + "name": "TCA_h2NMX_v1_0_ResourceInstanceName1_tca", + "modelType": "onap.policies.monitoring.cdap.tca.hi.lo.app", + "properties": {"domain": "measurementsForVfScaling"}, + "shared": false, + "jsonRepresentation": {"schema": {}} + } + ], + "loopLogs": [ + { + "id": 1, + "logType": "INFO", + "logComponent": "CLAMP", + "message": "Operational and Guard policies UPDATED", + "logInstant": "2019-07-08T09:44:37Z" + } + ] +} diff --git a/ui-react/src/__test__/OpenLoopModal.test.js b/ui-react/src/__test__/OpenLoopModal.test.js new file mode 100644 index 000000000..044eeda89 --- /dev/null +++ b/ui-react/src/__test__/OpenLoopModal.test.js @@ -0,0 +1,35 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP CLAMP + * ================================================================================ + * Copyright (C) 2019 AT&T Intellectual Property. All rights + * reserved. + * ================================================================================ + * 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 React from 'react'; +import { shallow } from 'enzyme'; +import OpenLoopModal from '../components/dialogs/OpenLoop/OpenLoopModal'; + +describe('Verify OpenLoopModal', () => { + + it('Test the render method', () => { + const component = shallow(<OpenLoopModal/>); + expect(component).toMatchSnapshot(); + }); + + +}); diff --git a/ui-react/src/api/LoopCache.js b/ui-react/src/api/LoopCache.js index 4c8f68c23..3ee5acc68 100644 --- a/ui-react/src/api/LoopCache.js +++ b/ui-react/src/api/LoopCache.js @@ -29,13 +29,11 @@ export default class LoopCache { } updateMicroServiceProperties(type, newMsProperties) { - if (newMsProperties["name"] === type) { for (var policy in this.loopJsonCache["microServicePolicies"]) { if (this.loopJsonCache["microServicePolicies"][policy]["name"] === type) { - this.loopJsonCache["microServicePolicies"][policy] = newMsProperties; + this.loopJsonCache["microServicePolicies"][policy]["properties"] = newMsProperties; } } - } } updateGlobalProperties(newGlobalProperties) { @@ -51,49 +49,47 @@ export default class LoopCache { } getOperationalPolicyConfigurationJson() { - return JSON.parse(JSON.stringify(this.loopJsonCache["operationalPolicies"]["0"]["configurationsJson"])); + return this.loopJsonCache["operationalPolicies"]["0"]["configurationsJson"]; } getOperationalPolicies() { - return JSON.parse(JSON.stringify(this.loopJsonCache["operationalPolicies"])); + return this.loopJsonCache["operationalPolicies"]; } getGlobalProperties() { - return JSON.parse(JSON.stringify(this.loopJsonCache["globalPropertiesJson"])); + return this.loopJsonCache["globalPropertiesJson"]; } getDcaeDeploymentProperties() { - return JSON.parse(JSON.stringify(this.loopJsonCache["globalPropertiesJson"]["dcaeDeployParameters"])); + return this.loopJsonCache["globalPropertiesJson"]["dcaeDeployParameters"]; + } + + getMicroServicePolicies() { + return this.loopJsonCache["microServicePolicies"]; } - getMicroServicesJsonForType(type) { - var msProperties = this.loopJsonCache["microServicePolicies"]; + getMicroServiceForName(name) { + var msProperties=this.getMicroServicePolicies(); for (var policy in msProperties) { - if (msProperties[policy]["name"] === type) { - return JSON.parse(JSON.stringify(msProperties[policy])); + if (msProperties[policy]["name"] === name) { + return msProperties[policy]; } } return null; } - getMicroServiceProperties(type) { - var msProperties = this.loopJsonCache["microServicePolicies"]; - for (var policy in msProperties) { - if (msProperties[policy]["name"] === type) { - if (msProperties[policy]["properties"] !== null && msProperties[policy]["properties"] !== undefined) { - return JSON.parse(JSON.stringify(msProperties[policy]["properties"])); - } - } + getMicroServicePropertiesForName(name) { + var msConfig = this.getMicroServiceForName(name); + if (msConfig !== null) { + return msConfig["properties"]; } return null; } - getMicroServiceJsonRepresentationForType(type) { - var msProperties = this.loopJsonCache["microServicePolicies"]; - for (var policy in msProperties) { - if (msProperties[policy]["name"] === type) { - return JSON.parse(JSON.stringify(msProperties[policy]["jsonRepresentation"])); - } + getMicroServiceJsonRepresentationForName(name) { + var msConfig = this.getMicroServiceForName(name); + if (msConfig !== null) { + return msConfig["jsonRepresentation"]; } return null; } @@ -110,6 +106,10 @@ export default class LoopCache { return this.loopJsonCache.loopLogs; } + getComputedState() { + return this.loopJsonCache.lastComputedState; + } + getComponentStates() { return this.loopJsonCache.components; } diff --git a/ui-react/src/api/LoopService.js b/ui-react/src/api/LoopService.js index 2813a7c08..031ec638f 100644 --- a/ui-react/src/api/LoopService.js +++ b/ui-react/src/api/LoopService.js @@ -22,7 +22,7 @@ export default class LoopService { static getLoopNames() { - return fetch('/restservices/clds/v2/loop/getAllNames', { method: 'GET', credentials: 'include', }) + return fetch('/restservices/clds/v2/loop/getAllNames', { method: 'GET', credentials: 'same-origin', }) .then(function (response) { console.debug("GetLoopNames response received: ", response.status); if (response.ok) { @@ -42,9 +42,9 @@ export default class LoopService { return fetch('/restservices/clds/v2/loop/' + loopName, { method: 'GET', headers: { - "Content-Type": "application/json" + "Content-Type": "application/json", }, - credentials: 'include', + credentials: 'same-origin', }) .then(function (response) { console.debug("GetLoop response received: ", response.status); @@ -64,7 +64,7 @@ export default class LoopService { static getSvg(loopName) { return fetch('/restservices/clds/v2/loop/svgRepresentation/' + loopName, { method: 'GET', - credentials: 'include', + credentials: 'same-origin', }) .then(function (response) { console.debug("svgRepresentation response received: ", response.status); @@ -80,4 +80,52 @@ export default class LoopService { return ""; }); } + + static setMicroServiceProperties(loopName, jsonData) { + return fetch('/restservices/clds/v2/loop/updateMicroservicePolicy/' + loopName, { + method: 'POST', + credentials: 'same-origin', + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(jsonData), + }) + .then(function (response) { + console.debug("updateMicroservicePolicy response received: ", response.status); + if (response.ok) { + return response.text(); + } else { + console.error("updateMicroservicePolicy query failed"); + return ""; + } + }) + .catch(function (error) { + console.error("updateMicroservicePolicy error received", error); + return ""; + }); + } + + static updateGlobalProperties(loopName, jsonData) { + return fetch('/restservices/clds/v2/loop/updateGlobalProperties/' + loopName, { + method: 'POST', + credentials: 'same-origin', + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(jsonData), + }) + .then(function (response) { + console.debug("updateGlobalProperties response received: ", response.status); + if (response.ok) { + return response.text(); + } else { + console.error("updateGlobalProperties query failed"); + return ""; + } + }) + .catch(function (error) { + console.error("updateGlobalProperties error received", error); + return ""; + }); + } } diff --git a/ui-react/src/api/UserService.js b/ui-react/src/api/UserService.js index 8f53d7b8a..be21e692a 100644 --- a/ui-react/src/api/UserService.js +++ b/ui-react/src/api/UserService.js @@ -26,7 +26,7 @@ export default class UserService { static login() { return fetch('/restservices/clds/v1/user/getUser', { method: 'GET', - credentials: 'include', + credentials: 'same-origin', }) .then(function (response) { console.debug("getUser response received, status code:", response.status); @@ -47,5 +47,26 @@ export default class UserService { return UserService.notLoggedUserName; }); } -} + static getUserInfo() { + return fetch('/restservices/clds/v1/clds/cldsInfo', { + method: 'GET', + credentials: 'same-origin', + }) + .then(function (response) { + console.debug("getUserInfo response received, status code:", response.status); + if (response.ok) { + return response.json(); + } + }) + .then(function (data) { + console.info ("User info received:",data) + return data; + }) + .catch(function(error) { + console.warn("getUserInfo error received, user set to: ",UserService.notLoggedUserName); + console.error("getUserInfo error:",error); + return; + }); + } +} diff --git a/ui-react/src/components/dialogs/ConfigurationPolicy/ConfigurationPolicyModal.js b/ui-react/src/components/dialogs/ConfigurationPolicy/ConfigurationPolicyModal.js index 1a8b6e2c5..8178bf47f 100644 --- a/ui-react/src/components/dialogs/ConfigurationPolicy/ConfigurationPolicyModal.js +++ b/ui-react/src/components/dialogs/ConfigurationPolicy/ConfigurationPolicyModal.js @@ -24,8 +24,9 @@ import React from 'react' import Button from 'react-bootstrap/Button'; import Modal from 'react-bootstrap/Modal'; -import { LOOP_CACHE } from '../../../api/LoopCache' import styled from 'styled-components'; +import LoopService from '../../../api/LoopService'; +import JSONEditor from '@json-editor/json-editor'; const ModalStyled = styled(Modal)` background-color: transparent; @@ -33,23 +34,71 @@ const ModalStyled = styled(Modal)` export default class ConfigurationPolicyModal extends React.Component { + state = { + show: true, + loopCache: this.props.loopCache, + jsonEditor: null, + componentName: "", + }; + constructor(props, context) { super(props, context); - this.handleClose = this.handleClose.bind(this); + this.handleSave = this.handleSave.bind(this); + this.renderJsonEditor = this.renderJsonEditor.bind(this); + this.state.componentName = props.match.params.componentName; + } - this.state = { - show: true, - }; + handleSave() { + var errors = this.state.jsonEditor.validate(); + var editorData = this.state.jsonEditor.getValue(); + + if (errors.length !== 0) { + console.error("Errors detected during config policy data validation ", errors); + } + else { + console.info("NO validation errors found in config policy data"); + this.state.loopCache.updateMicroServiceProperties(this.state.componentName, editorData[0]); + LoopService.setMicroServiceProperties(this.state.loopCache.getLoopName(), this.state.loopCache.getMicroServiceForName(this.state.componentName)); + } + + this.setState({ show: false }); + this.props.history.push('/'); } handleClose() { this.setState({ show: false }); - this.props.history.push('/') + this.props.history.push('/'); } + componentDidMount() { + this.renderJsonEditor(); + } + + renderJsonEditor() { + console.debug("Rendering ConfigurationPolicyModal ", this.state.componentName); + var toscaModel = this.state.loopCache.getMicroServiceJsonRepresentationForName(this.state.componentName); + if (toscaModel == null) { + return; + } + var editorData = this.state.loopCache.getMicroServicePropertiesForName(this.state.componentName); + + JSONEditor.defaults.options.theme = 'bootstrap4'; + //JSONEditor.defaults.options.iconlib = 'bootstrap2'; + JSONEditor.defaults.options.object_layout = 'grid'; + JSONEditor.defaults.options.disable_properties = true; + JSONEditor.defaults.options.disable_edit_json = false; + JSONEditor.defaults.options.disable_array_reorder = true; + JSONEditor.defaults.options.disable_array_delete_last_row = true; + JSONEditor.defaults.options.disable_array_delete_all_rows = false; + JSONEditor.defaults.options.show_errors = 'always'; + this.setState({ + jsonEditor: new JSONEditor(document.getElementById("editor"), + { schema: toscaModel.schema, startval: editorData }) + }) + } render() { return ( @@ -58,15 +107,14 @@ export default class ConfigurationPolicyModal extends React.Component { <Modal.Title>Configuration policies</Modal.Title> </Modal.Header> <Modal.Body> - - + <div id="editor" /> </Modal.Body> <Modal.Footer> <Button variant="secondary" onClick={this.handleClose}> Close </Button> - <Button variant="primary" onClick={this.handleClose}> + <Button variant="primary" onClick={this.handleSave}> Save Changes </Button> </Modal.Footer> diff --git a/ui-react/src/components/dialogs/LoopProperties.js b/ui-react/src/components/dialogs/LoopProperties.js new file mode 100644 index 000000000..2cde8d46b --- /dev/null +++ b/ui-react/src/components/dialogs/LoopProperties.js @@ -0,0 +1,112 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP CLAMP + * ================================================================================ + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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 React from 'react' +import Button from 'react-bootstrap/Button'; +import Modal from 'react-bootstrap/Modal'; +import Form from 'react-bootstrap/Form'; +import styled from 'styled-components'; +import LoopService from '../../api/LoopService'; + +const ModalStyled = styled(Modal)` + background-color: transparent; +` + +export default class LoopProperties extends React.Component { + + formProps = {dcaeDeployParameters: { + "location_id": "", + "service_id": "", + "policy_id": "TCA_h2NMX_v1_0_ResourceInstanceName1_tca" + }}; + + constructor(props, context) { + super(props, context); + + this.handleClose = this.handleClose.bind(this); + this.handleChange = this.handleChange.bind(this); + this.saveProperties = this.saveProperties.bind(this); + this.initialValues = this.initialValues.bind(this); + this.state = { + show: true, + loopName: 'LOOP_h2NMX_v1_0_ResourceInstanceName1_tca', + form: this.formProps + }; + + } + initialValues() { + const updatedForm = this.state.form; + Object.keys(updatedForm).forEach((key) => { + if (key === 'dcaeDeployParameters') { + updatedForm[key] = JSON.stringify(this.state.form[key]); + } else { + updatedForm[key] = this.state.form[key]; + } + this.setState({ form: updatedForm }); + }); + } + handleClose() { + this.props.history.push('/'); + } + handleChange(e) { + const formUpdated = this.state.form; + formUpdated[e.target.name] = e.target.value; + this.setState({ form: formUpdated }); + }; + saveProperties(event) { + // translate the deploymentParameter into jsonFormat at submit time + const updatedForm = this.state.form; + Object.keys(updatedForm).forEach((key) => { + if (key === 'dcaeDeployParameters') { + try { + let value = JSON.parse(updatedForm[key]); + updatedForm[key] = value; + // save Properties + this.setState( {form: updatedForm} ); + LoopService.updateGlobalProperties(this.state.loopName, this.state.form); + this.props.history.push('/'); + } catch (error) { + alert("Deployment Parameters is not in good Json format. Please correct it."); + } + } + }); + } + render() { + return ( + <ModalStyled size="lg" show={this.state.show} onHide={this.handleClose} onEntered={this.initialValues}> + <Modal.Header closeButton> + <Modal.Title>Model Properties</Modal.Title> + </Modal.Header> + <Modal.Body> + <Form.Group controlId="exampleForm.ControlTextarea1"> + <Form.Label>Deploy Parameters</Form.Label> + <Form.Control as="textarea" rows="3" name="dcaeDeployParameters" onChange={this.handleChange} defaultValue={this.state.form["dcaeDeployParameters"]}/> + </Form.Group> + </Modal.Body> + <Modal.Footer> + <Button variant="secondary" type="null" onClick={this.handleClose}>Cacel</Button> + <Button variant="primary" type="submit" onClick={this.saveProperties}>Save</Button> + </Modal.Footer> + </ModalStyled> + ); + } +} diff --git a/ui-react/src/components/dialogs/OpenLoop/OpenLoopModal.js b/ui-react/src/components/dialogs/OpenLoop/OpenLoopModal.js index 6986209d4..0bf71580a 100644 --- a/ui-react/src/components/dialogs/OpenLoop/OpenLoopModal.js +++ b/ui-react/src/components/dialogs/OpenLoop/OpenLoopModal.js @@ -54,7 +54,7 @@ export default class OpenLoopModal extends React.Component { }; } - componentDidMount() { + componentWillMount() { this.getLoopNames(); } @@ -110,7 +110,7 @@ export default class OpenLoopModal extends React.Component { </Modal.Body> <Modal.Footer> <Button variant="secondary" type="null" onClick={this.handleClose}>Cancel</Button> - <Button variant="primary" type="submit" onClick={this.handleOpen}>OK</Button> + <Button variant="primary" type="submit" onClick={this.handleOpen}>Open</Button> </Modal.Footer> </ModalStyled> diff --git a/ui-react/src/components/dialogs/OperationalPolicy/OperationalPolicyModal.js b/ui-react/src/components/dialogs/OperationalPolicy/OperationalPolicyModal.js index 6822f3ff3..2a812c877 100644 --- a/ui-react/src/components/dialogs/OperationalPolicy/OperationalPolicyModal.js +++ b/ui-react/src/components/dialogs/OperationalPolicy/OperationalPolicyModal.js @@ -217,7 +217,7 @@ export default class OperationalPolicyModal extends React.Component { </div> <span id="formSpan" style={{ display: 'none' }}> - <form className="policyProperties" className="form-horizontal" + <form className="policyProperties form-horizontal" style={{ border: '2px dotted gray' }} title="Operational Policy Properties"> <div className="form-group clearfix"> @@ -350,7 +350,7 @@ export default class OperationalPolicyModal extends React.Component { </div> </div> </form> - <form className="policyTarget" className="form-horizontal" + <form className="policyTarget form-horizontal" title="Operational Policy Target" style={{ border: '2px dotted gray' }}> <div className="form-group clearfix"> <label htmlFor="type" className="col-sm-4 control-label"> Target @@ -437,7 +437,7 @@ export default class OperationalPolicyModal extends React.Component { </select> </div> </div> - <form className="guardProperties" className="form-horizontal" + <form className="guardProperties form-horizontal" title="Guard policy associated" style={{ border: '2px dotted gray' }}> <div className="form-group clearfix withnote"> diff --git a/ui-react/src/components/dialogs/UserInfo.js b/ui-react/src/components/dialogs/UserInfo.js new file mode 100644 index 000000000..a8ef717b3 --- /dev/null +++ b/ui-react/src/components/dialogs/UserInfo.js @@ -0,0 +1,161 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP CLAMP + * ================================================================================ + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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 React from 'react' +import Button from 'react-bootstrap/Button'; +import Modal from 'react-bootstrap/Modal'; +import Form from 'react-bootstrap/Form'; +import Row from 'react-bootstrap/Row'; +import Col from 'react-bootstrap/Col'; +import styled from 'styled-components'; +import UserService from '../../api/UserService'; + +const ModalStyled = styled(Modal)` + background-color: transparent; +` + +export default class UserInfo extends React.Component { + + constructor(props, context) { + super(props, context); + + this.handleClose = this.handleClose.bind(this); + this.initialValues = this.initialValues.bind(this); + this.renderReadTemplatePermission = this.renderReadTemplatePermission.bind(this); + this.renderReadModelPermission = this.renderReadModelPermission.bind(this); + this.renderReadToscaPermission = this.renderReadToscaPermission.bind(this); + this.renderUpdateTemplatePermission = this.renderUpdateTemplatePermission.bind(this); + this.renderUpdateModelPermission = this.renderUpdateModelPermission.bind(this); + this.renderUpdateToscaPermission = this.renderUpdateToscaPermission.bind(this); + this.renderUserName = this.renderUserName.bind(this); + this.state = { + show: true, + userInfo: {permissionReadTemplate: true, + permissionReadCl: true, + permissionReadTosca: true, + permissionUpdateCl: true, + permissionUpdateTemplate: true, + permissionUpdateTosca: true, + userName: 'admin', + cldsVersion: '1.0.0' + } + }; + + } + initialValues() { + UserService.getUserInfo().then(userInfo => { + this.setState({ userInfo: userInfo }) + }); + } + handleClose() { + this.props.history.push('/'); + } + renderReadTemplatePermission() { + if (this.state.userInfo["permissionReadTemplate"]) { + return <Form.Control plaintext readOnly defaultValue="Read Template" /> + } else { + return; + } + } + renderReadModelPermission() { + if (this.state.userInfo["permissionReadCl"]) { + return <Form.Control plaintext readOnly defaultValue="Read Model" /> + } else { + return; + } + } + renderReadToscaPermission() { + if (this.state.userInfo["permissionReadTosca"]) { + return <Form.Control plaintext readOnly defaultValue="Read Tosca" /> + } else { + return; + } + } + renderUpdateTemplatePermission() { + if (this.state.userInfo["permissionUpdateTemplate"]) { + return <Form.Control plaintext readOnly defaultValue="Edit Template" /> + } else { + return; + } + } + renderUpdateModelPermission() { + if (this.state.userInfo["permissionUpdateCl"]) { + return <Form.Control plaintext readOnly defaultValue="Edit Model" /> + } else { + return; + } + } + renderUpdateToscaPermission() { + if (this.state.userInfo["permissionUpdateTosca"]) { + return <Form.Control plaintext readOnly defaultValue="Edit Tosca" /> + } else { + return; + } + } + renderUserName() { + if (this.state.userInfo["userName"]) { + return <Form.Control plaintext readOnly defaultValue={this.state.userInfo["userName"]} /> + } else { + return; + } + } + renderVersion() { + if (this.state.userInfo["cldsVersion"]) { + return <Form.Control plaintext readOnly defaultValue={this.state.userInfo["cldsVersion"]} /> + } else { + return; + } + } + render() { + return ( + <ModalStyled size="lg" show={this.state.show} onHide={this.handleClose} onEntered={this.initialValues}> + <Modal.Header closeButton> + <Modal.Title>User Info</Modal.Title> + </Modal.Header> + <Modal.Body> + <Form.Group as={Row} controlId="userName"> + <Form.Label column sm="3">Current User:</Form.Label> + <Col>{this.renderUserName()}</Col> + </Form.Group> + <Form.Group as={Row} controlId="cldsVersion"> + <Form.Label column sm="3">CLDS Version:</Form.Label> + <Col>{this.renderVersion()}</Col> + </Form.Group> + <Form.Group as={Row} controlId="userPermissions"> + <Form.Label column sm="3">User Permissions:</Form.Label> + <Col> + {this.renderReadTemplatePermission()} + {this.renderReadModelPermission()} + {this.renderReadToscaPermission()} + {this.renderUpdateTemplatePermission()} + {this.renderUpdateModelPermission()} + {this.renderUpdateToscaPermission()} + </Col> + </Form.Group> + </Modal.Body> + <Modal.Footer> + <Button variant="secondary" type="null" onClick={this.handleClose}>Cacel</Button> + </Modal.Footer> + </ModalStyled> + ); + } +} diff --git a/ui-react/src/components/loop_viewer/logs/LoopLogs.js b/ui-react/src/components/loop_viewer/logs/LoopLogs.js new file mode 100644 index 000000000..b6a777a40 --- /dev/null +++ b/ui-react/src/components/loop_viewer/logs/LoopLogs.js @@ -0,0 +1,97 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP CLAMP + * ================================================================================ + * Copyright (C) 2019 AT&T Intellectual Property. All rights + * reserved. + * ================================================================================ + * 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 React from 'react'; +import Table from 'react-bootstrap/Table'; +import LoopCache from '../../../api/LoopCache'; +import styled from 'styled-components'; + +const LoopLogsHeaderDivStyled = styled.div` + background-color: ${props => props.theme.loopViewerHeaderBackgroundColor}; + padding: 10px 10px; + color: ${props => props.theme.loopViewerHeaderFontColor}; +` +const TableStyled = styled(Table)` + + overflow: auto; +` +const TableRow = ({ logRow }) => ( + <tr> + <td>{logRow.logInstant}</td> + <td>{logRow.logType}</td> + <td>{logRow.logComponent}</td> + <td>{logRow.message}</td> + </tr> + +) + +export default class LoopLogs extends React.Component { + + state = { + loopCache: new LoopCache({}), + } + constructor(props) { + super(props); + this.renderLogs = this.renderLogs.bind(this); + this.state.loopCache = props.loopCache; + } + + shouldComponentUpdate(nextProps, nextState) { + return this.state.loopCache !== nextState.loopCache; + } + + componentWillReceiveProps(newProps) { + this.setState({ + loopCache: newProps.loopCache, + }); + } + + renderLogs() { + if (this.state.loopCache.getLoopLogsArray() != null) { + return ( + this.state.loopCache.getLoopLogsArray().map(row => <TableRow logRow={row} />) + ) + } + } + + render() { + return ( + <LoopLogsHeaderDivStyled> + <label>Loop Logs</label> + <TableStyled striped hover variant responsive> + <thead> + <tr> + <th><span align="left">Date</span></th> + <th><span align="left">Type</span></th> + <th><span align="left">Component</span></th> + <th><span align="right">Log</span></th> + </tr> + </thead> + <tbody> + {this.renderLogs()} + </tbody> + </TableStyled> + </LoopLogsHeaderDivStyled> + + ); + } +} diff --git a/ui-react/src/components/loop_viewer/status/LoopStatus.js b/ui-react/src/components/loop_viewer/status/LoopStatus.js index f904d6740..141a41f51 100644 --- a/ui-react/src/components/loop_viewer/status/LoopStatus.js +++ b/ui-react/src/components/loop_viewer/status/LoopStatus.js @@ -22,34 +22,84 @@ */ import React from 'react'; import Table from 'react-bootstrap/Table'; -import './LoopStatus.css'; +import styled from 'styled-components'; +import LoopCache from '../../../api/LoopCache'; + +const LoopStatusViewDivStyled = styled.div` + background-color: ${props => props.theme.loopViewerHeaderBackgroundColor}; + padding: 10px 10px; + color: ${props => props.theme.loopViewerHeaderFontColor}; +` + +const TableStyled = styled(Table)` + overflow: auto; +` + +const TableRow = ({ statusRow }) => ( + <tr> + <td>{statusRow.componentName}</td> + <td>{statusRow.stateName}</td> + <td>{statusRow.description}</td> + </tr> + +) export default class LoopStatus extends React.Component { - render() { - return ( - <div> - <span id="status_clds" className="status_title">Status: - <span className="status"> TestStatus </span> - </span> - - <div className="status_table"> - <Table striped hover> - <thead> - <tr> - <th><span align="left" className="text">ComponentState</span></th> - <th><span align="left" className="text">Description</span></th> - </tr> - </thead> - <tbody> - <tr> - <td className="row_30_per">long test State</td> - <td className="row_70_per">test description very very very long description</td> - </tr> - </tbody> - </Table> - </div> - </div> - ); - } + state = { + loopCache: new LoopCache({}), + } + + constructor(props) { + super(props); + this.renderStatus = this.renderStatus.bind(this); + this.state.loopCache = props.loopCache; + } + + + renderStatus() { + if (this.state.loopCache.getComponentStates() != null) { + return Object.keys(this.state.loopCache.getComponentStates()).map((key) => { + console.debug("Adding status for: ",key); + var res={} + res[key]=this.state.loopCache.getComponentStates()[key]; + return (<TableRow statusRow={{'componentName':key,'stateName':this.state.loopCache.getComponentStates()[key].componentState.stateName,'description':this.state.loopCache.getComponentStates()[key].componentState.description}} />) + }) + + } + } + + shouldComponentUpdate(nextProps, nextState) { + return this.state.loopCache !== nextState.loopCache; + } + + componentWillReceiveProps(newProps) { + this.setState({ + loopCache: newProps.loopCache, + }); + } + + render() { + return ( + <LoopStatusViewDivStyled> + <label>Loop Status: {this.state.loopCache.getComputedState()} + </label> + + <div > + <TableStyled striped hover variant responsive> + <thead> + <tr> + <th><span align="left">Component Name</span></th> + <th><span align="left">Component State</span></th> + <th><span align="right">Description</span></th> + </tr> + </thead> + <tbody> + {this.renderStatus()} + </tbody> + </TableStyled> + </div> + </LoopStatusViewDivStyled> + ); + } } diff --git a/ui-react/src/components/loop_viewer/svg/LoopComponentConverter.js b/ui-react/src/components/loop_viewer/svg/LoopComponentConverter.js new file mode 100644 index 000000000..a409d2cd0 --- /dev/null +++ b/ui-react/src/components/loop_viewer/svg/LoopComponentConverter.js @@ -0,0 +1,18 @@ +export default class LoopComponentConverter { + + static buildMapOfComponents(loopCache) { + var componentsMap = new Map([]); + if (typeof (loopCache.getMicroServicePolicies()) !== "undefined") { + loopCache.getMicroServicePolicies().forEach(ms => { + componentsMap.set(ms.name, "/configurationPolicyModal/"+ms.name); + }) + } + if (typeof (loopCache.getOperationalPolicies()) !== "undefined") { + loopCache.getOperationalPolicies().forEach(op => { + componentsMap.set(op.name, "/operationalPolicyModal"); + }) + } + componentsMap.set("OperationalPolicy","/operationalPolicyModal"); + return componentsMap; + } +} diff --git a/ui-react/src/components/loop_viewer/svg/LoopSvg.js b/ui-react/src/components/loop_viewer/svg/LoopSvg.js index 2858ccd8a..3ac2f31fd 100644 --- a/ui-react/src/components/loop_viewer/svg/LoopSvg.js +++ b/ui-react/src/components/loop_viewer/svg/LoopSvg.js @@ -22,34 +22,39 @@ */ import React from 'react'; import styled from 'styled-components'; +import LoopCache from '../../../api/LoopCache'; import { withRouter } from "react-router"; -import LoopCache from '../../../api/LoopCache' -import LoopService from '../../../api/LoopService' +import LoopService from '../../../api/LoopService'; +import LoopComponentConverter from './LoopComponentConverter'; const LoopViewSvgDivStyled = styled.div` overflow: hidden; background-color: ${props => (props.theme.loopViewerBackgroundColor)}; border: 1px solid; border-color: ${props => (props.theme.loopViewerHeaderColor)}; - height: 50%; + margin-left: auto; + margin-right:auto; + text-align: center; + ` class LoopViewSvg extends React.Component { static emptySvg = "<svg><text x=\"20\" y=\"40\">No LOOP (SVG)</text></svg>"; - static operationalPolicyDataElementId = "OperationalPolicy"; - state = { svgContent: LoopViewSvg.emptySvg, - loopCache: this.props.loopCache, + loopCache: new LoopCache({}), + componentModalMapping: new Map([]), } constructor(props) { super(props); - this.state.loopCache = props.loopCache; this.handleSvgClick = this.handleSvgClick.bind(this); this.getSvg = this.getSvg.bind(this); + this.state.loopCache = props.loopCache; + this.state.componentModalMapping = LoopComponentConverter.buildMapOfComponents(props.loopCache); + this.getSvg(props.loopCache.getLoopName()); } shouldComponentUpdate(nextProps, nextState) { @@ -57,40 +62,36 @@ class LoopViewSvg extends React.Component { } componentWillReceiveProps(newProps) { - this.state.loopCache = newProps.loopCache; - this.getSvg(); - } + this.setState({ + loopCache: newProps.loopCache, + componentModalMapping: LoopComponentConverter.buildMapOfComponents(newProps.loopCache), - componentDidMount() { - this.getSvg(); + }); + this.getSvg(newProps.loopCache.getLoopName()); } - getSvg() { - LoopService.getSvg(this.state.loopCache.getLoopName()).then(svgXml => { - if (svgXml.length != 0) { - this.setState({ svgContent: svgXml }) - } else { - this.setState({ svgContent: LoopViewSvg.emptySvg }) - } - }); + getSvg(loopName) { + if (typeof loopName !== "undefined") { + LoopService.getSvg(loopName).then(svgXml => { + if (svgXml.length !== 0) { + this.setState({ svgContent: svgXml }) + } else { + this.setState({ svgContent: LoopViewSvg.emptySvg }) + } + }); + } } handleSvgClick(event) { console.debug("svg click event received"); var elementName = event.target.parentNode.parentNode.parentNode.getAttribute('data-element-id'); console.info("SVG element clicked", elementName); - if (typeof elementName === "undefined" || elementName === "VES") { - return; - } else if (elementName === LoopViewSvg.operationalPolicyDataElementId) { - this.props.history.push('/operationalPolicyModal'); - } else { - this.props.history.push('/configurationPolicyModal'); - } + this.props.history.push(this.state.componentModalMapping.get(elementName)); } render() { return ( - <LoopViewSvgDivStyled id="loop_svg" dangerouslySetInnerHTML={{ __html: this.state.svgContent }} onClick={this.handleSvgClick}> + <LoopViewSvgDivStyled dangerouslySetInnerHTML={{ __html: this.state.svgContent }} onClick={this.handleSvgClick}> </LoopViewSvgDivStyled> ); diff --git a/ui-react/src/components/menu/MenuBar.js b/ui-react/src/components/menu/MenuBar.js index 1a7c5d4e4..491cc675d 100644 --- a/ui-react/src/components/menu/MenuBar.js +++ b/ui-react/src/components/menu/MenuBar.js @@ -35,14 +35,13 @@ const StyledNavDropdownItem = styled(NavDropdown.Item)` `; export default class MenuBar extends React.Component { - render () { return ( <Navbar.Collapse id="basic-navbar-nav" className="justify-content-center"> <NavDropdown title="Closed Loop" id="basic-nav-dropdown"> <StyledNavDropdownItem href="/openLoop">Open CL</StyledNavDropdownItem> - <StyledNavDropdownItem href="#action/3.2">Properties CL</StyledNavDropdownItem> - <StyledNavDropdownItem href="#action/3.3">Close Model</StyledNavDropdownItem> + <StyledNavDropdownItem href="/loopProperties">Properties CL</StyledNavDropdownItem> + <StyledNavDropdownItem href="/closeLoop">Close Model</StyledNavDropdownItem> </NavDropdown> <NavDropdown title="Manage" id="basic-nav-dropdown"> <StyledNavDropdownItem href="/operationalPolicyModal">Submit</StyledNavDropdownItem> diff --git a/ui-react/src/setupTests.js b/ui-react/src/setupTests.js new file mode 100644 index 000000000..fc7b0dce1 --- /dev/null +++ b/ui-react/src/setupTests.js @@ -0,0 +1,4 @@ +import Enzyme from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; + +Enzyme.configure({ adapter: new Adapter() }); diff --git a/ui-react/src/theme/globalStyle.js b/ui-react/src/theme/globalStyle.js index 1630c5074..08630cbd9 100644 --- a/ui-react/src/theme/globalStyle.js +++ b/ui-react/src/theme/globalStyle.js @@ -56,13 +56,12 @@ export const GlobalClampStyle = createGlobalStyle` font-size: ${props => props.theme.fontSize}; border-radius: 4px; color: ${props => props.theme.fontNormal}; - background-color: ${props => (props.theme.backgroundColor)}; + background-color: ${props => (props.theme.backgroundColor)}; + margin-top: 1px; } - + svg { - padding: 10px; overflow: hidden; - background-color: ${props => (props.theme.loopViewerBackgroundColor)}; width: 100%; height: 100%; } @@ -75,13 +74,13 @@ export const DefaultClampTheme = { fontDark: '#888888', fontHighlight: '#ffff00', fontNormal: 'black', - + backgroundColor: '#eeeeee', fontFamily: 'Arial, Sans-serif', fontSize: '15px', - - loopViewerBackgroundColor: 'white', - loopViewerFontColor: 'yellow', - loopViewerHeaderBackgroundColor: '#337ab7', - loopViewerHeaderFontColor: 'white', + + loopViewerBackgroundColor: 'white', + loopViewerFontColor: 'yellow', + loopViewerHeaderBackgroundColor: '#337ab7', + loopViewerHeaderFontColor: 'white', }; |