Add a customValidation method to PolicyModal
[clamp.git] / ui-react / src / components / dialogs / Policy / PolicyModal.js
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP CLAMP
4  * ================================================================================
5  * Copyright (C) 2020 AT&T Intellectual Property. All rights
6  *                             reserved.
7  * ================================================================================
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  * ============LICENSE_END============================================
20  * ===================================================================
21  *
22  */
23
24 import React from 'react'
25 import Button from 'react-bootstrap/Button';
26 import Form from 'react-bootstrap/Form';
27 import Col from 'react-bootstrap/Col';
28 import Row from 'react-bootstrap/Row';
29 import Select from 'react-select';
30 import Modal from 'react-bootstrap/Modal';
31 import styled from 'styled-components';
32 import LoopService from '../../../api/LoopService';
33 import LoopCache from '../../../api/LoopCache';
34 import JSONEditor from '@json-editor/json-editor';
35 import Alert from 'react-bootstrap/Alert';
36 import OnapConstant from '../../../utils/OnapConstants';
37 import OnapUtils from '../../../utils/OnapUtils';
38
39 const ModalStyled = styled(Modal)`
40         background-color: transparent;
41 `
42
43 const DivWhiteSpaceStyled = styled.div`
44         white-space: pre;
45 `
46
47 export default class PolicyModal extends React.Component {
48
49         state = {
50                 show: true,
51                 loopCache: this.props.loopCache,
52                 jsonEditor: null,
53                 policyName: this.props.match.params.policyName,
54                 // This is to indicate whether it's an operational or config policy (in terms of loop instance)
55                 policyInstanceType: this.props.match.params.policyInstanceType,
56                 pdpGroup: null,
57                 pdpGroupList: [],
58                 pdpSubgroupList: [],
59                 chosenPdpGroup: '',
60                 chosenPdpSubgroup: '',
61                 showSucAlert: false,
62                 showFailAlert: false
63         };
64
65         constructor(props, context) {
66                 super(props, context);
67                 this.handleClose = this.handleClose.bind(this);
68                 this.handleSave = this.handleSave.bind(this);
69                 this.renderJsonEditor = this.renderJsonEditor.bind(this);
70                 this.handlePdpGroupChange = this.handlePdpGroupChange.bind(this);
71                 this.handlePdpSubgroupChange = this.handlePdpSubgroupChange.bind(this);
72                 this.createJsonEditor = this.createJsonEditor.bind(this);
73                 this.handleRefresh = this.handleRefresh.bind(this);
74                 this.disableAlert =  this.disableAlert.bind(this);
75                 this.renderPdpGroupDropDown = this.renderPdpGroupDropDown.bind(this);
76                 this.renderOpenLoopMessage = this.renderOpenLoopMessage.bind(this);
77                 this.renderModalTitle = this.renderModalTitle.bind(this);
78                 this.readOnly = props.readOnly !== undefined ? props.readOnly : false;
79         }
80
81         handleSave() {
82                 var editorData = this.state.jsonEditor.getValue();
83                 var errors = this.state.jsonEditor.validate();
84                 errors = errors.concat(this.customValidation(editorData, this.state.loopCache.getTemplateName()));
85
86                 if (errors.length !== 0) {
87                         console.error("Errors detected during policy data validation ", errors);
88                         this.setState({
89                                 showFailAlert: true,
90                                 showMessage: 'Errors detected during policy data validation:\n' + OnapUtils.jsonEditorErrorFormatter(errors)
91                         });
92                         return;
93                 }
94                 else {
95                         console.info("NO validation errors found in policy data");
96                         if (this.state.policyInstanceType === OnapConstant.microServiceType) {
97                                 this.state.loopCache.updateMicroServiceProperties(this.state.policyName, editorData);
98                                 this.state.loopCache.updateMicroServicePdpGroup(this.state.policyName, this.state.chosenPdpGroup, this.state.chosenPdpSubgroup);
99                                 LoopService.setMicroServiceProperties(this.state.loopCache.getLoopName(), this.state.loopCache.getMicroServiceForName(this.state.policyName)).then(resp => {
100                                         this.setState({ show: false });
101                                         this.props.history.push('/');
102                                         this.props.loadLoopFunction(this.state.loopCache.getLoopName());
103                                 });
104                         } else if (this.state.policyInstanceType === OnapConstant.operationalPolicyType) {
105                                 this.state.loopCache.updateOperationalPolicyProperties(this.state.policyName, editorData);
106                                 this.state.loopCache.updateOperationalPolicyPdpGroup(this.state.policyName, this.state.chosenPdpGroup, this.state.chosenPdpSubgroup);
107                                 LoopService.setOperationalPolicyProperties(this.state.loopCache.getLoopName(), this.state.loopCache.getOperationalPolicies()).then(resp => {
108                                         this.setState({ show: false });
109                                         this.props.history.push('/');
110                                         this.props.loadLoopFunction(this.state.loopCache.getLoopName());
111                                 });
112                         }
113                 }
114         }
115
116         customValidation(editorData, templateName) {
117                 // method for sub-classes to override with customized validation
118                 return [];
119         }
120
121         handleClose() {
122                 this.setState({ show: false });
123                 this.props.history.push('/');
124         }
125
126         componentDidMount() {
127                 this.renderJsonEditor();
128         }
129
130         componentDidUpdate() {
131                 if (this.state.showSucAlert === true || this.state.showFailAlert === true) {
132                         let modalElement = document.getElementById("policyModal")
133                         if (modalElement) {
134                                 modalElement.scrollTo(0, 0);
135                         }
136                 }
137         }
138
139     createJsonEditor(toscaModel, editorData) {
140         JSONEditor.defaults.themes.myBootstrap4 = JSONEditor.defaults.themes.bootstrap4.extend({
141                         getTab: function(text,tabId) {
142                                 var liel = document.createElement('li');
143                                 liel.classList.add('nav-item');
144                                 var ael = document.createElement("a");
145                                 ael.classList.add("nav-link");
146                                 ael.setAttribute("style",'padding:10px;max-width:160px;');
147                                 ael.setAttribute("href", "#" + tabId);
148                                 ael.setAttribute('data-toggle', 'tab');
149                                 text.setAttribute("style",'word-wrap:break-word;');
150                                 ael.appendChild(text);
151                                 liel.appendChild(ael);
152                                 return liel;
153                         }
154                 });
155         return new JSONEditor(document.getElementById("editor"),
156         {   schema: toscaModel,
157               startval: editorData,
158               theme: 'myBootstrap4',
159               object_layout: 'grid',
160               disable_properties: false,
161               disable_edit_json: false,
162               disable_array_reorder: true,
163               disable_array_delete_last_row: true,
164               disable_array_delete_all_rows: false,
165               array_controls_top: true,
166               keep_oneof_values: false,
167               collapsed:true,
168               show_errors: 'always',
169               display_required_only: false,
170               show_opt_in: false,
171               prompt_before_delete: true,
172               required_by_default: false
173         })
174     }
175
176         renderJsonEditor() {
177                 console.debug("Rendering PolicyModal ", this.state.policyName);
178                 var toscaModel = {};
179                 var editorData = {};
180                 var pdpGroupValues = {};
181                 var chosenPdpGroupValue, chosenPdpSubgroupValue;
182                 if (this.state.policyInstanceType === OnapConstant.microServiceType) {
183                         toscaModel = this.state.loopCache.getMicroServiceJsonRepresentationForName(this.state.policyName);
184                         editorData = this.state.loopCache.getMicroServicePropertiesForName(this.state.policyName);
185                         pdpGroupValues = this.state.loopCache.getMicroServiceSupportedPdpGroup(this.state.policyName);
186                         chosenPdpGroupValue = this.state.loopCache.getMicroServicePdpGroup(this.state.policyName);
187                         chosenPdpSubgroupValue = this.state.loopCache.getMicroServicePdpSubgroup(this.state.policyName);
188                 } else if (this.state.policyInstanceType === OnapConstant.operationalPolicyType) {
189                         toscaModel = this.state.loopCache.getOperationalPolicyJsonRepresentationForName(this.state.policyName);
190                         editorData = this.state.loopCache.getOperationalPolicyPropertiesForName(this.state.policyName);
191                         pdpGroupValues = this.state.loopCache.getOperationalPolicySupportedPdpGroup(this.state.policyName);
192                         chosenPdpGroupValue = this.state.loopCache.getOperationalPolicyPdpGroup(this.state.policyName);
193                         chosenPdpSubgroupValue = this.state.loopCache.getOperationalPolicyPdpSubgroup(this.state.policyName);
194                 }
195
196                 if (toscaModel == null) {
197                         return;
198                 }
199
200         var pdpSubgroupValues = [];
201                 if (typeof(chosenPdpGroupValue) !== "undefined") {
202                         var selectedPdpGroup =  pdpGroupValues.filter(entry => (Object.keys(entry)[0] === chosenPdpGroupValue));
203                         pdpSubgroupValues = selectedPdpGroup[0][chosenPdpGroupValue].map((pdpSubgroup) => { return { label: pdpSubgroup, value: pdpSubgroup } });
204                 }
205                 this.setState({
206                                         jsonEditor: this.createJsonEditor(toscaModel,editorData),
207                                         pdpGroup: pdpGroupValues,
208                                         pdpGroupList: pdpGroupValues.map(entry => {
209                                                                 return { label: Object.keys(entry)[0], value: Object.keys(entry)[0] };
210                                                 }),
211                                         pdpSubgroupList: pdpSubgroupValues,
212                                         chosenPdpGroup: chosenPdpGroupValue,
213                                         chosenPdpSubgroup: chosenPdpSubgroupValue
214                                 })
215         }
216
217         handlePdpGroupChange(e) {
218                 var selectedPdpGroup =  this.state.pdpGroup.filter(entry => (Object.keys(entry)[0] === e.value));
219                 const pdpSubgroupValues = selectedPdpGroup[0][e.value].map((pdpSubgroup) => { return { label: pdpSubgroup, value: pdpSubgroup } });
220                 if (this.state.chosenPdpGroup !== e.value) {
221                         this.setState({
222                                 chosenPdpGroup: e.value,
223                                 chosenPdpSubgroup: '',
224                                 pdpSubgroupList: pdpSubgroupValues
225                         });
226                 }
227         }
228
229         handlePdpSubgroupChange(e) {
230                 this.setState({ chosenPdpSubgroup: e.value });
231         }
232
233         handleRefresh() {
234                 var newLoopCache, toscaModel, editorData;
235                 if (this.state.policyInstanceType === OnapConstant.microServiceType) {
236                         LoopService.refreshMicroServicePolicyJson(this.state.loopCache.getLoopName(),this.state.policyName).then(data => {
237                                 newLoopCache =  new LoopCache(data);
238                                 toscaModel = newLoopCache.getMicroServiceJsonRepresentationForName(this.state.policyName);
239                                 editorData = newLoopCache.getMicroServicePropertiesForName(this.state.policyName);
240                                 document.getElementById("editor").innerHTML = "";
241                                 this.setState({
242                                         loopCache: newLoopCache,
243                                         jsonEditor: this.createJsonEditor(toscaModel,editorData),
244                                         showSucAlert: true,
245                                         showMessage: "Successfully refreshed"
246                                 });
247                         })
248                         .catch(error => {
249                                 console.error("Error while refreshing the Operational Policy Json Representation");
250                                 this.setState({
251                                         showFailAlert: true,
252                                         showMessage: "Refreshing of UI failed"
253                                 });
254                         });
255                 } else if (this.state.policyInstanceType === OnapConstant.operationalPolicyType) {
256                         LoopService.refreshOperationalPolicyJson(this.state.loopCache.getLoopName(),this.state.policyName).then(data => {
257                                 var newLoopCache =  new LoopCache(data);
258                                 toscaModel = newLoopCache.getOperationalPolicyJsonRepresentationForName(this.state.policyName);
259                                 editorData = newLoopCache.getOperationalPolicyPropertiesForName(this.state.policyName);
260                                 document.getElementById("editor").innerHTML = "";
261                                 this.setState({
262                                         loopCache: newLoopCache,
263                                         jsonEditor: this.createJsonEditor(toscaModel,editorData),
264                                         showSucAlert: true,
265                                         showMessage: "Successfully refreshed"
266                                 });
267                         })
268                         .catch(error => {
269                                 console.error("Error while refreshing the Operational Policy Json Representation");
270                                 this.setState({
271                                         showFailAlert: true,
272                                         showMessage: "Refreshing of UI failed"
273                                 });
274                         });
275                 }
276         }
277
278         disableAlert() {
279                 this.setState ({ showSucAlert: false, showFailAlert: false });
280         }
281
282         renderPdpGroupDropDown() {
283                 if(this.state.policyInstanceType !== OnapConstant.operationalPolicyType || !this.state.loopCache.isOpenLoopTemplate()) {
284                         return (
285                                 <Form.Group as={Row} controlId="formPlaintextEmail">
286                                         <Form.Label column sm="2">Pdp Group Info</Form.Label>
287                                         <Col sm="3">
288                                                 <Select value={{ label: this.state.chosenPdpGroup, value: this.state.chosenPdpGroup }} onChange={this.handlePdpGroupChange} options={this.state.pdpGroupList} />
289                                         </Col>
290                                         <Col sm="3">
291                                                 <Select value={{ label: this.state.chosenPdpSubgroup, value: this.state.chosenPdpSubgroup }} onChange={this.handlePdpSubgroupChange} options={this.state.pdpSubgroupList} />
292                                         </Col>
293                                 </Form.Group>
294                         );
295                 }
296         }
297
298     renderOpenLoopMessage() {
299         if(this.state.policyInstanceType === OnapConstant.operationalPolicyType && this.state.loopCache.isOpenLoopTemplate()) {
300                 return (
301                                 "Operational Policy cannot be configured as only Open Loop is supported for this Template!"
302                         );
303                 }
304     }
305
306         renderModalTitle() {
307                 return (
308                                 <Modal.Title>Edit the policy</Modal.Title>
309                 );
310         }
311
312         renderButton() {
313             var allElement = [(<Button variant="secondary" onClick={this.handleClose}>
314                                                                 Close
315                          </Button>)];
316                 if(this.state.policyInstanceType !== OnapConstant.operationalPolicyType || !this.state.loopCache.isOpenLoopTemplate()) {
317              allElement.push((
318                 <Button variant="primary" disabled={this.readOnly} onClick={this.handleSave}>
319                                 Save Changes
320                 </Button>
321              ));
322              allElement.push((
323                 <Button variant="primary" disabled={this.readOnly} onClick={this.handleRefresh}>
324                                 Refresh
325                 </Button>
326              ));
327                 }
328                 return allElement;
329         }
330
331         render() {
332                 return (
333                         <ModalStyled size="xl" backdrop="static" keyboard={false} show={this.state.show} onHide={this.handleClose}>
334                                 <Modal.Header closeButton>
335                                         {this.renderModalTitle()}
336                                 </Modal.Header>
337                                 <Alert variant="success" show={this.state.showSucAlert} onClose={this.disableAlert} dismissible>
338                                         <DivWhiteSpaceStyled>
339                                                 {this.state.showMessage}
340                                         </DivWhiteSpaceStyled>
341                                 </Alert>
342                                 <Alert variant="danger" show={this.state.showFailAlert} onClose={this.disableAlert} dismissible>
343                                         <DivWhiteSpaceStyled>
344                                                 {this.state.showMessage}
345                                         </DivWhiteSpaceStyled>
346                                 </Alert>
347                                 <Modal.Body>
348                                         {this.renderOpenLoopMessage()}
349                                         <div id="editor" />
350                                         {this.renderPdpGroupDropDown()}
351                                 </Modal.Body>
352                                 <Modal.Footer>
353                                         {this.renderButton()}
354                                 </Modal.Footer>
355                         </ModalStyled>
356                 );
357         }
358 }