2 * ============LICENSE_START=======================================================
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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 * ===================================================================
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';
53 const ModalStyled = styled(Modal)`
54 @media (min-width: 1200px) {
59 background-color: transparent;
62 const MTableToolbarStyled = styled(MTableToolbar)`
67 const ColPullLeftStyled = styled(Col)`
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'};
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('|');
90 SelectedDictTypes = SelectedDictTypes.slice(0,-1);
91 onChange(SelectedDictTypes);
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>
106 function SubDict(props) {
107 const {onChange} = props;
109 subDicts.push('Default');
110 if (dictList !== undefined && dictList.length > 0) {
112 for(item in dictList) {
113 if(dictList[item].secondLevelDictionary === 1) {
114 subDicts.push(dictList[item].name);
119 let optionItems = subDicts.map(
120 (item) => <option key={item}>{item}</option>
122 function selectedValue (e) {
123 onChange(e.target.value);
126 <select onChange={selectedValue} >
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);
152 currentSelectedDictionary: null,
155 dictionaryElements: [],
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} />)
177 title: "Dictionary Name", field: "name",editable: 'onAdd',
178 cellStyle: cellStyle,
179 headerStyle: headerStyle
182 title: "Sub Dictionary ?", field: "secondLevelDictionary", lookup: {0: 'No', 1: 'Yes'},
183 cellStyle: cellStyle,
184 headerStyle: headerStyle
187 title: "Dictionary Type", field: "subDictionaryType",lookup: {string: 'string', number: 'number'},
188 cellStyle: cellStyle,
189 headerStyle: headerStyle
192 title: "Updated By", field: "updatedBy", editable: 'never',
193 cellStyle: cellStyle,
194 headerStyle: headerStyle
197 title: "Last Updated Date", field: "updatedDate", editable: 'never',
198 cellStyle: cellStyle,
199 headerStyle: headerStyle
202 dictElementColumns: [
204 title: "Element Short Name", field: "shortName",editable: 'onAdd',
205 cellStyle: cellStyle,
206 headerStyle: headerStyle
209 title: "Element Name", field: "name",
210 cellStyle: cellStyle,
211 headerStyle: headerStyle
214 title: "Element Description", field: "description",
215 cellStyle: cellStyle,
216 headerStyle: headerStyle
219 title: "Element Type", field: "type",
220 editComponent: props => (
222 <SelectSubDictType value={props.value} onChange={props.onChange} />
225 cellStyle: cellStyle,
226 headerStyle: headerStyle
229 title: "Sub-Dictionary", field: "subDictionary",
230 editComponent: props => (
232 <SubDict value={props.value} onChange={props.onChange} />
235 cellStyle: cellStyle,
236 headerStyle: headerStyle
239 title: "Updated By", field: "updatedBy", editable: 'never',
240 cellStyle: cellStyle,
241 headerStyle: headerStyle
244 title: "Updated Date", field: "updatedDate", editable: 'never',
245 cellStyle: cellStyle,
246 headerStyle: headerStyle
252 componentDidMount() {
253 this.getDictionaries();
257 TemplateMenuService.getDictionary().then(arrayOfdictionaries => {
258 this.setState({ dictionaries: arrayOfdictionaries, currentSelectedDictionary: null })
262 getDictionaryElements(dictionaryName) {
263 TemplateMenuService.getDictionaryElements(dictionaryName).then(dictionaryElements => {
264 dictList = this.state.dictionaries;
265 this.setState({ dictionaryElements: dictionaryElements.dictionaryElements} );
269 clickHandler(rowData) {
270 this.getDictionaries();
274 this.setState({ show: false });
275 this.props.history.push('/');
278 addReplaceDictionaryRequest(dictionaryEntry) {
279 TemplateMenuService.insDictionary(dictionaryEntry)
281 .then(() => {this.getDictionaries()});
284 updateDictionaryElementsRequest(dictElements) {
285 let reqData = { "name": this.state.currentSelectedDictionary, 'dictionaryElements': dictElements };
286 TemplateMenuService.insDictionaryElements(reqData)
288 .then(() => { this.getDictionaryElements(this.state.currentSelectedDictionary) });
291 deleteDictionaryRequest(dictionaryName) {
292 TemplateMenuService.deleteDictionary(dictionaryName)
293 .then(resp => { this.getDictionaries() });
296 deleteDictionaryElementRequest(dictionaryName, elemenetShortName) {
297 TemplateMenuService.deleteDictionaryElements({ 'name': dictionaryName, 'shortName': elemenetShortName })
299 this.getDictionaryElements(dictionaryName);
303 fileSelectedHandler = (event) => {
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) => {
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'];
315 let result = CsvToJson(reader.result, ',', '||||', userHeaderNames, jsonKeyNames, mandatory);
317 let errorMessages = result.errorMessages;
318 let jsonObjArray = result.jsonObjArray;
320 let validTypesErrorMesg = '';
322 for (let i=0; i < validTypes.length; ++i) {
324 validTypesErrorMesg = validTypes[i];
326 validTypesErrorMesg += ',' + validTypes[i];
330 if (errorMessages !== '') {
331 alert(errorMessages);
335 // Perform further checks on data that is now in JSON form
336 let subDictionaries = [];
338 // NOTE: dictList is a global variable maintained faithfully
339 // by the getDictionaries() method outside this import
342 for (item in dictList) {
343 if (dictList[item].secondLevelDictionary === 1) {
344 subDictionaries.push(dictList[item].name);
348 // Check for valid Sub-Dictionary and Element Type values
349 subDictionaries = subDictionaries.toString();
352 for (dictElem of jsonObjArray) {
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] +
360 ' can only contain alphanumeric characters and periods, hyphens or underscores';
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;
366 if (value !== "" && itemKey === 'subDictionary' && subDictionaries.indexOf(value) < 0) {
367 errorMessages += '\nInvalid Sub-Dictionary value of "' + value + '" at row #' + row;
373 alert(errorMessages);
377 // We made it through all the checks. Send it to back end
378 this.updateDictionaryElementsRequest(jsonObjArray);
380 reader.readAsText(event.target.files[0]);
382 this.setState({selectedFile: event.target.files[0]})
384 alert('Please upload .csv extention files only.');
388 addDictionaryRow(newData) {
389 let validData = true;
390 return new Promise((resolve, reject) => {
392 if (/[^a-zA-Z0-9-_.]/.test(newData.name)) {
394 alert('Please enter alphanumeric input. Only allowed special characters are:(period, hyphen, underscore)');
397 for (let i = 0; i < this.state.dictionaries.length; i++) {
398 if (this.state.dictionaries[i].name === newData.name) {
400 alert(newData.name + ' dictionary name already exists')
405 this.addReplaceDictionaryRequest(newData);
413 updateDictionaryRow(oldData, newData) {
414 let validData = true;
415 return new Promise((resolve) => {
417 if (/[^a-zA-Z0-9-_.]/.test(newData.name)) {
419 alert('Please enter alphanumberic input. Only allowed special characters are:(period, hyphen, underscore)');
422 this.addReplaceDictionaryRequest(newData);
429 deleteDictionaryRow(oldData) {
430 return new Promise((resolve) => {
432 this.deleteDictionaryRequest(oldData.name);
438 addDictionaryElementRow(newData) {
439 return new Promise((resolve, reject) => {
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');
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';
452 if (!newData.shortName){
453 errorMessage += '\nShort Name must be specified';
456 errorMessage += '\nElement Name must be specified';
459 errorMessage += '\nElement Type must be specified';
461 if (!newData.description){
462 errorMessage += '\nElement Description must be specified';
464 if (errorMessage === '') {
465 dictionaryElements.push(newData);
466 this.updateDictionaryElementsRequest(dictionaryElements);
476 updateDictionaryElementRow(newData, oldData) {
477 return new Promise((resolve) => {
479 let dictionaryElements = this.state.dictionaryElements;
480 let validData = true;
483 alert('Element Type cannot be null');
486 const index = dictionaryElements.indexOf(oldData);
487 dictionaryElements[index] = newData;
488 this.updateDictionaryElementsRequest(dictionaryElements);
496 deleteDictionaryElementRow(oldData) {
497 return new Promise((resolve) => {
499 this.deleteDictionaryElementRequest(this.state.currentSelectedDictionary, oldData.shortName);
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>
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) => {
519 currentSelectedDictionary : rowData.name,
520 exportFilename: rowData.name
522 this.getDictionaryElements(rowData.name);
525 headerStyle: rowHeaderStyle,
528 onRowAdd: this.addDictionaryRow,
529 onRowUpdate: this.updateDictionaryRow,
530 onRowDelete: this.deleteDictionaryRow
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}
542 exportFileName: this.state.exportFilename,
543 headerStyle:{backgroundColor:'white', fontSize: '15pt', text: 'bold', border: '1px solid black'}
549 <MTableToolbarStyled {...props} />
551 <ColPullLeftStyled sm="1">
552 <Tooltip title="Import" placement = "bottom">
553 <IconButton aria-label="import" onClick={() => this.fileUpload.click()}>
554 <VerticalAlignTopIcon />
557 <input type="file" ref={(fileUpload) => {this.fileUpload = fileUpload;}}
558 style={{ visibility: 'hidden', width: '1px' }} onChange={this.fileSelectedHandler} />
564 onRowAdd: this.addDictionaryElementRow,
565 onRowUpdate: this.updateDictionaryElementRow,
566 onRowDelete: this.deleteDictionaryElementRow
570 {this.state.currentSelectedDictionary !== null ? <button onClick={this.clickHandler} style={{marginTop: '25px'}}>Go Back to Dictionaries List</button>:""}
573 <Button variant="secondary" type="null" onClick={this.handleClose}>Close</Button>