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