diff options
Diffstat (limited to 'gui-clamp/ui-react')
32 files changed, 2272 insertions, 570 deletions
diff --git a/gui-clamp/ui-react/package.json b/gui-clamp/ui-react/package.json index 8d11044..fd3dc0a 100644 --- a/gui-clamp/ui-react/package.json +++ b/gui-clamp/ui-react/package.json @@ -27,8 +27,8 @@ "dependencies": { "@json-editor/json-editor": "2.5.2", "@fortawesome/fontawesome-free": "5.15.2", - "react": "17.0.1", - "react-dom": "17.0.1", + "react": "17.0.2", + "react-dom": "17.0.2", "react-scripts": "4.0.3", "react-bootstrap": "1.5.2", "bootstrap-css-only": "4.3.1", @@ -36,9 +36,10 @@ "react-router-dom": "5.2.0", "@material-ui/core": "4.11.3", "@material-ui/icons": "4.11.2", + "@material-ui/pickers": "3.3.10", + "@material-ui/lab": "4.0.0-alpha.57", "material-table": "1.68.1", - "react-select": "4.2.1", - "react-uuid": "1.0.2" + "react-select": "4.2.1" }, "devDependencies": { "jest": "26.6.0", @@ -47,9 +48,10 @@ "@babel/preset-react": "7.12.13", "@babel/plugin-proposal-class-properties": "7.13.0", "enzyme": "3.11.0", - "enzyme-adapter-react-17-updated": "1.0.2", + "@wojtekmaj/enzyme-adapter-react-17": "0.6.1", "enzyme-to-json": "3.6.1", - "jest-fetch-mock": "3.0.3" + "jest-fetch-mock": "3.0.3", + "jest-canvas-mock": "2.3.1" }, "browserslist": [ ">0.2%", @@ -71,7 +73,8 @@ "\\.(css|png)$": "identity-obj-proxy" }, "setupFiles": [ - "./src/setupTests.js" + "./src/setupTests.js", + "jest-canvas-mock" ], "snapshotSerializers": [ "enzyme-to-json/serializer" @@ -95,4 +98,3 @@ ] } } - diff --git a/gui-clamp/ui-react/src/api/PoliciesListCache.js b/gui-clamp/ui-react/src/api/PoliciesListCache.js deleted file mode 100644 index 265dab4..0000000 --- a/gui-clamp/ui-react/src/api/PoliciesListCache.js +++ /dev/null @@ -1,34 +0,0 @@ -/*- - * ============LICENSE_START======================================================= - * ONAP 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============================================ - * =================================================================== - * - */ - -export default class PoliciesListCache { - policiesJsonCache; - - constructor(policiesJson) { - this.policiesJsonCache = policiesJson; - } - - getAllPolicies() { - return this.policiesJsonCache["policies"]; - } -} diff --git a/gui-clamp/ui-react/src/api/PolicyService.js b/gui-clamp/ui-react/src/api/PolicyService.js index 046f789..cf4f0fd 100644 --- a/gui-clamp/ui-react/src/api/PolicyService.js +++ b/gui-clamp/ui-react/src/api/PolicyService.js @@ -41,7 +41,7 @@ export default class PolicyService { console.error("getPoliciesList error occurred ", error); alert("getPoliciesList error occurred " + error); return undefined; - }) + }); } static createNewPolicy(policyModelType, policyModelVersion, policyName, policyVersion, policyJson) { @@ -95,4 +95,58 @@ export default class PolicyService { return undefined; }); } + + static updatePdpDeployment(pdpOperationsList) { + return fetch(window.location.pathname + 'restservices/clds/v2/policies/pdpDeployment', { + method: 'PUT', + credentials: 'same-origin', + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(pdpOperationsList) + }) + .then(function (response) { + console.debug("updatePdpDeployment response received: ", response.status); + if (response.ok) { + console.info("updatePdpDeployment query successful"); + return response.text(); + } else { + return response.text().then(responseBody => { + throw new Error("HTTP " + response.status + "," + responseBody); + }) + } + }) + .catch(function (error) { + console.error("updatePdpDeployment error occurred ", error); + alert("updatePdpDeployment error occurred " + error); + return undefined; + }); + } + + static sendNewPolicyModel(newPolicyModel) { + return fetch(window.location.pathname + 'restservices/clds/v2/policies/policytype', { + method: 'POST', + credentials: 'same-origin', + headers: { + "Content-Type": "plain/text" + }, + body: newPolicyModel + }) + .then(function (response) { + console.debug("sendNewPolicyModel response received: ", response.status); + if (response.ok) { + console.info("sendNewPolicyModel query successful"); + return response.text(); + } else { + return response.text().then(responseBody => { + throw new Error("HTTP " + response.status + "," + responseBody); + }) + } + }) + .catch(function (error) { + console.error("sendNewPolicyModel error occurred ", error); + alert("sendNewPolicyModel error occurred " + error); + return undefined; + }); + } } diff --git a/gui-clamp/ui-react/src/api/PolicyToscaService.js b/gui-clamp/ui-react/src/api/PolicyToscaService.js index b5282f9..6c74149 100644 --- a/gui-clamp/ui-react/src/api/PolicyToscaService.js +++ b/gui-clamp/ui-react/src/api/PolicyToscaService.js @@ -77,60 +77,4 @@ export default class PolicyToscaService { return {}; }); } - - static createPolicyModelFromToscaModel(jsonData) { - return fetch(window.location.pathname + 'restservices/clds/v2/policyToscaModels', { - method: 'POST', - credentials: 'same-origin', - headers: { - "Content-Type": "a", - }, - body: JSON.stringify(jsonData) - }) - .then(function (response) { - console.debug("createPolicyModelFromToscaModel response received: ", response.status); - if (response.ok) { - var message = { - status: response.status, - message: 'Tosca Policy Model successfully uploaded' - }; - return message; - } else { - console.error("createPolicyModelFromToscaModel failed"); - return response.text(); - } - }) - .catch(function (error) { - console.error("createPolicyModelFromToscaModel error received", error); - return ""; - }); - } - - static updatePolicyModelTosca(policyModelType, policyModelVersion, jsonData) { - return fetch(window.location.pathname + 'restservices/clds/v2/policyToscaModels/' + policyModelType + '/' + policyModelVersion, { - method: 'PUT', - credentials: 'same-origin', - headers: { - "Content-Type": "a", - }, - body: JSON.stringify(jsonData) - }) - .then(function (response) { - console.debug("updatePolicyModelTosca response received: ", response.status); - if (response.ok) { - var message = { - status: response.status, - message: 'Tosca Policy Model successfully uploaded' - }; - return message; - } else { - console.error("updatePolicyModelTosca failed"); - return response.text(); - } - }) - .catch(function (error) { - console.error("updatePolicyModelTosca error received", error); - return ""; - }); - } } diff --git a/gui-clamp/ui-react/src/components/dialogs/Loop/CreateLoopModal.test.js b/gui-clamp/ui-react/src/components/dialogs/Loop/CreateLoopModal.test.js index 401bb6a..3d94833 100644 --- a/gui-clamp/ui-react/src/components/dialogs/Loop/CreateLoopModal.test.js +++ b/gui-clamp/ui-react/src/components/dialogs/Loop/CreateLoopModal.test.js @@ -26,6 +26,13 @@ import CreateLoopModal from './CreateLoopModal'; import LoopService from '../../../api/LoopService'; import TemplateService from '../../../api/TemplateService'; +let errorMessage = ''; +window.alert = jest.fn().mockImplementation((mesg) => { + errorMessage = mesg; + return +}); + + describe('Verify CreateLoopModal', () => { it('Test the render method', async () => { diff --git a/gui-clamp/ui-react/src/components/dialogs/Loop/DeployLoopModal.js b/gui-clamp/ui-react/src/components/dialogs/Loop/DeployLoopModal.js index b7ab8a5..5fff586 100644 --- a/gui-clamp/ui-react/src/components/dialogs/Loop/DeployLoopModal.js +++ b/gui-clamp/ui-react/src/components/dialogs/Loop/DeployLoopModal.js @@ -1,8 +1,8 @@ /*- * ============LICENSE_START======================================================= - * ONAP CLAMP + * ONAP POLICY-CLAMP * ================================================================================ - * Copyright (C) 2019 AT&T Intellectual Property. All rights + * Copyright (C) 2019, 2021 AT&T Intellectual Property. All rights * reserved. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -44,6 +44,7 @@ const FormStyled = styled(Form.Group)` ` export default class DeployLoopModal extends React.Component { + constructor(props, context) { super(props, context); @@ -65,13 +66,7 @@ export default class DeployLoopModal extends React.Component { getInitialKeyValue(temporaryPropertiesJson) { const deployJsonList = temporaryPropertiesJson["dcaeDeployParameters"]; - let initialKey; - Object.keys(deployJsonList) - .filter((obj) => Object.keys(deployJsonList).indexOf(obj) === 0) - .map(obj => - initialKey = obj - ); - return initialKey; + return Object.keys(deployJsonList).find((obj) => Object.keys(deployJsonList).indexOf(obj) === 0); } componentWillReceiveProps(newProps) { @@ -145,8 +140,8 @@ export default class DeployLoopModal extends React.Component { const deployJsonList = this.state.temporaryPropertiesJson["dcaeDeployParameters"]; var indents = []; - Object.keys(deployJsonList).map((item, key) => - indents.push(<Tab eventKey={ item } title={ item }> + Object.keys(deployJsonList).forEach(item => + indents.push(<Tab key={ item } eventKey={ item } title={ item }> { this.renderDeployParam(deployJsonList[item]) } </Tab>) ); @@ -155,8 +150,8 @@ export default class DeployLoopModal extends React.Component { renderDeployParam(deployJson) { var indents = []; - Object.keys(deployJson).map((item, key) => - indents.push(<FormStyled> + Object.keys(deployJson).forEach(item => + indents.push(<FormStyled key={ item }> <Form.Label>{ item }</Form.Label> <Form.Control type="text" name={ item } onChange={ this.handleChange } defaultValue={ deployJson[item] }></Form.Control> </FormStyled>)); diff --git a/gui-clamp/ui-react/src/components/dialogs/Loop/ModifyLoopModal.js b/gui-clamp/ui-react/src/components/dialogs/Loop/ModifyLoopModal.js index 8f8b74d..0d20341 100644 --- a/gui-clamp/ui-react/src/components/dialogs/Loop/ModifyLoopModal.js +++ b/gui-clamp/ui-react/src/components/dialogs/Loop/ModifyLoopModal.js @@ -130,13 +130,6 @@ export default class ModifyLoopModal extends React.Component { this.initializeToscaPolicyModelsInfo(); } - componentWillReceiveProps(newProps) { - this.setState({ - loopCache: newProps.loopCache, - temporaryPropertiesJson: JSON.parse(JSON.stringify(newProps.loopCache.getGlobalProperties())) - }); - } - initializeToscaPolicyModelsInfo() { var operationalPolicies = this.state.loopCache.getOperationalPolicies(); var selectedPolicyModels = []; diff --git a/gui-clamp/ui-react/src/components/dialogs/Loop/__snapshots__/DeployLoopModal.test.js.snap b/gui-clamp/ui-react/src/components/dialogs/Loop/__snapshots__/DeployLoopModal.test.js.snap index 4779ced..33f8faa 100644 --- a/gui-clamp/ui-react/src/components/dialogs/Loop/__snapshots__/DeployLoopModal.test.js.snap +++ b/gui-clamp/ui-react/src/components/dialogs/Loop/__snapshots__/DeployLoopModal.test.js.snap @@ -26,9 +26,12 @@ exports[`Verify DeployLoopModal Test the render method 1`] = ` > <Tab eventKey="testMs" + key="testMs" title="testMs" > - <Styled(FormGroup)> + <Styled(FormGroup) + key="location_id" + > <FormLabel column={false} srOnly={false} @@ -42,7 +45,9 @@ exports[`Verify DeployLoopModal Test the render method 1`] = ` type="text" /> </Styled(FormGroup)> - <Styled(FormGroup)> + <Styled(FormGroup) + key="policy_id" + > <FormLabel column={false} srOnly={false} diff --git a/gui-clamp/ui-react/src/components/dialogs/Policy/PoliciesTreeViewer.js b/gui-clamp/ui-react/src/components/dialogs/Policy/PoliciesTreeViewer.js new file mode 100644 index 0000000..5bca4e6 --- /dev/null +++ b/gui-clamp/ui-react/src/components/dialogs/Policy/PoliciesTreeViewer.js @@ -0,0 +1,109 @@ +/*- + * ============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 TreeView from '@material-ui/lab/TreeView'; +import TreeItem from '@material-ui/lab/TreeItem'; +import FolderIcon from '@material-ui/icons/Folder'; +import FolderOpenIcon from '@material-ui/icons/FolderOpen'; +import DescriptionIcon from '@material-ui/icons/Description'; + + +export default class PoliciesTreeViewer extends React.Component { + + separator = "."; + + nodesList = new Map(); + + constructor(props, context) { + super(props, context); + this.createPoliciesTree = this.createPoliciesTree.bind(this); + this.handleTreeItemClick = this.handleTreeItemClick.bind(this); + this.buildNameWithParent = this.buildNameWithParent.bind(this); + + } + + state = { + policiesTreeData: this.createPoliciesTree(this.props.policiesData), + } + + componentDidUpdate(prevProps) { + if (prevProps.policiesData !== this.props.policiesData) { + this.setState({ policiesTreeData: this.createPoliciesTree(this.props.policiesData) }) + } + } + + createPoliciesTree(policiesArray) { + // put my policies array in a Json + let nodeId = 1; + let root = { id: nodeId, policyCount: 0, name: "ROOT", children: [], parent: undefined }; + this.nodesList.set(nodeId++, root); + + policiesArray.forEach(policy => { + let currentTreeNode = root; + policy[this.props.valueForTreeCreation].split(this.separator).forEach((policyNamePart, index, policyNamePartsArray) => { + let node = currentTreeNode["children"].find(element => element.name === policyNamePart); + if (typeof (node) === "undefined") { + node = { id: nodeId, policyCount: 0, children: [], name: policyNamePart, parent: currentTreeNode }; + this.nodesList.set(nodeId++, node); + currentTreeNode["children"].push(node); + } + if ((index + 1) === policyNamePartsArray.length) { + ++currentTreeNode["policyCount"]; + } + currentTreeNode = node; + }) + }) + return root; + } + + buildNameWithParent(node) { + let nameToBuild = node.name; + if (node.parent !== undefined) { + nameToBuild = this.buildNameWithParent(node.parent) + this.separator + node.name; + } + return nameToBuild; + } + + handleTreeItemClick(event, value) { + let fullName = this.buildNameWithParent(this.nodesList.get(value[0])).substring(5); + this.props.policiesFilterFunction(fullName); + } + + renderTreeItems(nodes) { + return (<TreeItem key={ nodes.id } nodeId={ nodes.id } label={ nodes.name + "(" + nodes.policyCount + ")" } onNodeSelect={ this.handleTreeItemClick }> + { + Array.isArray(nodes.children) ? nodes.children.map((node) => this.renderTreeItems(node)) : null + } + </TreeItem>); + }; + + render() { + return ( + <TreeView defaultExpanded={ ['root'] } defaultCollapseIcon={ <FolderOpenIcon/> } + defaultExpandIcon={ <FolderIcon/> } defaultEndIcon={ <DescriptionIcon/> } onNodeSelect={ this.handleTreeItemClick } multiSelect> + { this.renderTreeItems(this.state.policiesTreeData) } + </TreeView> + ); + } +} diff --git a/gui-clamp/ui-react/src/components/dialogs/Policy/PolicyDeploymentEditor.js b/gui-clamp/ui-react/src/components/dialogs/Policy/PolicyDeploymentEditor.js new file mode 100644 index 0000000..de29947 --- /dev/null +++ b/gui-clamp/ui-react/src/components/dialogs/Policy/PolicyDeploymentEditor.js @@ -0,0 +1,178 @@ +/*- + * ============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 Modal from 'react-bootstrap/Modal'; +import styled from 'styled-components'; +import Button from 'react-bootstrap/Button'; +import Alert from 'react-bootstrap/Alert'; +import PolicyService from '../../../api/PolicyService'; +import FormGroup from '@material-ui/core/FormGroup'; +import Checkbox from '@material-ui/core/Checkbox'; +import FormControlLabel from '@material-ui/core/FormControlLabel'; + +const DivWhiteSpaceStyled = styled.div` + white-space: pre; +` + +const PanelDiv = styled.div` + text-align: justify; + font-size: ${ props => props.theme.policyEditorFontSize }; + background-color: ${ props => props.theme.loopViewerBackgroundColor }; +` + +export default class PolicyDeploymentEditor extends React.Component { + + state = { + policyData: this.props.policyData, + showSuccessAlert: false, + showFailAlert: false, + checkboxesState: this.createPdpStructure(this.props.policyData), + checkboxesInitialState: this.createPdpStructure(this.props.policyData), + }; + + constructor(props, context) { + super(props, context); + this.handleClose = this.handleClose.bind(this); + this.handleUpdatePdpDeployment = this.handleUpdatePdpDeployment.bind(this); + this.disableAlert = this.disableAlert.bind(this); + this.renderPdpDeploymentCheckboxes = this.renderPdpDeploymentCheckboxes.bind(this); + this.createPdpStructure = this.createPdpStructure.bind(this); + this.handleCheckboxChange = this.handleCheckboxChange.bind(this); + this.createPdpGroupOperations = this.createPdpGroupOperations.bind(this); + } + + handleClose() { + this.setState({ show: false }); + + } + + disableAlert() { + this.setState({ showSuccessAlert: false, showFailAlert: false }); + } + + createPdpGroupOperations(initialStates, newStates) { + let commandsArray = []; + initialStates.forEach(initElem => { + let newStateFound = newStates.find(newElement => newElement.name === initElem.name); + if (initElem.value !== newStateFound.value) { + let newPdpGroupsArray = newStateFound.name.split("/"); + let operation = "POST/"; + if (initElem.value) { + operation = "DELETE/"; + } + commandsArray.push(operation + newPdpGroupsArray[0] + "/" + newPdpGroupsArray[1] + "/" + + this.state.policyData.name + "/" + this.state.policyData.version); + } + }); + return commandsArray.length > 0 ? { "PdpActions": commandsArray } : undefined; + } + + handleUpdatePdpDeployment() { + let operationsList = this.createPdpGroupOperations(this.state.checkboxesInitialState, + this.state.checkboxesState); + if (typeof (operationsList) !== "undefined") { + PolicyService.updatePdpDeployment(operationsList).then(respPdpDeploymentUpdate => { + if (typeof (respPdpDeploymentUpdate) === "undefined") { + //it indicates a failure + this.setState({ + showFailAlert: true, + showMessage: 'Pdp Deployment update Failure' + }); + } else { + this.setState({ + showSuccessAlert: true, + showMessage: 'Pdp Deployment Update successful' + }); + this.props.policiesTableUpdateFunction(); + } + }) + } else { + this.setState({ + showSuccessAlert: true, + showMessage: 'Pdp Deployment: Nothing to change' + }); + } + } + + createPdpStructure(policyData) { + // Create map with data for all group/subgroup where the policy is deployed + let infoPdpMap = new Map(); + if (typeof policyData.pdpGroupInfo !== "undefined") { + policyData["pdpGroupInfo"].forEach(pdpGroupElem => { + let pdpGroupName = Object.keys(pdpGroupElem)[0]; + pdpGroupElem[pdpGroupName]["pdpSubgroups"].forEach(pdpSubGroupElem => { + infoPdpMap.set(pdpGroupName + "/" + pdpSubGroupElem["pdpType"], true); + }); + }); + } + // Create the possible values for pdpgroup/subgroup and tick the ones where policy is deployed + let pdpStates = []; + if (typeof policyData.supportedPdpGroups !== "undefined") { + for (const pdpGroup of policyData["supportedPdpGroups"]) { + let pdpGroupName = Object.keys(pdpGroup)[0]; + for (const pdpSubGroup of Object.values(pdpGroup)[0]) { + let fullName = pdpGroupName + "/" + pdpSubGroup; + pdpStates.push({ + name: fullName, + value: infoPdpMap.get(fullName) !== undefined + }); + } + } + } + return pdpStates; + } + + handleCheckboxChange(event) { + const checkboxesArray = this.state.checkboxesState; + checkboxesArray.find(element => element.name === event.target.name).value = event.target.checked; + this.setState({ checkboxesState: checkboxesArray }); + } + + renderPdpDeploymentCheckboxes() { + return this.state.checkboxesState.map(item => { + return <FormControlLabel control={ <Checkbox checked={ item.value } onChange={ this.handleCheckboxChange } + name={ item.name }/> } label={ item.name }/>; + }); + } + + render() { + return ( + <PanelDiv> + <Alert variant="success" show={ this.state.showSuccessAlert } onClose={ this.disableAlert } dismissible> + <DivWhiteSpaceStyled> + { this.state.showMessage } + </DivWhiteSpaceStyled> + </Alert> + <Alert variant="danger" show={ this.state.showFailAlert } onClose={ this.disableAlert } dismissible> + <DivWhiteSpaceStyled> + { this.state.showMessage } + </DivWhiteSpaceStyled> + </Alert> + <Button variant="secondary" title="Update the policy to the specified PDP Groups/Subgroups" + onClick={ this.handleUpdatePdpDeployment }>Update PDP</Button> + <FormGroup>{ this.renderPdpDeploymentCheckboxes() }</FormGroup> + </PanelDiv> + ); + } +} diff --git a/gui-clamp/ui-react/src/components/dialogs/Policy/PolicyEditor.js b/gui-clamp/ui-react/src/components/dialogs/Policy/PolicyEditor.js index 525d66b..1e27541 100644 --- a/gui-clamp/ui-react/src/components/dialogs/Policy/PolicyEditor.js +++ b/gui-clamp/ui-react/src/components/dialogs/Policy/PolicyEditor.js @@ -31,9 +31,7 @@ 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; ` @@ -46,7 +44,6 @@ const JsonEditorDiv = styled.div` 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 }; @@ -63,7 +60,7 @@ export default class PolicyEditor extends React.Component { showSuccessAlert: false, showFailAlert: false, jsonEditor: null, - jsonEditorDivId: uuid(), + jsonEditorDivId: this.props.policyModelType + "_" + this.props.policyModelTypeVersion + "_" + this.props.policyName + "_" + this.props.policyVersion, } constructor(props, context) { @@ -112,17 +109,12 @@ export default class PolicyEditor extends React.Component { showSuccessAlert: true, showMessage: 'Policy ' + this.state.policyName + '/' + this.state.policyVersion + ' created successfully' }); - this.props.policyUpdateFunction(); + this.props.policiesTableUpdateFunction(); } }) } } - 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 !== {}) { @@ -139,27 +131,10 @@ export default class PolicyEditor extends React.Component { } 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', diff --git a/gui-clamp/ui-react/src/components/dialogs/Policy/PolicyEditor.test.js b/gui-clamp/ui-react/src/components/dialogs/Policy/PolicyEditor.test.js new file mode 100644 index 0000000..111f2c6 --- /dev/null +++ b/gui-clamp/ui-react/src/components/dialogs/Policy/PolicyEditor.test.js @@ -0,0 +1,72 @@ +/*- + * ============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 PolicyEditor from './PolicyEditor'; +import { shallow, mount } from 'enzyme'; +import PolicyToscaService from '../../../api/PolicyToscaService'; + +describe('Verify PolicyEditor', () => { + const fs = require('fs'); + + let toscaJson = fs.readFileSync('src/components/dialogs/Policy/toscaData.test.json', { encoding: 'utf8', flag: 'r' }) + + const policyProperties = { + "tca.policy": { + "domain": "measurementsForVfScaling", + "metricsPerEventName": [ + { + "policyScope": "DCAE", + "thresholds": [ + { + "version": "1.0.2", + "severity": "MAJOR", + "thresholdValue": 200, + "closedLoopEventStatus": "ONSET", + "closedLoopControlName": "LOOP_test", + "direction": "LESS_OR_EQUAL", + "fieldPath": "$.event.measurementsForVfScalingFields.vNicPerformanceArray[*].receivedTotalPacketsDelta" + } + ], + "eventName": "vLoadBalancer", + "policyVersion": "v0.0.1", + "controlLoopSchemaType": "VM", + "policyName": "DCAE.Config_tca-hi-lo" + } + ] + } + }; + + + it('Test the render method', async () => { + PolicyToscaService.getToscaPolicyModel = jest.fn().mockImplementation(() => { + return Promise.resolve(toscaJson); + }); + + const component = mount(<PolicyEditor policyModelType="onap.policies.monitoring.tcagen2" policyModelTypeVersion="1.0.0" + policyName="org.onap.new" policyVersion="1.0.0" policyProperties={ policyProperties } + policiesTableUpdateFunction={ () => { + } }/>); + await PolicyToscaService.getToscaPolicyModel(); + expect(component).toMatchSnapshot(); + }); +}); diff --git a/gui-clamp/ui-react/src/components/dialogs/Policy/PolicyModal.js b/gui-clamp/ui-react/src/components/dialogs/Policy/PolicyModal.js index 105b2c5..0c7637c 100644 --- a/gui-clamp/ui-react/src/components/dialogs/Policy/PolicyModal.js +++ b/gui-clamp/ui-react/src/components/dialogs/Policy/PolicyModal.js @@ -137,21 +137,6 @@ export default class PolicyModal extends React.Component { } 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, @@ -316,17 +301,17 @@ export default class PolicyModal extends React.Component { } renderButton() { - var allElement = [(<Button variant="secondary" onClick={ this.handleClose }> + var allElement = [(<Button key="close" variant="secondary" onClick={ this.handleClose }> Close </Button>)]; if (this.state.policyInstanceType !== OnapConstant.operationalPolicyType || !this.state.loopCache.isOpenLoopTemplate()) { allElement.push(( - <Button variant="primary" disabled={ this.readOnly } onClick={ this.handleSave }> + <Button key="save" variant="primary" disabled={ this.readOnly } onClick={ this.handleSave }> Save Changes </Button> )); allElement.push(( - <Button variant="primary" disabled={ this.readOnly } onClick={ this.handleRefresh }> + <Button key="refresh" variant="primary" disabled={ this.readOnly } onClick={ this.handleRefresh }> Refresh </Button> )); 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 index 734f22e..658b19e 100644 --- a/gui-clamp/ui-react/src/components/dialogs/Policy/PolicyModal.test.js +++ b/gui-clamp/ui-react/src/components/dialogs/Policy/PolicyModal.test.js @@ -1,8 +1,8 @@ /*- * ============LICENSE_START======================================================= - * ONAP CLAMP + * ONAP POLICY-CLAMP * ================================================================================ - * Copyright (C) 2020 AT&T Intellectual Property. All rights + * Copyright (C) 2020-2021 AT&T Intellectual Property. All rights * reserved. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,6 +26,7 @@ import PolicyModal from './PolicyModal'; import LoopCache from '../../../api/LoopCache'; import LoopService from '../../../api/LoopService'; import OnapConstant from '../../../utils/OnapConstants'; +import { shallow } from 'enzyme'; describe('Verify PolicyModal', () => { beforeEach(() => { @@ -52,6 +53,7 @@ describe('Verify PolicyModal', () => { "jsonRepresentation": { "schema": {} } }] }; + const loopCache = new LoopCache(loopCacheStr); const historyMock = { push: jest.fn() }; const flushPromises = () => new Promise(setImmediate); @@ -125,4 +127,9 @@ describe('Verify PolicyModal', () => { instance.handlePdpSubgroupChange(event); expect(component.state('chosenPdpSubgroup')).toEqual("option1"); }); + + it('Test the render method', () => { + const component = shallow(<PolicyModal loopCache={ loopCache } match={ match }/>) + expect(component).toMatchSnapshot(); + }); }); diff --git a/gui-clamp/ui-react/src/components/dialogs/Policy/PolicyToscaFileSelector.js b/gui-clamp/ui-react/src/components/dialogs/Policy/PolicyToscaFileSelector.js new file mode 100644 index 0000000..8093b7e --- /dev/null +++ b/gui-clamp/ui-react/src/components/dialogs/Policy/PolicyToscaFileSelector.js @@ -0,0 +1,133 @@ +/*- + * ============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 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 Alert from 'react-bootstrap/Alert'; +import { Input, InputLabel, Button, SvgIcon } from "@material-ui/core"; +import PublishIcon from '@material-ui/icons/Publish'; +import PolicyService from '../../../api/PolicyService'; + +const ModalStyled = styled(Modal)` + background-color: transparent; +` + +const StyledMessagesDiv = styled.div` + overflow: auto; + max-height: 300px; +` + + +export default class PolicyToscaFileSelector extends React.Component { + + state = { + show: this.props.show, + alertMessages: [], + } + + constructor(props, context) { + super(props, context); + this.handleClose = this.handleClose.bind(this); + this.onFileChange = this.onFileChange.bind(this); + } + + componentDidUpdate(prevProps) { + if (this.props.show !== this.state.show) { + this.setState({ show: this.props.show }); + } + } + + handleClose() { + this.props.disableFunction(); + this.setState({ alertMessages: [] }); + } + + onFileChange(target) { + this.setState({ alertMessages: [] }); + target.currentTarget.files.forEach(file => { + const fileReader = new FileReader(); + fileReader.readAsDataURL(file); + fileReader.onload = (content) => { + PolicyService.sendNewPolicyModel(atob(content.target.result.split(",")[1])).then(respModelCreate => { + if (typeof (respModelCreate) === "undefined") { + //it indicates a failure + this.setState(state => { + return { + alertMessages: [...state.alertMessages, (<Alert variant="danger"><Alert.Heading>{ file.name }</Alert.Heading><p>Policy Tosca Model Creation Failure</p> + <hr/> + <p>Type: { file.type }</p><p>Size: { file.size }</p></Alert>)] + }; + }); + } else { + this.props.toscaTableUpdateFunction(); + this.setState(state => { + return { + alertMessages: [...state.alertMessages, (<Alert variant="success"><Alert.Heading>{ file.name }</Alert.Heading><p>Policy Tosca Model Created Successfully</p> + <hr/> + <p>Type: { file.type }</p><p>Size: { file.size }</p></Alert>)] + }; + }); + } + }); + }; + }); + + } + + render() { + return ( + <ModalStyled size="lg" show={ this.state.show } onHide={ this.handleClose } backdrop="static" keyboard={ false }> + <Modal.Header closeButton> + <Modal.Title>Create New Policy Tosca Model</Modal.Title> + </Modal.Header> + <Modal.Body> + <Form.Group as={ Row } controlId="formPlaintextEmail"> + <Col sm="10"> + <input type="file" multiple accept=".yaml,.yml" id="fileUploadButton" style={ { display: 'none' } } onChange={ this.onFileChange }/> + <label htmlFor={ 'fileUploadButton' }> + <Button color="primary" variant="contained" component="span" + startIcon={ + <SvgIcon fontSize="small"> + <PublishIcon/> + </SvgIcon> + }> + Upload Files + </Button> + <p>(Only YAML files are supported)</p> + </label> + <StyledMessagesDiv> + { this.state.alertMessages } + </StyledMessagesDiv> + </Col> + </Form.Group> + </Modal.Body> + <Modal.Footer> + <Button variant="secondary" onClick={ this.handleClose }>Close</Button> + </Modal.Footer> + </ModalStyled> + ); + } +} diff --git a/gui-clamp/ui-react/src/components/dialogs/Policy/ToscaViewer.js b/gui-clamp/ui-react/src/components/dialogs/Policy/ToscaViewer.js index ce4edce..fc69a63 100644 --- a/gui-clamp/ui-react/src/components/dialogs/Policy/ToscaViewer.js +++ b/gui-clamp/ui-react/src/components/dialogs/Policy/ToscaViewer.js @@ -26,8 +26,7 @@ import PolicyToscaService from '../../../api/PolicyToscaService'; import styled from 'styled-components'; import Button from 'react-bootstrap/Button'; -const JsonEditorDiv = styled.div` - margin-top: 20px; +const ToscaDiv = styled.div` background-color: ${ props => props.theme.toscaTextareaBackgroundColor }; text-align: justify; font-size: ${ props => props.theme.toscaTextareaFontSize }; @@ -57,11 +56,11 @@ export default class ToscaViewer extends React.Component { render() { return ( - <JsonEditorDiv> + <ToscaDiv> <pre>{ this.state.yamlPolicyTosca }</pre> <Button variant="secondary" title="Create a new policy version from the defined parameters" onClick={ this.handleCreateNewVersion }>Create New Version</Button> - </JsonEditorDiv> + </ToscaDiv> ); } } diff --git a/gui-clamp/ui-react/src/components/dialogs/Policy/ToscaViewer.test.js b/gui-clamp/ui-react/src/components/dialogs/Policy/ToscaViewer.test.js new file mode 100644 index 0000000..5b59760 --- /dev/null +++ b/gui-clamp/ui-react/src/components/dialogs/Policy/ToscaViewer.test.js @@ -0,0 +1,54 @@ +/*- + * ============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 ToscaViewer from './ToscaViewer'; +import { shallow, mount } from 'enzyme'; +import PolicyToscaService from '../../../api/PolicyToscaService'; + +describe('Verify ToscaViewer', () => { + const fs = require('fs'); + + let toscaYaml = fs.readFileSync('src/components/dialogs/Policy/toscaData.test.yaml', { encoding: 'utf8', flag: 'r' }) + + const toscaData = { + "policyModelType": "onap.policies.controlloop.Guard", + "version": "1.0.0", + "policyAcronym": "Guard", + "createdDate": "2021-04-09T02:29:31.407356Z", + "updatedDate": "2021-04-09T02:29:31.407356Z", + "updatedBy": "Not found", + "createdBy": "Not found", + "tableData": { + "id": 0 + } + }; + + it('Test the render method', async () => { + PolicyToscaService.getToscaPolicyModelYaml = jest.fn().mockImplementation(() => { + return Promise.resolve(toscaYaml); + }); + const component = shallow(<ToscaViewer toscaData={ toscaData }/>); + await PolicyToscaService.getToscaPolicyModelYaml(); + expect(component).toMatchSnapshot(); + }); +}); diff --git a/gui-clamp/ui-react/src/components/dialogs/Policy/ViewAllPolicies.js b/gui-clamp/ui-react/src/components/dialogs/Policy/ViewAllPolicies.js index a97deea..f571bc1 100644 --- a/gui-clamp/ui-react/src/components/dialogs/Policy/ViewAllPolicies.js +++ b/gui-clamp/ui-react/src/components/dialogs/Policy/ViewAllPolicies.js @@ -43,24 +43,27 @@ 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 PublishIcon from '@material-ui/icons/Publish'; 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 Select from '@material-ui/core/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'; +import PolicyDeploymentEditor from './PolicyDeploymentEditor'; +import PoliciesTreeViewer from './PoliciesTreeViewer'; +import PolicyToscaFileSelector from './PolicyToscaFileSelector'; const DivWhiteSpaceStyled = styled.div` white-space: pre; ` const ModalStyled = styled(Modal)` - @media (min-width: 1000px) { + @media (min-width: 800px) { .modal-xl { max-width: 96%; } @@ -73,26 +76,38 @@ const DetailedRow = styled.div` font-size: ${ props => props.theme.policyEditorFontSize }; width: 97%; margin-left: auto; - margin-right: 0; + margin-right: auto; + margin-top: 20px; ` +const PoliciesTreeViewerDiv = styled.div` + width: 20%; + float: left; + left: 0; + overflow: auto; +` + +const MaterialTableDiv = styled.div` + float: right; + width: 80%; + left: 20%; +` 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: [], + policiesListDataFiltered: [], toscaModelsListData: [], + toscaModelsListDataFiltered: [], jsonEditorForPolicy: new Map(), - prefixGrouping: false, showSuccessAlert: false, showFailAlert: false, + showFileSelector: false, policyColumnsDefinition: [ { title: "Policy Name", field: "name", @@ -102,7 +117,7 @@ export default class ViewAllPolicies extends React.Component { { title: "Policy Version", field: "version", cellStyle: standardCellStyle, - headerStyle: headerStyle + headerStyle: headerStyle, }, { title: "Policy Type", field: "type", @@ -115,20 +130,13 @@ export default class ViewAllPolicies extends React.Component { 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, + title: "Deployable in PDP Group", field: "supportedPdpGroupsString", + cellStyle: standardCellStyle, headerStyle: headerStyle }, { - title: "PDP SubGroup", field: "pdpGroupInfo.pdpSubGroup", - cellStyle: cellPdpGroupStyle, + title: "Deployed in PDP Group", field: "pdpGroupInfoString", + cellStyle: standardCellStyle, headerStyle: headerStyle } ], @@ -183,62 +191,63 @@ export default class ViewAllPolicies extends React.Component { 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.generateAdditionalPolicyColumns = this.generateAdditionalPolicyColumns.bind(this); + this.filterPolicies = this.filterPolicies.bind(this); + this.filterTosca = this.filterTosca.bind(this); + this.showFileSelector = this.showFileSelector.bind(this); + this.disableFileSelector = this.disableFileSelector.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"]; - } - } + generateAdditionalPolicyColumns(policiesData) { + policiesData.forEach(policy => { + let supportedPdpGroupsString = ""; + if (typeof policy.supportedPdpGroups !== "undefined") { + for (const pdpGroup of policy["supportedPdpGroups"]) { + for (const pdpSubGroup of Object.values(pdpGroup)[0]) { + supportedPdpGroupsString += (Object.keys(pdpGroup)[0] + "/" + pdpSubGroup + "\r\n"); + } + } + policy["supportedPdpGroupsString"] = supportedPdpGroupsString; + } - 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 + let infoPdpGroup = ""; + if (typeof policy.pdpGroupInfo !== "undefined") { + policy["pdpGroupInfo"].forEach(pdpGroupElem => { + let groupName = Object.keys(pdpGroupElem)[0]; + pdpGroupElem[groupName]["pdpSubgroups"].forEach(pdpSubGroupElem => { + infoPdpGroup += (groupName + "/" + pdpSubGroupElem["pdpType"] + " (" + + pdpGroupElem[groupName]["pdpGroupState"] + ")" + "\r\n"); }); - } + policy["pdpGroupInfoString"] = infoPdpGroup; + }); } - } - if (typeof dataRow.pdpGroupInfo !== "undefined") { - selectedItem = { - label: dataRow["pdpGroupInfo"]["pdpGroup"] + "/" + dataRow["pdpGroupInfo"]["pdpSubGroup"], - value: dataRow["pdpGroupInfo"]["pdpGroup"] + "/" + dataRow["pdpGroupInfo"]["pdpSubGroup"] - }; - } - return (<div style={ { width: '250px' } }><Select value={ selectedItem } options={ optionItems } onChange={ this.handlePdpGroupChange }/></div>); + }); + } + + getAllToscaModels() { + PolicyToscaService.getToscaPolicyModels().then(toscaModelsList => { + this.setState({ + toscaModelsListData: toscaModelsList, + toscaModelsListDataFiltered: toscaModelsList + }); + }); } getAllPolicies() { PolicyService.getPoliciesList().then(allPolicies => { - this.setState({ policiesListData: allPolicies["policies"] }) + this.generateAdditionalPolicyColumns(allPolicies["policies"]) + this.setState({ + policiesListData: allPolicies["policies"], + policiesListDataFiltered: allPolicies["policies"], + }) }); + } handleClose() { @@ -246,10 +255,6 @@ export default class ViewAllPolicies extends React.Component { this.props.history.push('/') } - handlePrefixGrouping(event) { - this.setState({ prefixGrouping: event.target.checked }); - } - handleDeletePolicy(event, rowData) { PolicyService.deletePolicy(rowData["type"], rowData["type_version"], rowData["name"], rowData["version"]).then( respPolicyDeletion => { @@ -264,8 +269,8 @@ export default class ViewAllPolicies extends React.Component { showSuccessAlert: true, showMessage: 'Policy successfully Deleted' }); + this.getAllPolicies(); } - this.getAllPolicies(); } ) } @@ -274,62 +279,92 @@ export default class ViewAllPolicies extends React.Component { this.setState({ showSuccessAlert: false, showFailAlert: false }); } + filterPolicies(prefixForFiltering) { + this.setState({ policiesListDataFiltered: this.state.policiesListData.filter(element => element.name.startsWith(prefixForFiltering)) }); + } + + filterTosca(prefixForFiltering) { + this.setState({ toscaModelsListDataFiltered: this.state.toscaModelsListData.filter(element => element.policyModelType.startsWith(prefixForFiltering)) }); + } + + showFileSelector() { + this.setState({ showFileSelector: true }); + } + + disableFileSelector() { + this.setState({ showFileSelector: false }); + } + renderPoliciesTab() { return ( <Tab eventKey="policies" title="Policies in Policy Framework"> <Modal.Body> - <FormControlLabel - control={ <Switch checked={ this.state.prefixGrouping } onChange={ this.handlePrefixGrouping }/> } - label="Group by prefix" - /> - <MaterialTable - title={ "Policies" } - data={ this.state.policiesListData } - columns={ this.state.policyColumnsDefinition } - icons={ this.state.tableIcons } - onRowClick={ (event, rowData, togglePanel) => togglePanel() } - options={ { - grouping: true, - exportButton: true, - headerStyle: rowHeaderStyle, - rowStyle: rowData => ({ - backgroundColor: (this.state.selectedRowId !== -1 && this.state.selectedRowId === rowData.tableData.id) ? '#EEE' : '#FFF' - }), - actionsColumnIndex: -1 - } } - detailPanel={ [ - { - icon: ArrowForwardIosIcon, - tooltip: 'Show Configuration', - render: rowData => { - return ( - <DetailedRow> - <PolicyEditor policyModelType={ rowData["type"] } policyModelTypeVersion={ rowData["type_version"] } policyName={ rowData["name"] } policyVersion={ rowData["version"] } - policyProperties={ rowData["properties"] } policyUpdateFunction={ this.getAllPolicies }/> - </DetailedRow> - ) - }, - }, - { - icon: DehazeIcon, - tooltip: 'Show Raw Data', - render: rowData => { - return ( - <DetailedRow> - <pre>{ JSON.stringify(rowData, null, 2) }</pre> - </DetailedRow> - ) - }, - }, - ] } - actions={ [ - { - icon: forwardRef((props, ref) => <DeleteRoundedIcon { ...props } ref={ ref }/>), - tooltip: 'Delete Policy', - onClick: (event, rowData) => this.handleDeletePolicy(event, rowData) - } - ] } - /> + <div> + <PoliciesTreeViewerDiv> + <PoliciesTreeViewer policiesData={ this.state.policiesListData } valueForTreeCreation="name" policiesFilterFunction={ this.filterPolicies }/> + </PoliciesTreeViewerDiv> + <MaterialTableDiv> + <MaterialTable + title={ "Policies" } + data={ this.state.policiesListDataFiltered } + columns={ this.state.policyColumnsDefinition } + icons={ this.state.tableIcons } + onRowClick={ (event, rowData, togglePanel) => togglePanel() } + options={ { + grouping: true, + exportButton: true, + headerStyle: rowHeaderStyle, + actionsColumnIndex: -1 + } } + detailPanel={ [ + { + icon: ArrowForwardIosIcon, + tooltip: 'Show Configuration', + render: rowData => { + return ( + <DetailedRow> + <PolicyEditor policyModelType={ rowData["type"] } policyModelTypeVersion={ rowData["type_version"] } + policyName={ rowData["name"] } policyVersion={ rowData["version"] } policyProperties={ rowData["properties"] } + policiesTableUpdateFunction={ this.getAllPolicies }/> + </DetailedRow> + ) + }, + }, + { + icon: DehazeIcon, + openIcon: DehazeIcon, + tooltip: 'Show Raw Data', + render: rowData => { + return ( + <DetailedRow> + <pre>{ JSON.stringify(rowData, null, 2) }</pre> + </DetailedRow> + ) + }, + }, + { + icon: PublishIcon, + openIcon: PublishIcon, + tooltip: 'PDP Group Deployment', + render: rowData => { + return ( + <DetailedRow> + <PolicyDeploymentEditor policyData={ rowData } policiesTableUpdateFunction={ this.getAllPolicies }/> + </DetailedRow> + ) + }, + } + ] } + actions={ [ + { + icon: DeleteRoundedIcon, + tooltip: 'Delete Policy', + onClick: (event, rowData) => this.handleDeletePolicy(event, rowData) + } + ] } + /> + </MaterialTableDiv> + </div> </Modal.Body> </Tab> ); @@ -339,61 +374,71 @@ export default class ViewAllPolicies extends React.Component { return ( <Tab eventKey="tosca models" title="Tosca Models in Policy Framework"> <Modal.Body> - <FormControlLabel - control={ <Switch checked={ this.state.prefixGrouping } onChange={ this.handlePrefixGrouping }/> } - label="Group by prefix" - /> - <MaterialTable - title={ "Tosca Models" } - data={ this.state.toscaModelsListData } - columns={ this.state.toscaColumnsDefinition } - icons={ this.state.tableIcons } - onRowClick={ (event, rowData, togglePanel) => togglePanel() } - options={ { - grouping: true, - exportButton: true, - headerStyle: rowHeaderStyle, - rowStyle: rowData => ({ - backgroundColor: (this.state.selectedRowId !== -1 && this.state.selectedRowId === rowData.tableData.id) ? '#EEE' : '#FFF' - }), - actionsColumnIndex: -1 - } } - detailPanel={ [ - { - icon: ArrowForwardIosIcon, - tooltip: 'Show Tosca', - render: rowData => { - return ( - <DetailedRow> - <ToscaViewer toscaData={ rowData }/> - </DetailedRow> - ) - }, - }, - { - icon: DehazeIcon, - tooltip: 'Show Raw Data', - render: rowData => { - return ( - <DetailedRow> - <pre>{ JSON.stringify(rowData, null, 2) }</pre> - </DetailedRow> - ) - }, - }, - { - icon: AddIcon, - tooltip: 'Create a policy from this model', - render: rowData => { - return ( - <DetailedRow> - <PolicyEditor policyModelType={ rowData["policyModelType"] } policyModelTypeVersion={ rowData["version"] } policyProperties={ {} } policyUpdateFunction={ this.getAllPolicies }/> - </DetailedRow> - ) - }, - }, - ] } - /> + <div> + <PoliciesTreeViewerDiv> + <PoliciesTreeViewer policiesData={ this.state.toscaModelsListData } valueForTreeCreation="policyModelType" policiesFilterFunction={ this.filterTosca }/> + </PoliciesTreeViewerDiv> + <MaterialTableDiv> + <MaterialTable + title={ "Tosca Models" } + data={ this.state.toscaModelsListDataFiltered } + columns={ this.state.toscaColumnsDefinition } + icons={ this.state.tableIcons } + onRowClick={ (event, rowData, togglePanel) => togglePanel() } + options={ { + grouping: true, + exportButton: true, + headerStyle: rowHeaderStyle, + actionsColumnIndex: -1 + } } + actions={ [ + { + icon: AddIcon, + tooltip: 'Add New Tosca Model', + isFreeAction: true, + onClick: () => this.showFileSelector() + } + ] } + detailPanel={ [ + { + icon: ArrowForwardIosIcon, + tooltip: 'Show Tosca', + render: rowData => { + return ( + <DetailedRow> + <ToscaViewer toscaData={ rowData }/> + </DetailedRow> + ) + }, + }, + { + icon: DehazeIcon, + openIcon: DehazeIcon, + tooltip: 'Show Raw Data', + render: rowData => { + return ( + <DetailedRow> + <pre>{ JSON.stringify(rowData, null, 2) }</pre> + </DetailedRow> + ) + }, + }, + { + icon: AddIcon, + openIcon: AddIcon, + tooltip: 'Create a policy from this model', + render: rowData => { + return ( + <DetailedRow> + <PolicyEditor policyModelType={ rowData["policyModelType"] } policyModelTypeVersion={ rowData["version"] } policyProperties={ {} } policiesTableUpdateFunction={ this.getAllPolicies }/> + </DetailedRow> + ) + }, + }, + ] } + /> + </MaterialTableDiv> + </div> </Modal.Body> </Tab> ); @@ -401,27 +446,30 @@ export default class ViewAllPolicies extends React.Component { render() { return ( - <ModalStyled size="xl" show={ this.state.show } onHide={ this.handleClose } backdrop="static" keyboard={ false }> - <Modal.Header closeButton> - </Modal.Header> - <Tabs id="controlled-tab-example" activeKey={ this.state.key } onSelect={ key => this.setState({ key, selectedRowData: {} }) }> - { this.renderPoliciesTab() } - { this.renderToscaTab() } - </Tabs> - <Alert variant="success" show={ this.state.showSuccessAlert } onClose={ this.disableAlert } dismissible> - <DivWhiteSpaceStyled> - { this.state.showMessage } - </DivWhiteSpaceStyled> - </Alert> - <Alert variant="danger" show={ this.state.showFailAlert } onClose={ this.disableAlert } dismissible> - <DivWhiteSpaceStyled> - { this.state.showMessage } - </DivWhiteSpaceStyled> - </Alert> - <Modal.Footer> - <Button variant="secondary" onClick={ this.handleClose }>Close</Button> - </Modal.Footer> - </ModalStyled> + <React.Fragment> + <ModalStyled size="xl" show={ this.state.show } onHide={ this.handleClose } backdrop="static" keyboard={ false }> + <Modal.Header closeButton> + </Modal.Header> + <Tabs id="controlled-tab-example" activeKey={ this.state.key } onSelect={ key => this.setState({ key, selectedRowData: {} }) }> + { this.renderPoliciesTab() } + { this.renderToscaTab() } + </Tabs> + <Alert variant="success" show={ this.state.showSuccessAlert } onClose={ this.disableAlert } dismissible> + <DivWhiteSpaceStyled> + { this.state.showMessage } + </DivWhiteSpaceStyled> + </Alert> + <Alert variant="danger" show={ this.state.showFailAlert } onClose={ this.disableAlert } dismissible> + <DivWhiteSpaceStyled> + { this.state.showMessage } + </DivWhiteSpaceStyled> + </Alert> + <Modal.Footer> + <Button variant="secondary" onClick={ this.handleClose }>Close</Button> + </Modal.Footer> + </ModalStyled> + <PolicyToscaFileSelector show={ this.state.showFileSelector } disableFunction={ this.disableFileSelector } toscaTableUpdateFunction={ this.getAllToscaModels }/> + </React.Fragment> ); } } diff --git a/gui-clamp/ui-react/src/components/dialogs/Policy/__snapshots__/PolicyEditor.test.js.snap b/gui-clamp/ui-react/src/components/dialogs/Policy/__snapshots__/PolicyEditor.test.js.snap new file mode 100644 index 0000000..bf84e91 --- /dev/null +++ b/gui-clamp/ui-react/src/components/dialogs/Policy/__snapshots__/PolicyEditor.test.js.snap @@ -0,0 +1,788 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Verify PolicyEditor Test the render method 1`] = ` +<PolicyEditor + policiesTableUpdateFunction={[Function]} + policyModelType="onap.policies.monitoring.tcagen2" + policyModelTypeVersion="1.0.0" + policyName="org.onap.new" + policyProperties={ + Object { + "tca.policy": Object { + "domain": "measurementsForVfScaling", + "metricsPerEventName": Array [ + Object { + "controlLoopSchemaType": "VM", + "eventName": "vLoadBalancer", + "policyName": "DCAE.Config_tca-hi-lo", + "policyScope": "DCAE", + "policyVersion": "v0.0.1", + "thresholds": Array [ + Object { + "closedLoopControlName": "LOOP_test", + "closedLoopEventStatus": "ONSET", + "direction": "LESS_OR_EQUAL", + "fieldPath": "$.event.measurementsForVfScalingFields.vNicPerformanceArray[*].receivedTotalPacketsDelta", + "severity": "MAJOR", + "thresholdValue": 200, + "version": "1.0.2", + }, + ], + }, + ], + }, + } + } + policyVersion="1.0.0" +> + <styled.div> + <div + className="sc-dlfnbm eVKaeY" + > + <Alert + closeLabel="Close alert" + dismissible={true} + onClose={[Function]} + show={false} + transition={ + Object { + "$$typeof": Symbol(react.forward_ref), + "defaultProps": Object { + "appear": false, + "in": false, + "mountOnEnter": false, + "timeout": 300, + "unmountOnExit": false, + }, + "render": [Function], + } + } + variant="success" + > + <Fade + appear={false} + in={false} + mountOnEnter={false} + timeout={300} + unmountOnExit={true} + > + <Transition + addEndListener={[Function]} + appear={false} + enter={true} + exit={true} + in={false} + mountOnEnter={false} + onEnter={[Function]} + onEntered={[Function]} + onEntering={[Function]} + onExit={[Function]} + onExited={[Function]} + onExiting={[Function]} + timeout={300} + unmountOnExit={true} + /> + </Fade> + </Alert> + <Alert + closeLabel="Close alert" + dismissible={true} + onClose={[Function]} + show={false} + transition={ + Object { + "$$typeof": Symbol(react.forward_ref), + "defaultProps": Object { + "appear": false, + "in": false, + "mountOnEnter": false, + "timeout": 300, + "unmountOnExit": false, + }, + "render": [Function], + } + } + variant="danger" + > + <Fade + appear={false} + in={false} + mountOnEnter={false} + timeout={300} + unmountOnExit={true} + > + <Transition + addEndListener={[Function]} + appear={false} + enter={true} + exit={true} + in={false} + mountOnEnter={false} + onEnter={[Function]} + onEntered={[Function]} + onEntering={[Function]} + onExit={[Function]} + onExited={[Function]} + onExiting={[Function]} + timeout={300} + unmountOnExit={true} + /> + </Fade> + </Alert> + <WithStyles(ForwardRef(TextField)) + defaultValue="org.onap.new" + id="policyName" + label="Required" + onChange={[Function]} + required={true} + size="small" + variant="outlined" + > + <ForwardRef(TextField) + classes={ + Object { + "root": "MuiTextField-root", + } + } + defaultValue="org.onap.new" + id="policyName" + label="Required" + onChange={[Function]} + required={true} + size="small" + variant="outlined" + > + <WithStyles(ForwardRef(FormControl)) + className="MuiTextField-root" + color="primary" + disabled={false} + error={false} + fullWidth={false} + required={true} + size="small" + variant="outlined" + > + <ForwardRef(FormControl) + className="MuiTextField-root" + classes={ + Object { + "fullWidth": "MuiFormControl-fullWidth", + "marginDense": "MuiFormControl-marginDense", + "marginNormal": "MuiFormControl-marginNormal", + "root": "MuiFormControl-root", + } + } + color="primary" + disabled={false} + error={false} + fullWidth={false} + required={true} + size="small" + variant="outlined" + > + <div + className="MuiFormControl-root MuiTextField-root" + > + <WithStyles(ForwardRef(InputLabel)) + htmlFor="policyName" + id="policyName-label" + > + <ForwardRef(InputLabel) + classes={ + Object { + "animated": "MuiInputLabel-animated", + "asterisk": "MuiInputLabel-asterisk", + "disabled": "Mui-disabled", + "error": "Mui-error", + "filled": "MuiInputLabel-filled", + "focused": "Mui-focused", + "formControl": "MuiInputLabel-formControl", + "marginDense": "MuiInputLabel-marginDense", + "outlined": "MuiInputLabel-outlined", + "required": "Mui-required", + "root": "MuiInputLabel-root", + "shrink": "MuiInputLabel-shrink", + } + } + htmlFor="policyName" + id="policyName-label" + > + <WithStyles(ForwardRef(FormLabel)) + className="MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-marginDense MuiInputLabel-outlined" + classes={ + Object { + "asterisk": "MuiInputLabel-asterisk", + "disabled": "Mui-disabled", + "error": "Mui-error", + "focused": "Mui-focused", + "required": "Mui-required", + } + } + data-shrink={true} + htmlFor="policyName" + id="policyName-label" + > + <ForwardRef(FormLabel) + className="MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-marginDense MuiInputLabel-outlined" + classes={ + Object { + "asterisk": "MuiFormLabel-asterisk MuiInputLabel-asterisk", + "colorSecondary": "MuiFormLabel-colorSecondary", + "disabled": "Mui-disabled Mui-disabled", + "error": "Mui-error Mui-error", + "filled": "MuiFormLabel-filled", + "focused": "Mui-focused Mui-focused", + "required": "Mui-required Mui-required", + "root": "MuiFormLabel-root", + } + } + data-shrink={true} + htmlFor="policyName" + id="policyName-label" + > + <label + className="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-marginDense MuiInputLabel-outlined MuiFormLabel-filled Mui-required Mui-required" + data-shrink={true} + htmlFor="policyName" + id="policyName-label" + > + Required + <span + aria-hidden={true} + className="MuiFormLabel-asterisk MuiInputLabel-asterisk" + > +   + * + </span> + </label> + </ForwardRef(FormLabel)> + </WithStyles(ForwardRef(FormLabel))> + </ForwardRef(InputLabel)> + </WithStyles(ForwardRef(InputLabel))> + <WithStyles(ForwardRef(OutlinedInput)) + autoFocus={false} + defaultValue="org.onap.new" + fullWidth={false} + id="policyName" + label={ + <React.Fragment> + Required +  * + </React.Fragment> + } + multiline={false} + onChange={[Function]} + > + <ForwardRef(OutlinedInput) + autoFocus={false} + classes={ + Object { + "adornedEnd": "MuiOutlinedInput-adornedEnd", + "adornedStart": "MuiOutlinedInput-adornedStart", + "colorSecondary": "MuiOutlinedInput-colorSecondary", + "disabled": "Mui-disabled", + "error": "Mui-error", + "focused": "Mui-focused", + "input": "MuiOutlinedInput-input", + "inputAdornedEnd": "MuiOutlinedInput-inputAdornedEnd", + "inputAdornedStart": "MuiOutlinedInput-inputAdornedStart", + "inputMarginDense": "MuiOutlinedInput-inputMarginDense", + "inputMultiline": "MuiOutlinedInput-inputMultiline", + "marginDense": "MuiOutlinedInput-marginDense", + "multiline": "MuiOutlinedInput-multiline", + "notchedOutline": "MuiOutlinedInput-notchedOutline", + "root": "MuiOutlinedInput-root", + } + } + defaultValue="org.onap.new" + fullWidth={false} + id="policyName" + label={ + <React.Fragment> + Required +  * + </React.Fragment> + } + multiline={false} + onChange={[Function]} + > + <WithStyles(ForwardRef(InputBase)) + autoFocus={false} + classes={ + Object { + "adornedEnd": "MuiOutlinedInput-adornedEnd", + "adornedStart": "MuiOutlinedInput-adornedStart", + "colorSecondary": "MuiOutlinedInput-colorSecondary", + "disabled": "Mui-disabled", + "error": "Mui-error", + "focused": "Mui-focused", + "input": "MuiOutlinedInput-input", + "inputAdornedEnd": "MuiOutlinedInput-inputAdornedEnd", + "inputAdornedStart": "MuiOutlinedInput-inputAdornedStart", + "inputMarginDense": "MuiOutlinedInput-inputMarginDense", + "inputMultiline": "MuiOutlinedInput-inputMultiline", + "marginDense": "MuiOutlinedInput-marginDense", + "multiline": "MuiOutlinedInput-multiline", + "notchedOutline": null, + "root": "MuiOutlinedInput-root", + } + } + defaultValue="org.onap.new" + fullWidth={false} + id="policyName" + inputComponent="input" + multiline={false} + onChange={[Function]} + renderSuffix={[Function]} + type="text" + > + <ForwardRef(InputBase) + autoFocus={false} + classes={ + Object { + "adornedEnd": "MuiInputBase-adornedEnd MuiOutlinedInput-adornedEnd", + "adornedStart": "MuiInputBase-adornedStart MuiOutlinedInput-adornedStart", + "colorSecondary": "MuiInputBase-colorSecondary MuiOutlinedInput-colorSecondary", + "disabled": "Mui-disabled Mui-disabled", + "error": "Mui-error Mui-error", + "focused": "Mui-focused Mui-focused", + "formControl": "MuiInputBase-formControl", + "fullWidth": "MuiInputBase-fullWidth", + "input": "MuiInputBase-input MuiOutlinedInput-input", + "inputAdornedEnd": "MuiInputBase-inputAdornedEnd MuiOutlinedInput-inputAdornedEnd", + "inputAdornedStart": "MuiInputBase-inputAdornedStart MuiOutlinedInput-inputAdornedStart", + "inputHiddenLabel": "MuiInputBase-inputHiddenLabel", + "inputMarginDense": "MuiInputBase-inputMarginDense MuiOutlinedInput-inputMarginDense", + "inputMultiline": "MuiInputBase-inputMultiline MuiOutlinedInput-inputMultiline", + "inputTypeSearch": "MuiInputBase-inputTypeSearch", + "marginDense": "MuiInputBase-marginDense MuiOutlinedInput-marginDense", + "multiline": "MuiInputBase-multiline MuiOutlinedInput-multiline", + "root": "MuiInputBase-root MuiOutlinedInput-root", + } + } + defaultValue="org.onap.new" + fullWidth={false} + id="policyName" + inputComponent="input" + multiline={false} + onChange={[Function]} + renderSuffix={[Function]} + type="text" + > + <div + className="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-formControl MuiInputBase-marginDense MuiOutlinedInput-marginDense" + onClick={[Function]} + > + <input + aria-invalid={false} + autoFocus={false} + className="MuiInputBase-input MuiOutlinedInput-input MuiInputBase-inputMarginDense MuiOutlinedInput-inputMarginDense" + defaultValue="org.onap.new" + disabled={false} + id="policyName" + onAnimationStart={[Function]} + onBlur={[Function]} + onChange={[Function]} + onFocus={[Function]} + required={true} + type="text" + /> + <WithStyles(ForwardRef(NotchedOutline)) + className="MuiOutlinedInput-notchedOutline" + label={ + <React.Fragment> + Required +  * + </React.Fragment> + } + labelWidth={0} + notched={true} + > + <ForwardRef(NotchedOutline) + className="MuiOutlinedInput-notchedOutline" + classes={ + Object { + "legend": "PrivateNotchedOutline-legend-2", + "legendLabelled": "PrivateNotchedOutline-legendLabelled-3", + "legendNotched": "PrivateNotchedOutline-legendNotched-4", + "root": "PrivateNotchedOutline-root-1", + } + } + label={ + <React.Fragment> + Required +  * + </React.Fragment> + } + labelWidth={0} + notched={true} + > + <fieldset + aria-hidden={true} + className="PrivateNotchedOutline-root-1 MuiOutlinedInput-notchedOutline" + > + <legend + className="PrivateNotchedOutline-legendLabelled-3 PrivateNotchedOutline-legendNotched-4" + > + <span> + Required +  * + </span> + </legend> + </fieldset> + </ForwardRef(NotchedOutline)> + </WithStyles(ForwardRef(NotchedOutline))> + </div> + </ForwardRef(InputBase)> + </WithStyles(ForwardRef(InputBase))> + </ForwardRef(OutlinedInput)> + </WithStyles(ForwardRef(OutlinedInput))> + </div> + </ForwardRef(FormControl)> + </WithStyles(ForwardRef(FormControl))> + </ForwardRef(TextField)> + </WithStyles(ForwardRef(TextField))> + <WithStyles(ForwardRef(TextField)) + defaultValue="1.0.0" + id="policyVersion" + label="Required" + onChange={[Function]} + required={true} + size="small" + variant="outlined" + > + <ForwardRef(TextField) + classes={ + Object { + "root": "MuiTextField-root", + } + } + defaultValue="1.0.0" + id="policyVersion" + label="Required" + onChange={[Function]} + required={true} + size="small" + variant="outlined" + > + <WithStyles(ForwardRef(FormControl)) + className="MuiTextField-root" + color="primary" + disabled={false} + error={false} + fullWidth={false} + required={true} + size="small" + variant="outlined" + > + <ForwardRef(FormControl) + className="MuiTextField-root" + classes={ + Object { + "fullWidth": "MuiFormControl-fullWidth", + "marginDense": "MuiFormControl-marginDense", + "marginNormal": "MuiFormControl-marginNormal", + "root": "MuiFormControl-root", + } + } + color="primary" + disabled={false} + error={false} + fullWidth={false} + required={true} + size="small" + variant="outlined" + > + <div + className="MuiFormControl-root MuiTextField-root" + > + <WithStyles(ForwardRef(InputLabel)) + htmlFor="policyVersion" + id="policyVersion-label" + > + <ForwardRef(InputLabel) + classes={ + Object { + "animated": "MuiInputLabel-animated", + "asterisk": "MuiInputLabel-asterisk", + "disabled": "Mui-disabled", + "error": "Mui-error", + "filled": "MuiInputLabel-filled", + "focused": "Mui-focused", + "formControl": "MuiInputLabel-formControl", + "marginDense": "MuiInputLabel-marginDense", + "outlined": "MuiInputLabel-outlined", + "required": "Mui-required", + "root": "MuiInputLabel-root", + "shrink": "MuiInputLabel-shrink", + } + } + htmlFor="policyVersion" + id="policyVersion-label" + > + <WithStyles(ForwardRef(FormLabel)) + className="MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-marginDense MuiInputLabel-outlined" + classes={ + Object { + "asterisk": "MuiInputLabel-asterisk", + "disabled": "Mui-disabled", + "error": "Mui-error", + "focused": "Mui-focused", + "required": "Mui-required", + } + } + data-shrink={true} + htmlFor="policyVersion" + id="policyVersion-label" + > + <ForwardRef(FormLabel) + className="MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-marginDense MuiInputLabel-outlined" + classes={ + Object { + "asterisk": "MuiFormLabel-asterisk MuiInputLabel-asterisk", + "colorSecondary": "MuiFormLabel-colorSecondary", + "disabled": "Mui-disabled Mui-disabled", + "error": "Mui-error Mui-error", + "filled": "MuiFormLabel-filled", + "focused": "Mui-focused Mui-focused", + "required": "Mui-required Mui-required", + "root": "MuiFormLabel-root", + } + } + data-shrink={true} + htmlFor="policyVersion" + id="policyVersion-label" + > + <label + className="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-marginDense MuiInputLabel-outlined MuiFormLabel-filled Mui-required Mui-required" + data-shrink={true} + htmlFor="policyVersion" + id="policyVersion-label" + > + Required + <span + aria-hidden={true} + className="MuiFormLabel-asterisk MuiInputLabel-asterisk" + > +   + * + </span> + </label> + </ForwardRef(FormLabel)> + </WithStyles(ForwardRef(FormLabel))> + </ForwardRef(InputLabel)> + </WithStyles(ForwardRef(InputLabel))> + <WithStyles(ForwardRef(OutlinedInput)) + autoFocus={false} + defaultValue="1.0.0" + fullWidth={false} + id="policyVersion" + label={ + <React.Fragment> + Required +  * + </React.Fragment> + } + multiline={false} + onChange={[Function]} + > + <ForwardRef(OutlinedInput) + autoFocus={false} + classes={ + Object { + "adornedEnd": "MuiOutlinedInput-adornedEnd", + "adornedStart": "MuiOutlinedInput-adornedStart", + "colorSecondary": "MuiOutlinedInput-colorSecondary", + "disabled": "Mui-disabled", + "error": "Mui-error", + "focused": "Mui-focused", + "input": "MuiOutlinedInput-input", + "inputAdornedEnd": "MuiOutlinedInput-inputAdornedEnd", + "inputAdornedStart": "MuiOutlinedInput-inputAdornedStart", + "inputMarginDense": "MuiOutlinedInput-inputMarginDense", + "inputMultiline": "MuiOutlinedInput-inputMultiline", + "marginDense": "MuiOutlinedInput-marginDense", + "multiline": "MuiOutlinedInput-multiline", + "notchedOutline": "MuiOutlinedInput-notchedOutline", + "root": "MuiOutlinedInput-root", + } + } + defaultValue="1.0.0" + fullWidth={false} + id="policyVersion" + label={ + <React.Fragment> + Required +  * + </React.Fragment> + } + multiline={false} + onChange={[Function]} + > + <WithStyles(ForwardRef(InputBase)) + autoFocus={false} + classes={ + Object { + "adornedEnd": "MuiOutlinedInput-adornedEnd", + "adornedStart": "MuiOutlinedInput-adornedStart", + "colorSecondary": "MuiOutlinedInput-colorSecondary", + "disabled": "Mui-disabled", + "error": "Mui-error", + "focused": "Mui-focused", + "input": "MuiOutlinedInput-input", + "inputAdornedEnd": "MuiOutlinedInput-inputAdornedEnd", + "inputAdornedStart": "MuiOutlinedInput-inputAdornedStart", + "inputMarginDense": "MuiOutlinedInput-inputMarginDense", + "inputMultiline": "MuiOutlinedInput-inputMultiline", + "marginDense": "MuiOutlinedInput-marginDense", + "multiline": "MuiOutlinedInput-multiline", + "notchedOutline": null, + "root": "MuiOutlinedInput-root", + } + } + defaultValue="1.0.0" + fullWidth={false} + id="policyVersion" + inputComponent="input" + multiline={false} + onChange={[Function]} + renderSuffix={[Function]} + type="text" + > + <ForwardRef(InputBase) + autoFocus={false} + classes={ + Object { + "adornedEnd": "MuiInputBase-adornedEnd MuiOutlinedInput-adornedEnd", + "adornedStart": "MuiInputBase-adornedStart MuiOutlinedInput-adornedStart", + "colorSecondary": "MuiInputBase-colorSecondary MuiOutlinedInput-colorSecondary", + "disabled": "Mui-disabled Mui-disabled", + "error": "Mui-error Mui-error", + "focused": "Mui-focused Mui-focused", + "formControl": "MuiInputBase-formControl", + "fullWidth": "MuiInputBase-fullWidth", + "input": "MuiInputBase-input MuiOutlinedInput-input", + "inputAdornedEnd": "MuiInputBase-inputAdornedEnd MuiOutlinedInput-inputAdornedEnd", + "inputAdornedStart": "MuiInputBase-inputAdornedStart MuiOutlinedInput-inputAdornedStart", + "inputHiddenLabel": "MuiInputBase-inputHiddenLabel", + "inputMarginDense": "MuiInputBase-inputMarginDense MuiOutlinedInput-inputMarginDense", + "inputMultiline": "MuiInputBase-inputMultiline MuiOutlinedInput-inputMultiline", + "inputTypeSearch": "MuiInputBase-inputTypeSearch", + "marginDense": "MuiInputBase-marginDense MuiOutlinedInput-marginDense", + "multiline": "MuiInputBase-multiline MuiOutlinedInput-multiline", + "root": "MuiInputBase-root MuiOutlinedInput-root", + } + } + defaultValue="1.0.0" + fullWidth={false} + id="policyVersion" + inputComponent="input" + multiline={false} + onChange={[Function]} + renderSuffix={[Function]} + type="text" + > + <div + className="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-formControl MuiInputBase-marginDense MuiOutlinedInput-marginDense" + onClick={[Function]} + > + <input + aria-invalid={false} + autoFocus={false} + className="MuiInputBase-input MuiOutlinedInput-input MuiInputBase-inputMarginDense MuiOutlinedInput-inputMarginDense" + defaultValue="1.0.0" + disabled={false} + id="policyVersion" + onAnimationStart={[Function]} + onBlur={[Function]} + onChange={[Function]} + onFocus={[Function]} + required={true} + type="text" + /> + <WithStyles(ForwardRef(NotchedOutline)) + className="MuiOutlinedInput-notchedOutline" + label={ + <React.Fragment> + Required +  * + </React.Fragment> + } + labelWidth={0} + notched={true} + > + <ForwardRef(NotchedOutline) + className="MuiOutlinedInput-notchedOutline" + classes={ + Object { + "legend": "PrivateNotchedOutline-legend-2", + "legendLabelled": "PrivateNotchedOutline-legendLabelled-3", + "legendNotched": "PrivateNotchedOutline-legendNotched-4", + "root": "PrivateNotchedOutline-root-1", + } + } + label={ + <React.Fragment> + Required +  * + </React.Fragment> + } + labelWidth={0} + notched={true} + > + <fieldset + aria-hidden={true} + className="PrivateNotchedOutline-root-1 MuiOutlinedInput-notchedOutline" + > + <legend + className="PrivateNotchedOutline-legendLabelled-3 PrivateNotchedOutline-legendNotched-4" + > + <span> + Required +  * + </span> + </legend> + </fieldset> + </ForwardRef(NotchedOutline)> + </WithStyles(ForwardRef(NotchedOutline))> + </div> + </ForwardRef(InputBase)> + </WithStyles(ForwardRef(InputBase))> + </ForwardRef(OutlinedInput)> + </WithStyles(ForwardRef(OutlinedInput))> + </div> + </ForwardRef(FormControl)> + </WithStyles(ForwardRef(FormControl))> + </ForwardRef(TextField)> + </WithStyles(ForwardRef(TextField))> + <Button + active={false} + disabled={false} + onClick={[Function]} + title="Create a new policy version from the defined parameters" + variant="secondary" + > + <button + className="btn btn-secondary" + disabled={false} + onClick={[Function]} + title="Create a new policy version from the defined parameters" + type="button" + > + Create New Version + </button> + </Button> + <styled.div + id="onap.policies.monitoring.tcagen2_1.0.0_org.onap.new_1.0.0" + title="Policy Properties" + > + <div + className="sc-gsTCUz hXXDCR" + id="onap.policies.monitoring.tcagen2_1.0.0_org.onap.new_1.0.0" + title="Policy Properties" + /> + </styled.div> + </div> + </styled.div> +</PolicyEditor> +`; diff --git a/gui-clamp/ui-react/src/components/dialogs/Policy/__snapshots__/PolicyModal.test.js.snap b/gui-clamp/ui-react/src/components/dialogs/Policy/__snapshots__/PolicyModal.test.js.snap new file mode 100644 index 0000000..8b1261b --- /dev/null +++ b/gui-clamp/ui-react/src/components/dialogs/Policy/__snapshots__/PolicyModal.test.js.snap @@ -0,0 +1,159 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Verify PolicyModal Test the render method 1`] = ` +<Styled(Modal) + backdrop="static" + keyboard={false} + onHide={[Function]} + show={true} + size="xl" +> + <ModalHeader + closeButton={true} + closeLabel="Close" + > + <ModalTitle> + Edit the policy + </ModalTitle> + </ModalHeader> + <Alert + closeLabel="Close alert" + dismissible={true} + onClose={[Function]} + show={false} + transition={ + Object { + "$$typeof": Symbol(react.forward_ref), + "defaultProps": Object { + "appear": false, + "in": false, + "mountOnEnter": false, + "timeout": 300, + "unmountOnExit": false, + }, + "render": [Function], + } + } + variant="success" + > + <styled.div /> + </Alert> + <Alert + closeLabel="Close alert" + dismissible={true} + onClose={[Function]} + show={false} + transition={ + Object { + "$$typeof": Symbol(react.forward_ref), + "defaultProps": Object { + "appear": false, + "in": false, + "mountOnEnter": false, + "timeout": 300, + "unmountOnExit": false, + }, + "render": [Function], + } + } + variant="danger" + > + <styled.div /> + </Alert> + <ModalBody> + <div + id="editor" + /> + <FormGroup + as={ + Object { + "$$typeof": Symbol(react.forward_ref), + "defaultProps": Object { + "noGutters": false, + }, + "render": [Function], + } + } + controlId="formPlaintextEmail" + > + <FormLabel + column={true} + sm="2" + srOnly={false} + > + Pdp Group Info + </FormLabel> + <Col + sm="3" + > + <StateManager + defaultInputValue="" + defaultMenuIsOpen={false} + defaultValue={null} + onChange={[Function]} + options={ + Array [ + Object { + "label": "monitoring", + "value": "monitoring", + }, + ] + } + value={ + Object { + "label": undefined, + "value": undefined, + } + } + /> + </Col> + <Col + sm="3" + > + <StateManager + defaultInputValue="" + defaultMenuIsOpen={false} + defaultValue={null} + onChange={[Function]} + options={Array []} + value={ + Object { + "label": undefined, + "value": undefined, + } + } + /> + </Col> + </FormGroup> + </ModalBody> + <ModalFooter> + <Button + active={false} + disabled={false} + key="close" + onClick={[Function]} + variant="secondary" + > + Close + </Button> + <Button + active={false} + disabled={false} + key="save" + onClick={[Function]} + variant="primary" + > + Save Changes + </Button> + <Button + active={false} + disabled={false} + key="refresh" + onClick={[Function]} + variant="primary" + > + Refresh + </Button> + </ModalFooter> +</Styled(Modal)> +`; diff --git a/gui-clamp/ui-react/src/components/dialogs/Policy/__snapshots__/ToscaViewer.test.js.snap b/gui-clamp/ui-react/src/components/dialogs/Policy/__snapshots__/ToscaViewer.test.js.snap new file mode 100644 index 0000000..61fb485 --- /dev/null +++ b/gui-clamp/ui-react/src/components/dialogs/Policy/__snapshots__/ToscaViewer.test.js.snap @@ -0,0 +1,30 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Verify ToscaViewer Test the render method 1`] = ` +<styled.div> + <pre> + tosca_definitions_version: tosca_simple_yaml_1_1_0 +policy_types: + onap.policies.controlloop.Guard: + properties: { + } + name: onap.policies.controlloop.Guard + version: 1.0.0 + derived_from: tosca.policies.Root + metadata: { + } + description: Guard Policies for Control Loop Operational Policies +name: ToscaServiceTemplateSimple +version: 1.0.0 + + </pre> + <Button + active={false} + disabled={false} + title="Create a new policy version from the defined parameters" + variant="secondary" + > + Create New Version + </Button> +</styled.div> +`; diff --git a/gui-clamp/ui-react/src/components/dialogs/Policy/toscaData.test.json b/gui-clamp/ui-react/src/components/dialogs/Policy/toscaData.test.json new file mode 100644 index 0000000..3b001b3 --- /dev/null +++ b/gui-clamp/ui-react/src/components/dialogs/Policy/toscaData.test.json @@ -0,0 +1,179 @@ +{ + "title": "onap.policies.monitoring.tcagen2", + "type": "object", + "required": [ + "tca.policy" + ], + "properties": { + "tca.policy": { + "title": "onap.datatypes.monitoring.tca_policy", + "type": "object", + "required": [ + "domain", + "metricsPerEventName" + ], + "properties": { + "domain": { + "type": "string", + "description": "Domain name to which TCA needs to be applied", + "default": "measurementsForVfScaling", + "const": "measurementsForVfScaling" + }, + "metricsPerEventName": { + "type": "array", + "description": "Contains eventName and threshold details that need to be applied to given eventName", + "items": { + "title": "onap.datatypes.monitoring.metricsPerEventName", + "type": "object", + "required": [ + "controlLoopSchemaType", + "eventName", + "policyName", + "policyScope", + "policyVersion", + "thresholds" + ], + "properties": { + "controlLoopSchemaType": { + "type": "string", + "description": "Specifies Control Loop Schema Type for the event Name e.g. VNF, VM", + "enum": [ + "VM", + "VNF" + ] + }, + "eventName": { + "type": "string", + "description": "Event name to which thresholds need to be applied" + }, + "policyName": { + "type": "string", + "description": "TCA Policy Scope Name" + }, + "policyScope": { + "type": "string", + "description": "TCA Policy Scope" + }, + "policyVersion": { + "type": "string", + "description": "TCA Policy Scope Version" + }, + "thresholds": { + "type": "array", + "description": "Thresholds associated with eventName", + "items": { + "title": "onap.datatypes.monitoring.thresholds", + "type": "object", + "required": [ + "closedLoopControlName", + "closedLoopEventStatus", + "direction", + "fieldPath", + "severity", + "thresholdValue", + "version" + ], + "properties": { + "closedLoopControlName": { + "type": "string", + "description": "Closed Loop Control Name associated with the threshold" + }, + "closedLoopEventStatus": { + "type": "string", + "description": "Closed Loop Event Status of the threshold", + "enum": [ + "ONSET", + "ABATED" + ] + }, + "direction": { + "type": "string", + "description": "Direction of the threshold", + "enum": [ + "LESS", + "LESS_OR_EQUAL", + "GREATER", + "GREATER_OR_EQUAL", + "EQUAL" + ] + }, + "fieldPath": { + "type": "string", + "description": "Json field Path as per CEF message which needs to be analyzed for TCA", + "enum": [ + "$.event.measurementsForVfScalingFields.vNicPerformanceArray[*].receivedTotalPacketsDelta", + "$.event.measurementsForVfScalingFields.vNicPerformanceArray[*].receivedOctetsDelta", + "$.event.measurementsForVfScalingFields.vNicPerformanceArray[*].receivedUnicastPacketsDelta", + "$.event.measurementsForVfScalingFields.vNicPerformanceArray[*].receivedMulticastPacketsDelta", + "$.event.measurementsForVfScalingFields.vNicPerformanceArray[*].receivedBroadcastPacketsDelta", + "$.event.measurementsForVfScalingFields.vNicPerformanceArray[*].receivedDiscardedPacketsDelta", + "$.event.measurementsForVfScalingFields.vNicPerformanceArray[*].receivedErrorPacketsDelta", + "$.event.measurementsForVfScalingFields.vNicPerformanceArray[*].receivedTotalPacketsAccumulated", + "$.event.measurementsForVfScalingFields.vNicPerformanceArray[*].receivedOctetsAccumulated", + "$.event.measurementsForVfScalingFields.vNicPerformanceArray[*].receivedUnicastPacketsAccumulated", + "$.event.measurementsForVfScalingFields.vNicPerformanceArray[*].receivedMulticastPacketsAccumulated", + "$.event.measurementsForVfScalingFields.vNicPerformanceArray[*].receivedBroadcastPacketsAccumulated", + "$.event.measurementsForVfScalingFields.vNicPerformanceArray[*].receivedDiscardedPacketsAccumulated", + "$.event.measurementsForVfScalingFields.vNicPerformanceArray[*].receivedErrorPacketsAccumulated", + "$.event.measurementsForVfScalingFields.vNicPerformanceArray[*].transmittedTotalPacketsDelta", + "$.event.measurementsForVfScalingFields.vNicPerformanceArray[*].transmittedOctetsDelta", + "$.event.measurementsForVfScalingFields.vNicPerformanceArray[*].transmittedUnicastPacketsDelta", + "$.event.measurementsForVfScalingFields.vNicPerformanceArray[*].transmittedMulticastPacketsDelta", + "$.event.measurementsForVfScalingFields.vNicPerformanceArray[*].transmittedBroadcastPacketsDelta", + "$.event.measurementsForVfScalingFields.vNicPerformanceArray[*].transmittedDiscardedPacketsDelta", + "$.event.measurementsForVfScalingFields.vNicPerformanceArray[*].transmittedErrorPacketsDelta", + "$.event.measurementsForVfScalingFields.vNicPerformanceArray[*].transmittedTotalPacketsAccumulated", + "$.event.measurementsForVfScalingFields.vNicPerformanceArray[*].transmittedOctetsAccumulated", + "$.event.measurementsForVfScalingFields.vNicPerformanceArray[*].transmittedUnicastPacketsAccumulated", + "$.event.measurementsForVfScalingFields.vNicPerformanceArray[*].transmittedMulticastPacketsAccumulated", + "$.event.measurementsForVfScalingFields.vNicPerformanceArray[*].transmittedBroadcastPacketsAccumulated", + "$.event.measurementsForVfScalingFields.vNicPerformanceArray[*].transmittedDiscardedPacketsAccumulated", + "$.event.measurementsForVfScalingFields.vNicPerformanceArray[*].transmittedErrorPacketsAccumulated", + "$.event.measurementsForVfScalingFields.cpuUsageArray[*].cpuIdle", + "$.event.measurementsForVfScalingFields.cpuUsageArray[*].cpuUsageInterrupt", + "$.event.measurementsForVfScalingFields.cpuUsageArray[*].cpuUsageNice", + "$.event.measurementsForVfScalingFields.cpuUsageArray[*].cpuUsageSoftIrq", + "$.event.measurementsForVfScalingFields.cpuUsageArray[*].cpuUsageSteal", + "$.event.measurementsForVfScalingFields.cpuUsageArray[*].cpuUsageSystem", + "$.event.measurementsForVfScalingFields.cpuUsageArray[*].cpuWait", + "$.event.measurementsForVfScalingFields.cpuUsageArray[*].percentUsage", + "$.event.measurementsForVfScalingFields.meanRequestLatency", + "$.event.measurementsForVfScalingFields.memoryUsageArray[*].memoryBuffered", + "$.event.measurementsForVfScalingFields.memoryUsageArray[*].memoryCached", + "$.event.measurementsForVfScalingFields.memoryUsageArray[*].memoryConfigured", + "$.event.measurementsForVfScalingFields.memoryUsageArray[*].memoryFree", + "$.event.measurementsForVfScalingFields.memoryUsageArray[*].memoryUsed", + "$.event.measurementsForVfScalingFields.additionalMeasurements[*].arrayOfFields[0].value" + ] + }, + "severity": { + "type": "string", + "description": "Threshold Event Severity", + "enum": [ + "CRITICAL", + "MAJOR", + "MINOR", + "WARNING", + "NORMAL" + ] + }, + "thresholdValue": { + "type": "integer", + "description": "Threshold value for the field Path inside CEF message" + }, + "version": { + "type": "string", + "description": "Version number associated with the threshold" + } + } + }, + "format": "tabs-top" + } + } + }, + "format": "tabs-top" + } + } + } + } +} diff --git a/gui-clamp/ui-react/src/components/dialogs/Policy/toscaData.test.yaml b/gui-clamp/ui-react/src/components/dialogs/Policy/toscaData.test.yaml new file mode 100644 index 0000000..15a3cec --- /dev/null +++ b/gui-clamp/ui-react/src/components/dialogs/Policy/toscaData.test.yaml @@ -0,0 +1,13 @@ +tosca_definitions_version: tosca_simple_yaml_1_1_0 +policy_types: + onap.policies.controlloop.Guard: + properties: { + } + name: onap.policies.controlloop.Guard + version: 1.0.0 + derived_from: tosca.policies.Root + metadata: { + } + description: Guard Policies for Control Loop Operational Policies +name: ToscaServiceTemplateSimple +version: 1.0.0 diff --git a/gui-clamp/ui-react/src/components/dialogs/Tosca/ViewLoopTemplatesModal.js b/gui-clamp/ui-react/src/components/dialogs/Tosca/ViewLoopTemplatesModal.js index 7257337..e7be984 100644 --- a/gui-clamp/ui-react/src/components/dialogs/Tosca/ViewLoopTemplatesModal.js +++ b/gui-clamp/ui-react/src/components/dialogs/Tosca/ViewLoopTemplatesModal.js @@ -1,8 +1,8 @@ /*- * ============LICENSE_START======================================================= - * ONAP CLAMP + * ONAP POLICY-CLAMP * ================================================================================ - * Copyright (C) 2019 AT&T Intellectual Property. All rights + * Copyright (C) 2019, 2021 AT&T Intellectual Property. All rights * reserved. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/gui-clamp/ui-react/src/components/dialogs/UserInfoModal.js b/gui-clamp/ui-react/src/components/dialogs/UserInfoModal.js index e58c61a..d452f6d 100644 --- a/gui-clamp/ui-react/src/components/dialogs/UserInfoModal.js +++ b/gui-clamp/ui-react/src/components/dialogs/UserInfoModal.js @@ -60,7 +60,7 @@ export default class UserInfoModal extends React.Component { renderPermissions() { if (this.state.userInfo["allPermissions"]) { var listOfPermissions = this.state.userInfo["allPermissions"].map(function (perm) { - return <Form.Control plaintext readOnly defaultValue={ perm }/>; + return <Form.Control key={ perm } plaintext readOnly defaultValue={ perm }/>; }) return listOfPermissions; } else { diff --git a/gui-clamp/ui-react/src/components/dialogs/__snapshots__/UserInfoModal.test.js.snap b/gui-clamp/ui-react/src/components/dialogs/__snapshots__/UserInfoModal.test.js.snap index 7c725bc..548a2d9 100644 --- a/gui-clamp/ui-react/src/components/dialogs/__snapshots__/UserInfoModal.test.js.snap +++ b/gui-clamp/ui-react/src/components/dialogs/__snapshots__/UserInfoModal.test.js.snap @@ -91,11 +91,13 @@ exports[`Verify UserInfoModal Test the render method full permission 1`] = ` <Col> <FormControl defaultValue="permission1" + key="permission1" plaintext={true} readOnly={true} /> <FormControl defaultValue="permission2" + key="permission2" plaintext={true} readOnly={true} /> diff --git a/gui-clamp/ui-react/src/components/loop_viewer/logs/LoopLogs.js b/gui-clamp/ui-react/src/components/loop_viewer/logs/LoopLogs.js index b03b740..3435ba3 100644 --- a/gui-clamp/ui-react/src/components/loop_viewer/logs/LoopLogs.js +++ b/gui-clamp/ui-react/src/components/loop_viewer/logs/LoopLogs.js @@ -67,10 +67,9 @@ export default class LoopLogs extends React.Component { } renderLogs() { - if (this.state.loopCache.getLoopLogsArray() != null) { - return ( - this.state.loopCache.getLoopLogsArray().map(row => <TableRow logRow={ row }/>) - ) + let logsArray = this.state.loopCache.getLoopLogsArray(); + if (logsArray != null) { + return (logsArray.map(row => <TableRow key={ row.id } logRow={ row }/>)); } } diff --git a/gui-clamp/ui-react/src/components/loop_viewer/logs/__snapshots__/LoopLogs.test.js.snap b/gui-clamp/ui-react/src/components/loop_viewer/logs/__snapshots__/LoopLogs.test.js.snap index 75b817b..996c674 100644 --- a/gui-clamp/ui-react/src/components/loop_viewer/logs/__snapshots__/LoopLogs.test.js.snap +++ b/gui-clamp/ui-react/src/components/loop_viewer/logs/__snapshots__/LoopLogs.test.js.snap @@ -45,6 +45,7 @@ exports[`Verify LoopLogs Test the render method 1`] = ` </thead> <tbody> <TableRow + key="1" logRow={ Object { "id": 1, diff --git a/gui-clamp/ui-react/src/components/loop_viewer/status/LoopStatus.js b/gui-clamp/ui-react/src/components/loop_viewer/status/LoopStatus.js index 2994c84..f539ad4 100644 --- a/gui-clamp/ui-react/src/components/loop_viewer/status/LoopStatus.js +++ b/gui-clamp/ui-react/src/components/loop_viewer/status/LoopStatus.js @@ -57,12 +57,13 @@ export default class LoopStatus extends React.Component { renderStatus() { - if (this.state.loopCache.getComponentStates() != null) { - return Object.keys(this.state.loopCache.getComponentStates()).map((key) => { + let componentStates = this.state.loopCache.getComponentStates(); + if (componentStates != null) { + return Object.keys(componentStates).map((key) => { console.debug("Adding status for: ", key); var res = {} res[key] = this.state.loopCache.getComponentStates()[key]; - return (<TableRow statusRow={ { + return (<TableRow key={ key } statusRow={ { 'componentName': key, 'stateName': this.state.loopCache.getComponentStates()[key].componentState.stateName, 'description': this.state.loopCache.getComponentStates()[key].componentState.description diff --git a/gui-clamp/ui-react/src/components/loop_viewer/status/__snapshots__/LoopStatus.test.js.snap b/gui-clamp/ui-react/src/components/loop_viewer/status/__snapshots__/LoopStatus.test.js.snap index 73da5ff..24d879d 100644 --- a/gui-clamp/ui-react/src/components/loop_viewer/status/__snapshots__/LoopStatus.test.js.snap +++ b/gui-clamp/ui-react/src/components/loop_viewer/status/__snapshots__/LoopStatus.test.js.snap @@ -40,6 +40,7 @@ exports[`Verify LoopStatus Test the render method 1`] = ` </thead> <tbody> <TableRow + key="POLICY" statusRow={ Object { "componentName": "POLICY", @@ -49,6 +50,7 @@ exports[`Verify LoopStatus Test the render method 1`] = ` } /> <TableRow + key="DCAE" statusRow={ Object { "componentName": "DCAE", diff --git a/gui-clamp/ui-react/src/components/loop_viewer/svg/SvgGenerator.js b/gui-clamp/ui-react/src/components/loop_viewer/svg/SvgGenerator.js index f5f5047..2692aef 100644 --- a/gui-clamp/ui-react/src/components/loop_viewer/svg/SvgGenerator.js +++ b/gui-clamp/ui-react/src/components/loop_viewer/svg/SvgGenerator.js @@ -27,220 +27,222 @@ import LoopCache from '../../../api/LoopCache'; import OnapConstant from '../../../utils/OnapConstants'; const DivStyled = styled.div` - overflow-x: scroll; - display: flex; - width: 100%; - height: 100%; + overflow-x: scroll; + display: flex; + width: 100%; + height: 100%; ` -const emptySvg = (<svg> <text x="60" y="40">No LOOP (SVG)</text> </svg>); +const emptySvg = (<svg> + <text x="60" y="40">No LOOP (SVG)</text> +</svg>); class SvgGenerator extends React.Component { - boxWidth = 200; - boxHeight = 100; - boxSpace = 50; - - static GENERATED_FROM_INSTANCE = "INSTANCE"; - static GENERATED_FROM_TEMPLATE = "TEMPLATE"; - - state = { - loopCache: new LoopCache({}), - clickable: false, - generatedFrom: SvgGenerator.GENERATED_FROM_INSTANCE, // INSTANCE / TEMPLATE - } - - constructor(props) { - super(props); - this.state.loopCache = props.loopCache; - this.state.clickable = props.clickable; - this.state.generatedFrom = props.generatedFrom; - this.handleSvgClick = this.handleSvgClick.bind(this); - this.renderSvg = this.renderSvg.bind(this); - } - - shouldComponentUpdate(nextProps, nextState) { - return this.state.loopCache !== nextState.loopCache; - } - - componentWillReceiveProps(newProps) { - if (this.state.loopCache !== newProps.loopCache) { - this.setState({ - loopCache: newProps.loopCache, - }); - } - } - - handleSvgClick(event) { - console.debug("svg click event received"); - if (this.state.clickable) { - var elementName = event.target.parentNode.getAttribute('policyId'); - console.info("SVG element clicked", elementName); - // Only allow movement to policy editing IF there busyLoadingCOunt is 0, - // meaning we are not waiting for refreshStatus to complete, for example - if (elementName !== null && !this.props.isBusyLoading()) { - this.props.history.push("/policyModal/"+event.target.parentNode.getAttribute('policyType')+"/"+elementName); - } - } - } - - createVesBox (xPos) { - return this.createOneBox(xPos,null,null,'VES Collector','VES',null); + boxWidth = 200; + boxHeight = 100; + boxSpace = 50; + + static GENERATED_FROM_INSTANCE = "INSTANCE"; + static GENERATED_FROM_TEMPLATE = "TEMPLATE"; + + state = { + loopCache: new LoopCache({}), + clickable: false, + generatedFrom: SvgGenerator.GENERATED_FROM_INSTANCE, // INSTANCE / TEMPLATE + } + + constructor(props) { + super(props); + this.state.loopCache = props.loopCache; + this.state.clickable = props.clickable; + this.state.generatedFrom = props.generatedFrom; + this.handleSvgClick = this.handleSvgClick.bind(this); + this.renderSvg = this.renderSvg.bind(this); + } + + shouldComponentUpdate(nextProps, nextState) { + return this.state.loopCache !== nextProps.loopCache; + } + + componentDidUpdate(prevProps) { + if (prevProps.loopCache !== this.props.loopCache) { + this.setState({ + loopCache: this.props.loopCache, + }); } - - createOneArrow(xPos) { - return ( - <svg width={this.boxSpace} height={this.boxHeight} x={xPos}> - <defs> - <marker viewBox="0 0 20 20" markerWidth="20" markerHeight="20" orient="auto" refX="8.5" refY="5" id="arrow"> - <path d="m 1 5 l 0 -3 l 7 3 l -7 3 z" - stroke-width= "1" stroke-linecap= "butt" stroke-dasharray= "10000, 1" - fill="#000000" stroke="#000000" /> - </marker> - </defs> - <line x1="0" y1="50%" x2="100%" y2="50%" stroke-width="2" color="black" stroke="black" marker-end="url(#arrow)"/> - </svg> - ); - } - - createBeginCircle(xPos, text) { - return ( - <svg width={this.boxWidth} height={this.boxHeight} x={xPos}> - <circle cx={this.boxWidth-30} cy="50%" r="30" stroke-width="1" color="black" stroke="black" fill="#27ae60"/> - <text x={this.boxWidth-30} y="50%" text-anchor="middle" dominant-baseline="middle" textLength="20%" lengthAdjust="spacingAndGlyphs" >{text}</text> - </svg> - ); + } + + handleSvgClick(event) { + console.debug("svg click event received"); + if (this.state.clickable) { + var elementName = event.target.parentNode.getAttribute('policyId'); + console.info("SVG element clicked", elementName); + // Only allow movement to policy editing IF there busyLoadingCOunt is 0, + // meaning we are not waiting for refreshStatus to complete, for example + if (elementName !== null && !this.props.isBusyLoading()) { + this.props.history.push("/policyModal/" + event.target.parentNode.getAttribute('policyType') + "/" + elementName); + } } - - createEndCircle(xPos, text) { - return ( - <svg width={this.boxWidth} height={this.boxHeight} x={xPos}> - <circle cx={30} cy="50%" r="30" stroke-width="2" color="black" stroke="black" fill="#27ae60"/> - <text x={30} y="50%" text-anchor="middle" dominant-baseline="middle" textLength="20%" lengthAdjust="spacingAndGlyphs" >{text}</text> - </svg> - ); + } + + createVesBox(xPos) { + return this.createOneBox(xPos, null, null, 'VES Collector', 'VES', null); + } + + createOneArrow(xPos) { + return ( + <svg width={ this.boxSpace } height={ this.boxHeight } x={ xPos }> + <defs> + <marker viewBox="0 0 20 20" markerWidth="20" markerHeight="20" orient="auto" refX="8.5" refY="5" id="arrow"> + <path d="m 1 5 l 0 -3 l 7 3 l -7 3 z" + stroke-width="1" stroke-linecap="butt" stroke-dasharray="10000, 1" + fill="#000000" stroke="#000000"/> + </marker> + </defs> + <line x1="0" y1="50%" x2="100%" y2="50%" stroke-width="2" color="black" stroke="black" marker-end="url(#arrow)"/> + </svg> + ); + } + + createBeginCircle(xPos, text) { + return ( + <svg width={ this.boxWidth } height={ this.boxHeight } x={ xPos }> + <circle cx={ this.boxWidth - 30 } cy="50%" r="30" stroke-width="1" color="black" stroke="black" fill="#27ae60"/> + <text x={ this.boxWidth - 30 } y="50%" text-anchor="middle" dominant-baseline="middle" textLength="20%" lengthAdjust="spacingAndGlyphs">{ text }</text> + </svg> + ); + } + + createEndCircle(xPos, text) { + return ( + <svg width={ this.boxWidth } height={ this.boxHeight } x={ xPos }> + <circle cx={ 30 } cy="50%" r="30" stroke-width="2" color="black" stroke="black" fill="#27ae60"/> + <text x={ 30 } y="50%" text-anchor="middle" dominant-baseline="middle" textLength="20%" lengthAdjust="spacingAndGlyphs">{ text }</text> + </svg> + ); + } + + createOneBox(xPos, policyId, loopElementModelId, name, title, policyType) { + return ( + <svg width={ this.boxWidth } height={ this.boxHeight } x={ xPos } title="test"> + <g policyId={ policyId } loopElementModelId={ loopElementModelId } policyType={ policyType }> + <rect width="100%" height="100%" stroke-width="2" color="black" stroke="black" fill="#1abc9c"/> + <text x="50%" y="15%" color="white" fill="white" dominant-baseline="middle" text-anchor="middle" textLength="50%" lengthAdjust="spacingAndGlyphs">{ title }</text> + <text x="50%" y="50%" text-anchor="middle" dominant-baseline="middle" textLength="80%" lengthAdjust="spacingAndGlyphs">{ name }</text> + <text x="50%" y="80%" text-anchor="middle" dominant-baseline="middle" textLength="110%" lengthAdjust="spacingAndGlyphs">{ policyId }</text> + </g> + </svg> + ); + } + + createSvgFromTemplate() { + const allElements = []; + var xPos = 0; + + allElements.push(this.createBeginCircle(xPos, "Start")) + xPos += (this.boxWidth + this.boxSpace); + + allElements.push(this.createOneArrow(xPos - this.boxSpace)); + + allElements.push(this.createVesBox(xPos)); + xPos += (this.boxWidth + this.boxSpace); + + allElements.push(this.createOneArrow(xPos - this.boxSpace)); + //createOneBox(xPos, policyId, loopElementModelId , name, title, policyType) + for (var loopElement of this.state.loopCache.getAllLoopElementModels()) { + + allElements.push(this.createOneBox(xPos, + loopElement['name'], + loopElement['name'], + loopElement['shortName'], + loopElement['loopElementType'], + loopElement['loopElementType'])) + xPos += (this.boxWidth + this.boxSpace); + allElements.push(this.createOneArrow(xPos - this.boxSpace)); } - createOneBox(xPos, policyId, loopElementModelId , name, title, policyType) { - return ( - <svg width={this.boxWidth} height={this.boxHeight} x={xPos} title="test"> - <g policyId={policyId} loopElementModelId={loopElementModelId} policyType={policyType}> - <rect width="100%" height="100%" stroke-width="2" color="black" stroke="black" fill="#1abc9c"/> - <text x="50%" y="15%" color="white" fill="white" dominant-baseline="middle" text-anchor="middle" textLength="50%" lengthAdjust="spacingAndGlyphs">{title}</text> - <text x="50%" y="50%" text-anchor="middle" dominant-baseline="middle" textLength="80%" lengthAdjust="spacingAndGlyphs" >{name}</text> - <text x="50%" y="80%" text-anchor="middle" dominant-baseline="middle" textLength="110%" lengthAdjust="spacingAndGlyphs" >{policyId}</text> - </g> - </svg> - ); - } + allElements.push(this.createEndCircle(xPos, "End")) + xPos += (this.boxWidth + this.boxSpace); - createSvgFromTemplate() { - const allElements = []; - var xPos = 0; + return allElements; + } - allElements.push(this.createBeginCircle(xPos,"Start")) - xPos+=(this.boxWidth+this.boxSpace); + createSvgFromInstance() { + const allElements = []; + var xPos = 0; - allElements.push(this.createOneArrow(xPos-this.boxSpace)); + allElements.push(this.createBeginCircle(xPos, "Start")) + xPos += (this.boxWidth + this.boxSpace); - allElements.push(this.createVesBox(xPos)); - xPos+=(this.boxWidth+this.boxSpace); + allElements.push(this.createOneArrow(xPos - this.boxSpace)); - allElements.push(this.createOneArrow(xPos-this.boxSpace)); - //createOneBox(xPos, policyId, loopElementModelId , name, title, policyType) - for (var loopElement of this.state.loopCache.getAllLoopElementModels()) { + allElements.push(this.createVesBox(xPos)); + xPos += (this.boxWidth + this.boxSpace); - allElements.push(this.createOneBox(xPos, - loopElement['name'], - loopElement['name'], - loopElement['shortName'], - loopElement['loopElementType'], - loopElement['loopElementType'])) - xPos+=(this.boxWidth+this.boxSpace); - allElements.push(this.createOneArrow(xPos-this.boxSpace)); - } + allElements.push(this.createOneArrow(xPos - this.boxSpace)); - allElements.push(this.createEndCircle(xPos, "End")) - xPos+=(this.boxWidth+this.boxSpace); - - return allElements; + for (var msPolicy in this.state.loopCache.getMicroServicePolicies()) { + var loopElementModelName = this.state.loopCache.getMicroServicePolicies()[msPolicy]['loopElementModel']; + if (loopElementModelName !== undefined) { + loopElementModelName = loopElementModelName['name']; + } + allElements.push(this.createOneBox(xPos, + this.state.loopCache.getMicroServicePolicies()[msPolicy]['name'], + loopElementModelName, + this.state.loopCache.getMicroServicePolicies()[msPolicy]['policyModel']['policyAcronym'], + 'microservice', + OnapConstant.microServiceType)) + xPos += (this.boxWidth + this.boxSpace); + allElements.push(this.createOneArrow(xPos - this.boxSpace)); } - createSvgFromInstance() { - const allElements = []; - var xPos = 0; - - allElements.push(this.createBeginCircle(xPos,"Start")) - xPos+=(this.boxWidth+this.boxSpace); - - allElements.push(this.createOneArrow(xPos-this.boxSpace)); - - allElements.push(this.createVesBox(xPos)); - xPos+=(this.boxWidth+this.boxSpace); - - allElements.push(this.createOneArrow(xPos-this.boxSpace)); - - for (var msPolicy in this.state.loopCache.getMicroServicePolicies()) { - var loopElementModelName = this.state.loopCache.getMicroServicePolicies()[msPolicy]['loopElementModel']; - if (loopElementModelName !== undefined) { - loopElementModelName = loopElementModelName['name']; - } - allElements.push(this.createOneBox(xPos, - this.state.loopCache.getMicroServicePolicies()[msPolicy]['name'], - loopElementModelName, - this.state.loopCache.getMicroServicePolicies()[msPolicy]['policyModel']['policyAcronym'], - 'microservice', - OnapConstant.microServiceType)) - xPos+=(this.boxWidth+this.boxSpace); - allElements.push(this.createOneArrow(xPos-this.boxSpace)); - } - - for (var opPolicy in this.state.loopCache.getOperationalPolicies()) { - loopElementModelName = this.state.loopCache.getOperationalPolicies()[opPolicy]['loopElementModel']; - if (loopElementModelName !== undefined) { - loopElementModelName = loopElementModelName['name']; - } - allElements.push(this.createOneBox(xPos, - this.state.loopCache.getOperationalPolicies()[opPolicy]['name'], - loopElementModelName, - this.state.loopCache.getOperationalPolicies()[opPolicy]['policyModel']['policyAcronym'], - 'operational', - OnapConstant.operationalPolicyType)) - xPos+=(this.boxWidth+this.boxSpace); - allElements.push(this.createOneArrow(xPos-this.boxSpace)); - } - - allElements.push(this.createEndCircle(xPos, "End")) - xPos+=(this.boxWidth+this.boxSpace); - - return allElements; + for (var opPolicy in this.state.loopCache.getOperationalPolicies()) { + loopElementModelName = this.state.loopCache.getOperationalPolicies()[opPolicy]['loopElementModel']; + if (loopElementModelName !== undefined) { + loopElementModelName = loopElementModelName['name']; + } + allElements.push(this.createOneBox(xPos, + this.state.loopCache.getOperationalPolicies()[opPolicy]['name'], + loopElementModelName, + this.state.loopCache.getOperationalPolicies()[opPolicy]['policyModel']['policyAcronym'], + 'operational', + OnapConstant.operationalPolicyType)) + xPos += (this.boxWidth + this.boxSpace); + allElements.push(this.createOneArrow(xPos - this.boxSpace)); } - renderSvg() { - if (this.state.loopCache.getLoopName() === undefined) { - return [emptySvg]; - } - if (this.state.generatedFrom === SvgGenerator.GENERATED_FROM_INSTANCE) { - return this.createSvgFromInstance(); - } else if (this.state.generatedFrom === SvgGenerator.GENERATED_FROM_TEMPLATE) { - return this.createSvgFromTemplate(); - } - } + allElements.push(this.createEndCircle(xPos, "End")) + xPos += (this.boxWidth + this.boxSpace); + + return allElements; + } - render() { - var allTheElements = this.renderSvg(); - var svgWidth = this.boxWidth*allTheElements.length; - var svgHeight = this.boxHeight+50; - return ( - - <DivStyled onClick={this.handleSvgClick} > - <svg height={svgHeight} width={svgWidth} viewBox="0,0,{svgWidth},{svgHeight}" preserveAspectRatio="none"> - <svg x="-50" y="25"> - {allTheElements} - </svg> - </svg> - </DivStyled> - ); + renderSvg() { + if (this.state.loopCache.getLoopName() === undefined) { + return [emptySvg]; } + if (this.state.generatedFrom === SvgGenerator.GENERATED_FROM_INSTANCE) { + return this.createSvgFromInstance(); + } else if (this.state.generatedFrom === SvgGenerator.GENERATED_FROM_TEMPLATE) { + return this.createSvgFromTemplate(); + } + } + + render() { + var allTheElements = this.renderSvg(); + var svgWidth = this.boxWidth * allTheElements.length; + var svgHeight = this.boxHeight + 50; + return ( + + <DivStyled onClick={ this.handleSvgClick }> + <svg key="main" height={ svgHeight } width={ svgWidth } viewBox="0,0,{svgWidth},{svgHeight}" preserveAspectRatio="none"> + <svg key="content" x="-50" y="25"> + { allTheElements } + </svg> + </svg> + </DivStyled> + ); + } } export default withRouter(SvgGenerator); diff --git a/gui-clamp/ui-react/src/setupTests.js b/gui-clamp/ui-react/src/setupTests.js index f553583..30ce019 100644 --- a/gui-clamp/ui-react/src/setupTests.js +++ b/gui-clamp/ui-react/src/setupTests.js @@ -22,7 +22,7 @@ */ import { configure } from 'enzyme'; -import Adapter from 'enzyme-adapter-react-17-updated'; +import Adapter from '@wojtekmaj/enzyme-adapter-react-17'; configure({ adapter: new Adapter() }); -global.fetch = require('jest-fetch-mock');
\ No newline at end of file +global.fetch = require('jest-fetch-mock'); |