From 87111eea8588fb30936a8f876f5f3feed61e7b8a Mon Sep 17 00:00:00 2001 From: brunomilitzer Date: Tue, 18 May 2021 12:50:32 +0100 Subject: React Front-End UI This commit is to move the React front end files from the Clamp Policy Repo to the Onap Gui Policy Repo. Also created the added Maven featue to compile the React project via Node and NPM, and copy the production files to the target directory. Fixed in gitignore that was ignoring the contents inside the logs/ directory. Reformated JS spacing files from 4 spaces to 2 spaces. Fixed Broken JEST test Applied Jim's Code Review as well updated Node Version from 12.18 to 14.17 accross the modules. Unfortunately cannot apply NPM version since it broke npm install. Fixed ONAP Job Builder Linting Error Applied Jim's Second Code Review Issue-ID: POLICY-3218 Signed-off-by: brunomilitzer Change-Id: I01f95c350d27d72f941c835592fd596472601d6e --- .../src/components/dialogs/Policy/PolicyEditor.js | 217 +++++++++++ .../src/components/dialogs/Policy/PolicyModal.js | 364 ++++++++++++++++++ .../components/dialogs/Policy/PolicyModal.test.js | 128 ++++++ .../src/components/dialogs/Policy/ToscaViewer.js | 67 ++++ .../components/dialogs/Policy/ViewAllPolicies.js | 427 +++++++++++++++++++++ 5 files changed, 1203 insertions(+) create mode 100644 gui-clamp/ui-react/src/components/dialogs/Policy/PolicyEditor.js create mode 100644 gui-clamp/ui-react/src/components/dialogs/Policy/PolicyModal.js create mode 100644 gui-clamp/ui-react/src/components/dialogs/Policy/PolicyModal.test.js create mode 100644 gui-clamp/ui-react/src/components/dialogs/Policy/ToscaViewer.js create mode 100644 gui-clamp/ui-react/src/components/dialogs/Policy/ViewAllPolicies.js (limited to 'gui-clamp/ui-react/src/components/dialogs/Policy') diff --git a/gui-clamp/ui-react/src/components/dialogs/Policy/PolicyEditor.js b/gui-clamp/ui-react/src/components/dialogs/Policy/PolicyEditor.js new file mode 100644 index 0000000..525d66b --- /dev/null +++ b/gui-clamp/ui-react/src/components/dialogs/Policy/PolicyEditor.js @@ -0,0 +1,217 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP POLICY-CLAMP + * ================================================================================ + * Copyright (C) 2021 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 PolicyToscaService from '../../../api/PolicyToscaService'; +import { JSONEditor } from '@json-editor/json-editor/dist/nonmin/jsoneditor.js'; +import "@fortawesome/fontawesome-free/css/all.css" +import styled from 'styled-components'; +import Button from 'react-bootstrap/Button'; +import TextField from '@material-ui/core/TextField'; +import Alert from 'react-bootstrap/Alert'; +import PolicyService from '../../../api/PolicyService'; +import OnapUtils from '../../../utils/OnapUtils'; +import uuid from 'react-uuid'; + +//const JSONEditor = require("@json-editor/json-editor").JSONEditor; +const DivWhiteSpaceStyled = styled.div` + white-space: pre; +` + +const JsonEditorDiv = styled.div` + margin-top: 20px; + background-color: ${ props => props.theme.loopViewerBackgroundColor }; + text-align: justify; + font-size: ${ props => props.theme.policyEditorFontSize }; + border: 1px solid #C0C0C0; +` +const PanelDiv = styled.div` + margin-top: 20px; + text-align: justify; + font-size: ${ props => props.theme.policyEditorFontSize }; + background-color: ${ props => props.theme.loopViewerBackgroundColor }; +` + +export default class PolicyEditor extends React.Component { + + state = { + policyModelType: this.props.policyModelType, + policyModelTypeVersion: this.props.policyModelTypeVersion, + policyName: (typeof this.props.policyName !== "undefined") ? this.props.policyName : "org.onap.policy.new", + policyVersion: (typeof this.props.policyVersion !== "undefined") ? this.props.policyVersion : "0.0.1", + policyProperties: this.props.policyProperties, + showSuccessAlert: false, + showFailAlert: false, + jsonEditor: null, + jsonEditorDivId: uuid(), + } + + constructor(props, context) { + super(props, context); + this.createJsonEditor = this.createJsonEditor.bind(this); + this.getToscaModelForPolicy = this.getToscaModelForPolicy.bind(this); + this.disableAlert = this.disableAlert.bind(this); + this.handleCreateNewVersion = this.handleCreateNewVersion.bind(this); + this.handleChangePolicyName = this.handleChangePolicyName.bind(this); + this.handleChangePolicyVersion = this.handleChangePolicyVersion.bind(this); + } + + disableAlert() { + this.setState({ showSuccessAlert: false, showFailAlert: false }); + } + + customValidation(editorData) { + // method for sub-classes to override with customized validation + return []; + } + + handleCreateNewVersion() { + var editorData = this.state.jsonEditor.getValue(); + var errors = this.state.jsonEditor.validate(); + errors = errors.concat(this.customValidation(editorData)); + + if (errors.length !== 0) { + console.error("Errors detected during policy data validation ", errors); + this.setState({ + showFailAlert: true, + showMessage: 'Errors detected during policy data validation:\n' + OnapUtils.jsonEditorErrorFormatter(errors) + }); + return; + } else { + console.info("NO validation errors found in policy data"); + PolicyService.createNewPolicy(this.state.policyModelType, this.state.policyModelTypeVersion, + this.state.policyName, this.state.policyVersion, editorData).then(respPolicyCreation => { + if (typeof (respPolicyCreation) === "undefined") { + //it indicates a failure + this.setState({ + showFailAlert: true, + showMessage: 'Policy Creation Failure' + }); + } else { + this.setState({ + showSuccessAlert: true, + showMessage: 'Policy ' + this.state.policyName + '/' + this.state.policyVersion + ' created successfully' + }); + this.props.policyUpdateFunction(); + } + }) + } + } + + bumpVersion(versionToBump) { + let semVer = versionToBump.split("."); + return parseInt(semVer[0]) + 1 + "." + semVer[1] + "." + semVer[2]; + } + + getToscaModelForPolicy() { + PolicyToscaService.getToscaPolicyModel(this.state.policyModelType, this.state.policyModelTypeVersion).then(respJsonPolicyTosca => { + if (respJsonPolicyTosca !== {}) { + this.setState({ + jsonSchemaPolicyTosca: respJsonPolicyTosca, + jsonEditor: this.createJsonEditor(respJsonPolicyTosca, this.state.policyProperties), + }) + } + }); + } + + componentDidMount() { + this.getToscaModelForPolicy(); + } + + createJsonEditor(toscaModel, editorData) { + /*JSONEditor.defaults.themes.myBootstrap4 = JSONEditor.defaults.themes.bootstrap4.extend({ + getTab: function(text,tabId) { + var liel = document.createElement('li'); + liel.classList.add('nav-item'); + var ael = document.createElement("a"); + ael.classList.add("nav-link"); + ael.setAttribute("style",'padding:10px;max-width:160px;'); + ael.setAttribute("href", "#" + tabId); + ael.setAttribute('data-toggle', 'tab'); + text.setAttribute("style",'word-wrap:break-word;'); + ael.appendChild(text); + liel.appendChild(ael); + return liel; + } + });*/ + + return new JSONEditor(document.getElementById(this.state.jsonEditorDivId), + { + schema: toscaModel, + startval: editorData, + //theme: 'myBootstrap4', + theme: 'bootstrap4', + iconlib: 'fontawesome5', + object_layout: 'grid', + disable_properties: false, + disable_edit_json: false, + disable_array_reorder: true, + disable_array_delete_last_row: true, + disable_array_delete_all_rows: false, + array_controls_top: true, + keep_oneof_values: false, + collapsed: true, + show_errors: 'always', + display_required_only: false, + show_opt_in: false, + prompt_before_delete: true, + required_by_default: false + }) + } + + handleChangePolicyName(event) { + this.setState({ + policyName: event.target.value, + }); + } + + handleChangePolicyVersion(event) { + this.setState({ + policyVersion: event.target.value, + }); + } + + render() { + return ( + + + + { this.state.showMessage } + + + + + { this.state.showMessage } + + + + + + + + ); + } +} diff --git a/gui-clamp/ui-react/src/components/dialogs/Policy/PolicyModal.js b/gui-clamp/ui-react/src/components/dialogs/Policy/PolicyModal.js new file mode 100644 index 0000000..105b2c5 --- /dev/null +++ b/gui-clamp/ui-react/src/components/dialogs/Policy/PolicyModal.js @@ -0,0 +1,364 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP POLICY-CLAMP + * ================================================================================ + * Copyright (C) 2020-2021 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 Form from 'react-bootstrap/Form'; +import Col from 'react-bootstrap/Col'; +import Row from 'react-bootstrap/Row'; +import Select from 'react-select'; +import Modal from 'react-bootstrap/Modal'; +import styled from 'styled-components'; +import LoopService from '../../../api/LoopService'; +import LoopCache from '../../../api/LoopCache'; +import { JSONEditor } from '@json-editor/json-editor/dist/jsoneditor.js'; +import "@fortawesome/fontawesome-free/css/all.css" +import Alert from 'react-bootstrap/Alert'; +import OnapConstant from '../../../utils/OnapConstants'; +import OnapUtils from '../../../utils/OnapUtils'; + +const ModalStyled = styled(Modal)` + background-color: transparent; +` + +const DivWhiteSpaceStyled = styled.div` + white-space: pre; +` + +export default class PolicyModal extends React.Component { + + state = { + show: true, + loopCache: this.props.loopCache, + jsonEditor: null, + policyName: this.props.match.params.policyName, + // This is to indicate whether it's an operational or config policy (in terms of loop instance) + policyInstanceType: this.props.match.params.policyInstanceType, + pdpGroup: null, + pdpGroupList: [], + pdpSubgroupList: [], + chosenPdpGroup: '', + chosenPdpSubgroup: '', + showSucAlert: false, + showFailAlert: false + }; + + 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.handlePdpGroupChange = this.handlePdpGroupChange.bind(this); + this.handlePdpSubgroupChange = this.handlePdpSubgroupChange.bind(this); + this.createJsonEditor = this.createJsonEditor.bind(this); + this.handleRefresh = this.handleRefresh.bind(this); + this.disableAlert = this.disableAlert.bind(this); + this.renderPdpGroupDropDown = this.renderPdpGroupDropDown.bind(this); + this.renderOpenLoopMessage = this.renderOpenLoopMessage.bind(this); + this.renderModalTitle = this.renderModalTitle.bind(this); + this.readOnly = props.readOnly !== undefined ? props.readOnly : false; + } + + handleSave() { + var editorData = this.state.jsonEditor.getValue(); + var errors = this.state.jsonEditor.validate(); + errors = errors.concat(this.customValidation(editorData, this.state.loopCache.getTemplateName())); + + if (errors.length !== 0) { + console.error("Errors detected during policy data validation ", errors); + this.setState({ + showFailAlert: true, + showMessage: 'Errors detected during policy data validation:\n' + OnapUtils.jsonEditorErrorFormatter(errors) + }); + return; + } else { + console.info("NO validation errors found in policy data"); + if (this.state.policyInstanceType === OnapConstant.microServiceType) { + this.state.loopCache.updateMicroServiceProperties(this.state.policyName, editorData); + this.state.loopCache.updateMicroServicePdpGroup(this.state.policyName, this.state.chosenPdpGroup, this.state.chosenPdpSubgroup); + LoopService.setMicroServiceProperties(this.state.loopCache.getLoopName(), this.state.loopCache.getMicroServiceForName(this.state.policyName)).then(resp => { + this.setState({ show: false }); + this.props.history.push('/'); + this.props.loadLoopFunction(this.state.loopCache.getLoopName()); + }); + } else if (this.state.policyInstanceType === OnapConstant.operationalPolicyType) { + this.state.loopCache.updateOperationalPolicyProperties(this.state.policyName, editorData); + this.state.loopCache.updateOperationalPolicyPdpGroup(this.state.policyName, this.state.chosenPdpGroup, this.state.chosenPdpSubgroup); + LoopService.setOperationalPolicyProperties(this.state.loopCache.getLoopName(), this.state.loopCache.getOperationalPolicies()).then(resp => { + this.setState({ show: false }); + this.props.history.push('/'); + this.props.loadLoopFunction(this.state.loopCache.getLoopName()); + }); + } + } + } + + customValidation(editorData, templateName) { + // method for sub-classes to override with customized validation + return []; + } + + handleClose() { + this.setState({ show: false }); + this.props.history.push('/'); + } + + componentDidMount() { + this.renderJsonEditor(); + } + + componentDidUpdate() { + if (this.state.showSucAlert === true || this.state.showFailAlert === true) { + let modalElement = document.getElementById("policyModal") + if (modalElement) { + modalElement.scrollTo(0, 0); + } + } + } + + createJsonEditor(toscaModel, editorData) { + /*JSONEditor.defaults.themes.myBootstrap4 = JSONEditor.defaults.themes.bootstrap4.extend({ + getTab: function(text,tabId) { + var liel = document.createElement('li'); + liel.classList.add('nav-item'); + var ael = document.createElement("a"); + ael.classList.add("nav-link"); + ael.setAttribute("style",'padding:10px;max-width:160px;'); + ael.setAttribute("href", "#" + tabId); + ael.setAttribute('data-toggle', 'tab'); + text.setAttribute("style",'word-wrap:break-word;'); + ael.appendChild(text); + liel.appendChild(ael); + return liel; + } + });*/ + return new JSONEditor(document.getElementById("editor"), + { + schema: toscaModel, + startval: editorData, + theme: 'bootstrap4', + iconlib: 'fontawesome5', + object_layout: 'grid', + disable_properties: false, + disable_edit_json: false, + disable_array_reorder: true, + disable_array_delete_last_row: true, + disable_array_delete_all_rows: false, + array_controls_top: true, + keep_oneof_values: false, + collapsed: true, + show_errors: 'always', + display_required_only: false, + show_opt_in: false, + prompt_before_delete: true, + required_by_default: false + }) + } + + renderJsonEditor() { + console.debug("Rendering PolicyModal ", this.state.policyName); + var toscaModel = {}; + var editorData = {}; + var pdpGroupValues = {}; + var chosenPdpGroupValue, chosenPdpSubgroupValue; + if (this.state.policyInstanceType === OnapConstant.microServiceType) { + toscaModel = this.state.loopCache.getMicroServiceJsonRepresentationForName(this.state.policyName); + editorData = this.state.loopCache.getMicroServicePropertiesForName(this.state.policyName); + pdpGroupValues = this.state.loopCache.getMicroServiceSupportedPdpGroup(this.state.policyName); + chosenPdpGroupValue = this.state.loopCache.getMicroServicePdpGroup(this.state.policyName); + chosenPdpSubgroupValue = this.state.loopCache.getMicroServicePdpSubgroup(this.state.policyName); + } else if (this.state.policyInstanceType === OnapConstant.operationalPolicyType) { + toscaModel = this.state.loopCache.getOperationalPolicyJsonRepresentationForName(this.state.policyName); + editorData = this.state.loopCache.getOperationalPolicyPropertiesForName(this.state.policyName); + pdpGroupValues = this.state.loopCache.getOperationalPolicySupportedPdpGroup(this.state.policyName); + chosenPdpGroupValue = this.state.loopCache.getOperationalPolicyPdpGroup(this.state.policyName); + chosenPdpSubgroupValue = this.state.loopCache.getOperationalPolicyPdpSubgroup(this.state.policyName); + } + + if (toscaModel == null) { + return; + } + + var pdpSubgroupValues = []; + if (typeof (chosenPdpGroupValue) !== "undefined") { + var selectedPdpGroup = pdpGroupValues.filter(entry => (Object.keys(entry)[0] === chosenPdpGroupValue)); + pdpSubgroupValues = selectedPdpGroup[0][chosenPdpGroupValue].map((pdpSubgroup) => { + return { label: pdpSubgroup, value: pdpSubgroup } + }); + } + this.setState({ + jsonEditor: this.createJsonEditor(toscaModel, editorData), + pdpGroup: pdpGroupValues, + pdpGroupList: pdpGroupValues.map(entry => { + return { label: Object.keys(entry)[0], value: Object.keys(entry)[0] }; + }), + pdpSubgroupList: pdpSubgroupValues, + chosenPdpGroup: chosenPdpGroupValue, + chosenPdpSubgroup: chosenPdpSubgroupValue + }) + } + + handlePdpGroupChange(e) { + var selectedPdpGroup = this.state.pdpGroup.filter(entry => (Object.keys(entry)[0] === e.value)); + const pdpSubgroupValues = selectedPdpGroup[0][e.value].map((pdpSubgroup) => { + return { label: pdpSubgroup, value: pdpSubgroup } + }); + if (this.state.chosenPdpGroup !== e.value) { + this.setState({ + chosenPdpGroup: e.value, + chosenPdpSubgroup: '', + pdpSubgroupList: pdpSubgroupValues + }); + } + } + + handlePdpSubgroupChange(e) { + this.setState({ chosenPdpSubgroup: e.value }); + } + + handleRefresh() { + var newLoopCache, toscaModel, editorData; + if (this.state.policyInstanceType === OnapConstant.microServiceType) { + LoopService.refreshMicroServicePolicyJson(this.state.loopCache.getLoopName(), this.state.policyName).then(data => { + newLoopCache = new LoopCache(data); + toscaModel = newLoopCache.getMicroServiceJsonRepresentationForName(this.state.policyName); + editorData = newLoopCache.getMicroServicePropertiesForName(this.state.policyName); + document.getElementById("editor").innerHTML = ""; + this.setState({ + loopCache: newLoopCache, + jsonEditor: this.createJsonEditor(toscaModel, editorData), + showSucAlert: true, + showMessage: "Successfully refreshed" + }); + }) + .catch(error => { + console.error("Error while refreshing the Operational Policy Json Representation"); + this.setState({ + showFailAlert: true, + showMessage: "Refreshing of UI failed" + }); + }); + } else if (this.state.policyInstanceType === OnapConstant.operationalPolicyType) { + LoopService.refreshOperationalPolicyJson(this.state.loopCache.getLoopName(), this.state.policyName).then(data => { + var newLoopCache = new LoopCache(data); + toscaModel = newLoopCache.getOperationalPolicyJsonRepresentationForName(this.state.policyName); + editorData = newLoopCache.getOperationalPolicyPropertiesForName(this.state.policyName); + document.getElementById("editor").innerHTML = ""; + this.setState({ + loopCache: newLoopCache, + jsonEditor: this.createJsonEditor(toscaModel, editorData), + showSucAlert: true, + showMessage: "Successfully refreshed" + }); + }) + .catch(error => { + console.error("Error while refreshing the Operational Policy Json Representation"); + this.setState({ + showFailAlert: true, + showMessage: "Refreshing of UI failed" + }); + }); + } + } + + disableAlert() { + this.setState({ showSucAlert: false, showFailAlert: false }); + } + + renderPdpGroupDropDown() { + if (this.state.policyInstanceType !== OnapConstant.operationalPolicyType || !this.state.loopCache.isOpenLoopTemplate()) { + return ( + + Pdp Group Info + + + + + ); + } + } + + renderOpenLoopMessage() { + if (this.state.policyInstanceType === OnapConstant.operationalPolicyType && this.state.loopCache.isOpenLoopTemplate()) { + return ( + "Operational Policy cannot be configured as only Open Loop is supported for this Template!" + ); + } + } + + renderModalTitle() { + return ( + Edit the policy + ); + } + + renderButton() { + var allElement = [()]; + if (this.state.policyInstanceType !== OnapConstant.operationalPolicyType || !this.state.loopCache.isOpenLoopTemplate()) { + allElement.push(( + + )); + allElement.push(( + + )); + } + return allElement; + } + + render() { + return ( + + + { this.renderModalTitle() } + + + + { this.state.showMessage } + + + + + { this.state.showMessage } + + + + { this.renderOpenLoopMessage() } +
+ { this.renderPdpGroupDropDown() } + + + { this.renderButton() } + + + ); + } +} diff --git a/gui-clamp/ui-react/src/components/dialogs/Policy/PolicyModal.test.js b/gui-clamp/ui-react/src/components/dialogs/Policy/PolicyModal.test.js new file mode 100644 index 0000000..734f22e --- /dev/null +++ b/gui-clamp/ui-react/src/components/dialogs/Policy/PolicyModal.test.js @@ -0,0 +1,128 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP CLAMP + * ================================================================================ + * Copyright (C) 2020 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 { mount } from 'enzyme'; +import PolicyModal from './PolicyModal'; +import LoopCache from '../../../api/LoopCache'; +import LoopService from '../../../api/LoopService'; +import OnapConstant from '../../../utils/OnapConstants'; + +describe('Verify PolicyModal', () => { + beforeEach(() => { + fetch.resetMocks(); + fetch.mockImplementation(() => { + return Promise.resolve({ + ok: true, + status: 200, + text: () => "OK" + }); + }); + }) + const loopCacheStr = { + "name": "LOOP_Jbv1z_v1_0_ResourceInstanceName1_tca", + "operationalPolicies": [{ + "name": "OPERATIONAL_h2NMX_v1_0_ResourceInstanceName1_tca", + "configurationsJson": { + "operational_policy": { + "controlLoop": {}, + "policies": [] + } + }, + "policyModel": { "policyPdpGroup": { "supportedPdpGroups": [{ "monitoring": ["xacml"] }] } }, + "jsonRepresentation": { "schema": {} } + }] + }; + const loopCache = new LoopCache(loopCacheStr); + const historyMock = { push: jest.fn() }; + const flushPromises = () => new Promise(setImmediate); + const match = { params: { policyName: "OPERATIONAL_h2NMX_v1_0_ResourceInstanceName1_tca", policyInstanceType: OnapConstant.operationalPolicyType } } + + it('Test handleClose', () => { + const handleClose = jest.spyOn(PolicyModal.prototype, 'handleClose'); + const component = mount() + + component.find('[variant="secondary"]').prop('onClick')(); + + expect(handleClose).toHaveBeenCalledTimes(1); + expect(component.state('show')).toEqual(false); + expect(historyMock.push.mock.calls[0]).toEqual(['/']); + }); + + it('Test handleSave', async () => { + const loadLoopFunction = jest.fn(); + const handleSave = jest.spyOn(PolicyModal.prototype, 'handleSave'); + const component = mount() + + component.find('[variant="primary"]').get(0).props.onClick(); + await flushPromises(); + component.update(); + + expect(handleSave).toHaveBeenCalledTimes(1); + expect(component.state('show')).toEqual(false); + expect(historyMock.push.mock.calls[0]).toEqual(['/']); + }); + + it('Test handleRefresh', async () => { + LoopService.refreshOperationalPolicyJson = jest.fn().mockImplementation(() => { + return Promise.resolve(loopCacheStr); + }); + const updateLoopFunction = jest.fn(); + const handleRefresh = jest.spyOn(PolicyModal.prototype, 'handleRefresh'); + const component = mount() + + component.find('[variant="primary"]').get(1).props.onClick(); + await flushPromises(); + component.update(); + + expect(handleRefresh).toHaveBeenCalledTimes(1); + expect(component.state('show')).toEqual(true); + expect(component.state('showSucAlert')).toEqual(true); + expect(component.state('showMessage')).toEqual("Successfully refreshed"); + }); + + it('Test handlePdpGroupChange', () => { + const component = mount() + component.setState({ + "pdpGroup": [{ "option1": ["subPdp1", "subPdp2"] }], + "chosenPdpGroup": "option2" + }); + expect(component.state('chosenPdpGroup')).toEqual("option2"); + + const instance = component.instance(); + const event = { label: "option1", value: "option1" } + instance.handlePdpGroupChange(event); + expect(component.state('chosenPdpGroup')).toEqual("option1"); + expect(component.state('chosenPdpSubgroup')).toEqual(""); + expect(component.state('pdpSubgroupList')).toEqual([{ label: "subPdp1", value: "subPdp1" }, { label: "subPdp2", value: "subPdp2" }]); + }); + + it('Test handlePdpSubgroupChange', () => { + const component = mount() + + const instance = component.instance(); + const event = { label: "option1", value: "option1" } + instance.handlePdpSubgroupChange(event); + expect(component.state('chosenPdpSubgroup')).toEqual("option1"); + }); +}); diff --git a/gui-clamp/ui-react/src/components/dialogs/Policy/ToscaViewer.js b/gui-clamp/ui-react/src/components/dialogs/Policy/ToscaViewer.js new file mode 100644 index 0000000..ce4edce --- /dev/null +++ b/gui-clamp/ui-react/src/components/dialogs/Policy/ToscaViewer.js @@ -0,0 +1,67 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP POLICY-CLAMP + * ================================================================================ + * Copyright (C) 2021 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 PolicyToscaService from '../../../api/PolicyToscaService'; +import styled from 'styled-components'; +import Button from 'react-bootstrap/Button'; + +const JsonEditorDiv = styled.div` + margin-top: 20px; + background-color: ${ props => props.theme.toscaTextareaBackgroundColor }; + text-align: justify; + font-size: ${ props => props.theme.toscaTextareaFontSize }; + width: 100%; + height: 30%; +` + +export default class ToscaViewer extends React.Component { + + state = { + toscaData: this.props.toscaData, + yamlPolicyTosca: this.getToscaModelYamlFor(this.props.toscaData), + } + + constructor(props, context) { + super(props, context); + this.getToscaModelYamlFor = this.getToscaModelYamlFor.bind(this); + } + + getToscaModelYamlFor(toscaData) { + PolicyToscaService.getToscaPolicyModelYaml(toscaData["policyModelType"], toscaData["version"]).then(respYamlPolicyTosca => { + this.setState({ + yamlPolicyTosca: respYamlPolicyTosca, + }) + }); + } + + render() { + return ( + +
{ this.state.yamlPolicyTosca }
+ +
+ ); + } +} diff --git a/gui-clamp/ui-react/src/components/dialogs/Policy/ViewAllPolicies.js b/gui-clamp/ui-react/src/components/dialogs/Policy/ViewAllPolicies.js new file mode 100644 index 0000000..a97deea --- /dev/null +++ b/gui-clamp/ui-react/src/components/dialogs/Policy/ViewAllPolicies.js @@ -0,0 +1,427 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP POLICY-CLAMP + * ================================================================================ + * Copyright (C) 2021 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, { forwardRef } from 'react' +import Button from 'react-bootstrap/Button'; +import Modal from 'react-bootstrap/Modal'; +import styled from 'styled-components'; +import AddBox from '@material-ui/icons/AddBox'; +import ArrowDownward from '@material-ui/icons/ArrowDownward'; +import Check from '@material-ui/icons/Check'; +import ChevronLeft from '@material-ui/icons/ChevronLeft'; +import ChevronRight from '@material-ui/icons/ChevronRight'; +import Clear from '@material-ui/icons/Clear'; +import DeleteRoundedIcon from '@material-ui/icons/DeleteRounded'; +import Edit from '@material-ui/icons/Edit'; +import FilterList from '@material-ui/icons/FilterList'; +import FirstPage from '@material-ui/icons/FirstPage'; +import LastPage from '@material-ui/icons/LastPage'; +import Remove from '@material-ui/icons/Remove'; +import SaveAlt from '@material-ui/icons/SaveAlt'; +import Search from '@material-ui/icons/Search'; +import ViewColumn from '@material-ui/icons/ViewColumn'; +import DehazeIcon from '@material-ui/icons/Dehaze'; +import ArrowForwardIosIcon from '@material-ui/icons/ArrowForwardIos'; +import AddIcon from '@material-ui/icons/Add'; +import FormControlLabel from '@material-ui/core/FormControlLabel'; +import Switch from '@material-ui/core/Switch'; +import MaterialTable from "material-table"; +import PolicyService from '../../../api/PolicyService'; +import PolicyToscaService from '../../../api/PolicyToscaService'; +import Select from 'react-select'; +import Alert from 'react-bootstrap/Alert'; +import Tabs from 'react-bootstrap/Tabs'; +import Tab from 'react-bootstrap/Tab'; +import PolicyEditor from './PolicyEditor'; +import ToscaViewer from './ToscaViewer'; + +const DivWhiteSpaceStyled = styled.div` + white-space: pre; +` + +const ModalStyled = styled(Modal)` + @media (min-width: 1000px) { + .modal-xl { + max-width: 96%; + } + } + background-color: transparent; +` +const DetailedRow = styled.div` + margin: 0 auto; + background-color: ${ props => props.theme.policyEditorBackgroundColor }; + font-size: ${ props => props.theme.policyEditorFontSize }; + width: 97%; + margin-left: auto; + margin-right: 0; +` + + +const standardCellStyle = { backgroundColor: '#039be5', color: '#FFF', border: '1px solid black' }; +const cellPdpGroupStyle = { backgroundColor: '#039be5', color: '#FFF', border: '1px solid black' }; +const headerStyle = { backgroundColor: '#ddd', border: '2px solid black' }; +const rowHeaderStyle = { backgroundColor: '#ddd', fontSize: '15pt', text: 'bold', border: '1px solid black' }; + +export default class ViewAllPolicies extends React.Component { + state = { + show: true, + content: 'Please select a policy to display it', + selectedRowId: -1, + policiesListData: [], + toscaModelsListData: [], + jsonEditorForPolicy: new Map(), + prefixGrouping: false, + showSuccessAlert: false, + showFailAlert: false, + policyColumnsDefinition: [ + { + title: "Policy Name", field: "name", + cellStyle: standardCellStyle, + headerStyle: headerStyle + }, + { + title: "Policy Version", field: "version", + cellStyle: standardCellStyle, + headerStyle: headerStyle + }, + { + title: "Policy Type", field: "type", + cellStyle: standardCellStyle, + headerStyle: headerStyle + }, + { + title: "Policy Type Version", field: "type_version", + cellStyle: standardCellStyle, + headerStyle: headerStyle + }, + { + title: "Deployed in PDP", field: "pdpGroupInfo.pdpGroup", + cellStyle: cellPdpGroupStyle, + headerStyle: headerStyle, + render: rowData => this.renderPdpGroupDropBox(rowData), + grouping: false + }, + { + title: "PDP Group", field: "pdpGroupInfo.pdpGroup", + cellStyle: cellPdpGroupStyle, + headerStyle: headerStyle + }, + { + title: "PDP SubGroup", field: "pdpGroupInfo.pdpSubGroup", + cellStyle: cellPdpGroupStyle, + headerStyle: headerStyle + } + ], + toscaColumnsDefinition: [ + { + title: "Policy Model Type", field: "policyModelType", + cellStyle: standardCellStyle, + headerStyle: headerStyle + }, + { + title: "Policy Acronym", field: "policyAcronym", + cellStyle: standardCellStyle, + headerStyle: headerStyle + }, + { + title: "Version", field: "version", + cellStyle: standardCellStyle, + headerStyle: headerStyle + }, + { + title: "Uploaded By", field: "updatedBy", + cellStyle: standardCellStyle, + headerStyle: headerStyle + }, + { + title: "Uploaded Date", field: "updatedDate", editable: 'never', + cellStyle: standardCellStyle, + headerStyle: headerStyle + } + ], + tableIcons: { + Add: forwardRef((props, ref) => ), + Check: forwardRef((props, ref) => ), + Clear: forwardRef((props, ref) => ), + Delete: forwardRef((props, ref) => ), + DetailPanel: forwardRef((props, ref) => ), + Edit: forwardRef((props, ref) => ), + Export: forwardRef((props, ref) => ), + Filter: forwardRef((props, ref) => ), + FirstPage: forwardRef((props, ref) => ), + LastPage: forwardRef((props, ref) => ), + NextPage: forwardRef((props, ref) => ), + PreviousPage: forwardRef((props, ref) => ), + ResetSearch: forwardRef((props, ref) => ), + Search: forwardRef((props, ref) => ), + SortArrow: forwardRef((props, ref) => ), + ThirdStateCheck: forwardRef((props, ref) => ), + ViewColumn: forwardRef((props, ref) => ) + } + }; + + constructor(props, context) { + super(props, context); + this.handleClose = this.handleClose.bind(this); + this.renderPdpGroupDropBox = this.renderPdpGroupDropBox.bind(this); + this.handlePdpGroupChange = this.handlePdpGroupChange.bind(this); + this.handlePrefixGrouping = this.handlePrefixGrouping.bind(this); + this.handleDeletePolicy = this.handleDeletePolicy.bind(this); + this.disableAlert = this.disableAlert.bind(this); + this.getAllPolicies = this.getAllPolicies.bind(this); + this.getAllToscaModels = this.getAllToscaModels.bind(this); + this.getAllPolicies(); + this.getAllToscaModels(); + } + + getAllToscaModels() { + PolicyToscaService.getToscaPolicyModels().then(toscaModelsList => { + this.setState({ toscaModelsListData: toscaModelsList }); + }); + } + + handlePdpGroupChange(e) { + let pdpSplit = e.value.split("/"); + let selectedPdpGroup = pdpSplit[0]; + let selectedSubPdpGroup = pdpSplit[1]; + if (typeof selectedSubPdpGroup !== "undefined") { + let temp = this.state.policiesListData; + temp[this.state.selectedRowId]["pdpGroupInfo"] = { "pdpGroup": selectedPdpGroup, "pdpSubGroup": selectedSubPdpGroup }; + this.setState({ policiesListData: temp }); + } else { + delete this.state.policiesListData[this.state.selectedRowId]["pdpGroupInfo"]; + } + } + + renderPdpGroupDropBox(dataRow) { + let optionItems = [{ label: "NOT DEPLOYED", value: "NOT DEPLOYED" }]; + let selectedItem = { label: "NOT DEPLOYED", value: "NOT DEPLOYED" }; + if (typeof dataRow.supportedPdpGroups !== "undefined") { + for (const pdpGroup of dataRow["supportedPdpGroups"]) { + for (const pdpSubGroup of Object.values(pdpGroup)[0]) { + optionItems.push({ + label: Object.keys(pdpGroup)[0] + "/" + pdpSubGroup, + value: Object.keys(pdpGroup)[0] + "/" + pdpSubGroup + }); + } + } + } + if (typeof dataRow.pdpGroupInfo !== "undefined") { + selectedItem = { + label: dataRow["pdpGroupInfo"]["pdpGroup"] + "/" + dataRow["pdpGroupInfo"]["pdpSubGroup"], + value: dataRow["pdpGroupInfo"]["pdpGroup"] + "/" + dataRow["pdpGroupInfo"]["pdpSubGroup"] + }; + } + return (