Fix bugs reported by sonar
[clamp.git] / ui-react / src / components / dialogs / ManageDictionaries / ManageDictionaries.js
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP CLAMP
4  * ================================================================================
5  * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END============================================
19  * ===================================================================
20  *
21  */
22
23
24 import React from 'react';
25 import Button from 'react-bootstrap/Button';
26 import Modal from 'react-bootstrap/Modal';
27 import styled from 'styled-components';
28 import TemplateMenuService from '../../../api/TemplateService';
29 import MaterialTable, {MTableToolbar} from "material-table";
30 import IconButton from '@material-ui/core/IconButton';
31 import Tooltip from '@material-ui/core/Tooltip';
32 import Grid from '@material-ui/core/Grid';
33 import { forwardRef }  from 'react';
34 import AddBox from '@material-ui/icons/AddBox';
35 import ArrowUpward from '@material-ui/icons/ArrowUpward';
36 import Check from '@material-ui/icons/Check';
37 import ChevronLeft from '@material-ui/icons/ChevronLeft';
38 import VerticalAlignTopIcon from '@material-ui/icons/VerticalAlignTop';
39 import VerticalAlignBottomIcon from '@material-ui/icons/VerticalAlignBottom';
40 import ChevronRight from '@material-ui/icons/ChevronRight';
41 import Clear from '@material-ui/icons/Clear';
42 import DeleteOutline from '@material-ui/icons/DeleteOutline';
43 import Edit from '@material-ui/icons/Edit';
44 import FilterList from '@material-ui/icons/FilterList';
45 import FirstPage from '@material-ui/icons/FirstPage';
46 import LastPage from '@material-ui/icons/LastPage';
47 import Remove from '@material-ui/icons/Remove';
48 import Search from '@material-ui/icons/Search';
49 import ViewColumn from '@material-ui/icons/ViewColumn';
50
51
52 const ModalStyled = styled(Modal)`
53         background-color: transparent;
54 `
55 const cellStyle = { border: '1px solid black' };
56 const headerStyle = { backgroundColor: '#ddd',  border: '2px solid black'       };
57 const rowHeaderStyle = {backgroundColor:'#ddd',  fontSize: '15pt', text: 'bold', border: '1px solid black'};
58 var dictList = [];
59
60 function SelectSubDictType(props) {
61         const {onChange} = props;
62         const selectedValues = (e) => {
63                 var options = e.target.options;
64                 var SelectedDictTypes = '';
65                 for (var dictType = 0, values = options.length; dictType < values; dictType++) {
66                         if (options[dictType].selected) {
67                                 SelectedDictTypes = SelectedDictTypes.concat(options[dictType].value);
68                                 SelectedDictTypes = SelectedDictTypes.concat('|');
69                         }
70                 }
71                 SelectedDictTypes = SelectedDictTypes.slice(0,-1);
72                 onChange(SelectedDictTypes);
73         }
74         return(
75                 <div>
76                         <select multiple={true}  onChange={selectedValues}>
77                                 <option value="string">string</option>
78                                 <option value="number">number</option>
79                                 <option value="datetime">datetime</option>
80                                 <option value="map">map</option>
81                                 <option value="json">json</option>
82                         </select>
83                 </div>
84         )
85 }
86
87 function SubDict(props) {
88         const {onChange} = props;
89         const subDicts = [];
90         subDicts.push('Default');
91         if (dictList != "undefined"  && dictList.length > 0) {
92         for(var item in dictList) {
93             if(dictList[item].secondLevelDictionary === 1) {
94                 subDicts.push(dictList[item].name);
95             }
96         };
97         }
98         subDicts.push('');
99         var optionItems = subDicts.map(
100                 (item) => <option key={item}>{item}</option>
101           );
102         function selectedValue (e) {
103                 onChange(e.target.value);
104         }
105         return(
106                 <select onChange={selectedValue} >
107                         {optionItems}
108                 </select>
109         )
110 }
111
112 export default class ManageDictionaries extends React.Component {
113         constructor(props, context) {
114                 super(props, context);
115                 this.handleClose = this.handleClose.bind(this);
116                 this.getDictionary = this.getDictionary.bind(this);
117                 this.getDictionaryElements = this.getDictionaryElements.bind(this);
118                 this.clickHandler = this.clickHandler.bind(this);
119                 this.addDictionary = this.addDictionary.bind(this);
120                 this.deleteDictionary = this.deleteDictionary.bind(this);
121                 this.fileSelectedHandler = this.fileSelectedHandler.bind(this);
122                 this.state = {
123                         show: true,
124                         selectedFile: '',
125                         dictNameFlag: false,
126                         exportFilename: '',
127                         content: null,
128                         newDict: '',
129                         newDictItem: '',
130                         delDictItem: '',
131                         addDict: false,
132                         delData: '',
133                         delDict: false,
134                         validImport: false,
135                         dictionaryNames: [],
136                         dictionaryElements: [],
137       tableIcons: {
138                 Add: forwardRef((props, ref) => <AddBox {...props} ref={ref} />),
139         Check: forwardRef((props, ref) => <Check {...props} ref={ref} />),
140         Clear: forwardRef((props, ref) => <Clear {...props} ref={ref} />),
141         Delete: forwardRef((props, ref) => <DeleteOutline {...props} ref={ref} />),
142         DetailPanel: forwardRef((props, ref) => <ChevronRight {...props} ref={ref} />),
143         Edit: forwardRef((props, ref) => <Edit {...props} ref={ref} />),
144         Export: forwardRef((props, ref) => <VerticalAlignBottomIcon {...props} ref={ref} />),
145         Filter: forwardRef((props, ref) => <FilterList {...props} ref={ref} />),
146         FirstPage: forwardRef((props, ref) => <FirstPage {...props} ref={ref} />),
147         LastPage: forwardRef((props, ref) => <LastPage {...props} ref={ref} />),
148         NextPage: forwardRef((props, ref) => <ChevronRight {...props} ref={ref} />),
149         PreviousPage: forwardRef((props, ref) => <ChevronLeft {...props} ref={ref} />),
150         ResetSearch: forwardRef((props, ref) => <Clear {...props} ref={ref} />),
151         Search: forwardRef((props, ref) => <Search {...props} ref={ref} />),
152         SortArrow: forwardRef((props, ref) => <ArrowUpward {...props} ref={ref} />),
153         ThirdStateCheck: forwardRef((props, ref) => <Remove {...props} ref={ref} />),
154         ViewColumn: forwardRef((props, ref) => <ViewColumn {...props} ref={ref} />)
155       },
156                         dictColumns: [
157                                 {
158                                         title: "Dictionary Name", field: "name",editable: 'onAdd',
159                                         cellStyle: cellStyle,
160                                         headerStyle: headerStyle
161                                 },
162                                 {
163                                         title: "Sub Dictionary ?", field: "secondLevelDictionary", lookup: {0: 'No', 1: 'Yes'},
164                                         cellStyle: cellStyle,
165                                         headerStyle: headerStyle
166                                 },
167                                 {
168                                         title: "Dictionary Type", field: "subDictionaryType",lookup: {string: 'string', number: 'number'},
169                                         cellStyle: cellStyle,
170                                         headerStyle: headerStyle
171                                 },
172                                 {
173                                         title: "Updated By", field: "updatedBy", editable: 'never',
174                                         cellStyle: cellStyle,
175                                         headerStyle: headerStyle
176                                 },
177                                 {
178                                         title: "Last Updated Date", field: "updatedDate", editable: 'never',
179                                         cellStyle: cellStyle,
180                                         headerStyle: headerStyle
181                                 }
182                         ],
183                         dictElementColumns: [
184                                 {
185                                         title: "Element Short Name", field: "shortName",editable: 'onAdd',
186                                         cellStyle: cellStyle,
187                                         headerStyle: headerStyle
188                                 },
189         {
190                                         title: "Element Name", field: "name",
191                                         cellStyle: cellStyle,
192                                         headerStyle: headerStyle
193                                 },
194                                 {
195                                         title: "Element Description", field: "description",
196                                         cellStyle: cellStyle,
197                                         headerStyle: headerStyle
198                                  },
199                                  {
200                                         title: "Element Type", field: "type",
201                                         editComponent: props => (
202                                                 <div>
203                                                         <SelectSubDictType  value={props.value} onChange={props.onChange} />
204                                                 </div>
205                                         ),
206                                         cellStyle: cellStyle,
207                                         headerStyle: headerStyle
208                                  },
209                                  {  
210                                     title: "Sub-Dictionary", field: "subDictionary",
211                                       editComponent: props => (
212                                                  <div>
213                                                          <SubDict  value={props.value} onChange={props.onChange} />
214                                                  </div>
215                                       ),
216                                     cellStyle: cellStyle,
217                                     headerStyle: headerStyle
218                                  },
219                                 {     
220                                         title: "Updated By", field: "updatedBy", editable: 'never',
221                                         cellStyle: cellStyle,
222                                         headerStyle: headerStyle
223                                 },
224                                 {
225                                         title: "Updated Date", field: "updatedDate", editable: 'never',
226                                         cellStyle: cellStyle,
227                                         headerStyle: headerStyle
228                                 }
229                         ]
230                 }
231         }
232
233         componentWillMount() {
234         this.getDictionary();
235     }
236
237     getDictionary() {
238         TemplateMenuService.getDictionary().then(dictionaryNames => {
239             this.setState({ dictionaryNames: dictionaryNames })
240         });
241     }
242
243     getDictionaryElements(dictionaryName) {
244         TemplateMenuService.getDictionaryElements(dictionaryName).then(dictionaryElements => {
245             dictList = this.state.dictionaryNames;
246             this.setState({ dictionaryElements: dictionaryElements.dictionaryElements});
247         });
248     }
249
250     clickHandler(rowData)   {
251         this.setState({
252             dictNameFlag: false,
253             addDict: false,
254     });
255     }
256
257     handleClose() {
258         this.setState({ show: false });
259         this.props.history.push('/');
260     }
261
262     addDictionary() {
263         var modifiedData = [];
264         if(this.state.newDict !== '') {
265             modifiedData = this.state.newDict;
266         } else {
267             modifiedData = {"name": this.state.dictionaryName, 'dictionaryElements': this.state.newDictItem};
268         }
269         if(this.state.newDictItem === '') {
270             TemplateMenuService.insDictionary(modifiedData).then(resp => {
271             });
272         } else {
273             TemplateMenuService.insDictionaryElements(modifiedData).then(resp => {
274             });
275         }
276     }
277
278     deleteDictionary() {
279         var modifiedData = [];
280         if(this.state.delData !== '') {
281             modifiedData = this.state.delData.name;
282         } else {
283             modifiedData = {"name": this.state.dictionaryName, "shortName": this.state.delDictItem.shortName};
284         }
285         if(this.state.delDictItem === '') {
286             TemplateMenuService.deleteDictionary(modifiedData).then(resp => {
287             });
288         } else {
289             TemplateMenuService.deleteDictionaryElements(modifiedData).then(resp => {
290             });
291         }
292     }
293
294     fileSelectedHandler = (event) => {
295         const text = this;
296         var dictionaryElements = [];
297         if (event.target.files[0].type === 'text/csv' ) {
298             if (event.target.files && event.target.files[0]) {
299                 let reader = new FileReader();
300                 reader.onload = function(e) {
301                     var dictElems = reader.result.split('\n');
302                     var jsonObj = [];
303                     var headers = dictElems[0].split(',');
304                     for(var i = 0; i < dictElems.length; i++) {
305                         var data = dictElems[i].split(',');
306                         var obj = {};
307                         for(var j = 0; j < data.length; j++) {
308                             obj[headers[j].trim()] = data[j].trim();
309                         }
310                         jsonObj.push(obj);
311                     }
312                     JSON.stringify(jsonObj);
313                     const dictKeys = ['Element Short Name','Element Name','Element Description','Element Type','Sub-Dictionary'];
314                     const mandatoryKeys = [ 'Element Short Name', 'Element Name', 'Element Type' ];
315                     const validTypes = ['string','number','datetime','json','map'];
316                     if (!dictElems){
317                         
318                         text.setState({validData: false});
319                     } else if (headers.length !== dictKeys.length){
320                         text.setState({validImport: false});
321                     } else {
322                         var subDictionaries = [];
323                         for(var item in dictList) {
324                             if(dictList[item].secondLevelDictionary === 1) {
325                                 subDictionaries.push(dictList[item].name);
326                             }
327                         };
328                         subDictionaries = subDictionaries.toString();
329                         var row = 0;
330                         for (var dictElem of jsonObj){
331                             ++row;
332                             for (var itemKey in dictElem){
333                                 var value = dictElem[itemKey].trim();
334                                 if (dictKeys.indexOf(itemKey) < 0){
335                                     var errorMessage = 'unknown field name of, ' + itemKey + ', found in CSV header';
336                                     text.setState({validImport: false});
337                                     alert(errorMessage);
338                                     break;
339                                 } else if (value === "" && mandatoryKeys.indexOf(itemKey) >= 0){
340                                     errorMessage = 'value for ' + itemKey + ', at row #, ' + row + ', is empty but required';
341                                     text.setState({validImport: false});
342                                     alert(errorMessage);
343                                     break;
344                                 } else if (itemKey === 'Element Type' && validTypes.indexOf(value) < 0 && row > 1) {
345                                     errorMessage = 'invalid dictElemenType of ' + value + ' at row #' + row;
346                                     text.setState({validImport: false});
347                                     alert(errorMessage);
348                                     break;
349                                 } else if (value !== "" && itemKey === 'Sub-Dictionary' && subDictionaries.indexOf(value) < 0 && row > 1) {
350                                     errorMessage = 'invalid subDictionary of ' + value + ' at row #' + row;
351                                     text.setState({validImport: false});
352                                     alert(errorMessage);
353                                 }
354                             }
355                         }
356                     }
357                     const headerKeys = ['shortName','name','description','type','subDictionary'];
358
359                     for(i = 1; i < dictElems.length; i++) {
360                         data = dictElems[i].split(',');
361                         obj = {};
362                         for(j = 0; j < data.length; j++) {
363                             obj[headerKeys[j].trim()] = data[j].trim();
364                         }
365                         dictionaryElements.push(obj);
366                     }
367                     text.setState({newDictItem: dictionaryElements, addDict: true});
368                 }
369                 reader.readAsText(event.target.files[0]);
370             }
371             this.setState({selectedFile: event.target.files[0]})
372         } else {
373             text.setState({validImport: false});
374             alert('Please upload .csv extention files only.');
375         }
376
377     }
378     
379     render() {
380         return (
381             <ModalStyled size="xl" show={this.state.show} onHide={this.handleClose} backdrop="static" keyboard={false} >
382                 <Modal.Header closeButton>
383                     <Modal.Title>Manage Dictionaries</Modal.Title>
384                 </Modal.Header>
385                 <Modal.Body>
386                     {!this.state.dictNameFlag? <MaterialTable
387                         title={"Dictionary List"}
388                         data={this.state.dictionaryNames}
389                         columns={this.state.dictColumns}
390                         icons={this.state.tableIcons}
391                         onRowClick={(event, rowData) => {this.getDictionaryElements(rowData.name);this.setState({dictNameFlag: true, exportFilename: rowData.name, dictionaryName: rowData.name})}}
392                         options={{
393                             headerStyle: rowHeaderStyle,
394                         }}
395                         editable={{
396                             onRowAdd: newData =>
397                             new Promise((resolve, reject) => {
398                                 setTimeout(() => {
399                                     {
400                                         const dictionaryNames = this.state.dictionaryNames;
401                                         var validData =  true;
402                                         if(/[^a-zA-Z0-9-_.]/.test(newData.name)) {
403                                             validData = false;
404                                             alert('Please enter alphanumberic input. Only allowed special characters are:(period, hyphen, underscore)');
405                                         }
406                                         for (var i = 0; i < this.state.dictionaryNames.length; i++) {
407                                             if (this.state.dictionaryNames[i].name === newData.name) {
408                                                 validData = false;
409                                                 alert(newData.name + ' dictionary name already exists')
410                                             }
411                                         }
412                                         if(validData){
413                                             dictionaryNames.push(newData);
414                                             this.setState({ dictionaryNames }, () => resolve());
415                                             this.setState({addDict: true, newDict: newData});
416                                         }
417                                     }
418                                     resolve();
419                                 }, 1000);
420                             }),
421                             onRowUpdate: (newData, oldData) =>
422                             new Promise((resolve, reject) => {
423                                 setTimeout(() => {
424                                     {
425                                         const dictionaryNames = this.state.dictionaryNames;
426                                         var validData =  true;
427                                         if(/[^a-zA-Z0-9-_.]/.test(newData.name)) {
428                                             validData = false;
429                                             alert('Please enter alphanumberic input. Only allowed special characters are:(period, hyphen, underscore)');
430                                         }
431                                         if(validData){
432                                             const index = dictionaryNames.indexOf(oldData);
433                                             dictionaryNames[index] = newData;
434                                             this.setState({ dictionaryNames }, () => resolve());
435                                             this.setState({addDict: true, newDict: newData});
436                                         }
437                                     }
438                                     resolve();
439                                 }, 1000);
440                             }),
441                             onRowDelete: oldData =>
442                 new Promise((resolve, reject) => {
443                                 setTimeout(() => {
444                                     {
445                                         let data = this.state.dictionaryNames;
446                     const index = data.indexOf(oldData);
447                     data.splice(index, 1);
448                     this.setState({ data }, () => resolve());
449                                         this.setState({delDict: true, delData: oldData})
450                     }
451                     resolve()
452                 }, 1000)
453                 })
454                         }}
455                         />:""
456                     }
457                     {this.state.dictNameFlag? <MaterialTable
458                         title={"Dictionary Elements List"}
459                         data={this.state.dictionaryElements}
460                         columns={this.state.dictElementColumns}
461                         icons={this.state.tableIcons}
462                         options={{
463                             exportButton: true,
464                             exportFileName: this.state.exportFilename,
465                             headerStyle:{backgroundColor:'white',  fontSize: '15pt', text: 'bold', border: '1px solid black'}
466                         }}
467                         components={{
468                             Toolbar: props => (
469                                 <div>
470                                     <MTableToolbar {...props} />
471                                 <div>
472                                     <Grid item container xs={12} alignItems="flex-end" direction="column" justify="flex-end">
473                                         <Tooltip title="Import" placement = "bottom">
474                                             <IconButton aria-label="import" onClick={() => this.fileUpload.click()}>
475                                                 <VerticalAlignTopIcon />
476                                             </IconButton>
477                                         </Tooltip>
478                                     </Grid>
479                                 </div>
480                                 <input type="file" ref={(fileUpload) => {this.fileUpload = fileUpload;}} style={{ visibility: 'hidden'}} onChange={this.fileSelectedHandler} />
481                                 </div>
482                             )
483                         }}
484                         editable={{
485                             onRowAdd: newData =>
486                             new Promise((resolve, reject) => {
487                                 setTimeout(() => {
488                                     {
489                                         const dictionaryElements = this.state.dictionaryElements;
490                                         var validData =  true;
491                                         for (var i = 0; i < this.state.dictionaryElements.length; i++) {
492                                             if (this.state.dictionaryElements[i].shortName === newData.shortName) {
493                                                 validData = false;
494                                                 alert(newData.shortname + 'short name already exists')
495                                             }
496                                         }
497                                         if(/[^a-zA-Z0-9-_.]/.test(newData.shortName)) {
498                                             validData = false;
499                                             alert('Please enter alphanumberic input. Only allowed special characters are:(period, hyphen, underscore)');
500                                         }
501                                         if(!newData.type){
502                                             validData = false;
503                                             alert('Element Type cannot be null');
504                                         }
505                                         if(validData){
506                                             dictionaryElements.push(newData);
507                                             this.setState({ dictionaryElements }, () => resolve());
508                                             this.setState({addDict: true, newDictItem: [newData]});
509                                         }
510                                     }
511                                     resolve();
512                                 }, 1000);
513                             }),
514                             onRowUpdate: (newData, oldData) =>
515                             new Promise((resolve, reject) => {
516                                 setTimeout(() => {
517                                     {
518                                         const dictionaryElements = this.state.dictionaryElements;
519                                         var validData =  true;
520                                         if(!newData.type){
521                                             validData = false;
522                                             alert('Element Type cannot be null');
523                                         }
524                                         if(validData){
525                                             const index = dictionaryElements.indexOf(oldData);
526                                             dictionaryElements[index] = newData;
527                                             this.setState({ dictionaryElements }, () => resolve());
528                                             this.setState({addDict: true, newDictItem: [newData]});
529                                         }
530                                     }
531                                     resolve();
532                                 }, 1000);
533                             }),
534                             onRowDelete: oldData =>
535                 new Promise((resolve, reject) => {
536                                 setTimeout(() => {
537                                     {
538                                         let data = this.state.dictionaryElements;
539                     const index = data.indexOf(oldData);
540                     data.splice(index, 1);
541                     this.setState({ data }, () => resolve());
542                                         this.setState({delDict: true, delDictItem: oldData})
543                     }
544                     resolve()
545                 }, 1000)
546                 })
547                         }}
548                         />:""
549                     }
550                     {this.state.dictNameFlag?<button onClick={this.clickHandler} style={{marginTop: '25px'}}>Go Back to Dictionaries List</button>:""}
551                     {this.state.addDict && this.addDictionary()}
552                     {this.state.delDict && this.deleteDictionary()}
553                 </Modal.Body>
554                 <Modal.Footer>
555                     <Button variant="secondary" type="null" onClick={this.handleClose}>Close</Button>
556                 </Modal.Footer>
557             </ModalStyled>
558         );
559     }
560 }