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