/*- * ============LICENSE_START======================================================= * ONAP CLAMP * ================================================================================ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ============LICENSE_END============================================ * =================================================================== * */ import React from 'react'; import Button from 'react-bootstrap/Button'; import Modal from 'react-bootstrap/Modal'; import styled from 'styled-components'; import TemplateMenuService from '../../../api/TemplateService'; import MaterialTable, {MTableToolbar} from "material-table"; import IconButton from '@material-ui/core/IconButton'; import Tooltip from '@material-ui/core/Tooltip'; import Grid from '@material-ui/core/Grid'; import { forwardRef } from 'react'; import AddBox from '@material-ui/icons/AddBox'; import ArrowUpward from '@material-ui/icons/ArrowUpward'; import Check from '@material-ui/icons/Check'; import ChevronLeft from '@material-ui/icons/ChevronLeft'; import VerticalAlignTopIcon from '@material-ui/icons/VerticalAlignTop'; import VerticalAlignBottomIcon from '@material-ui/icons/VerticalAlignBottom'; import ChevronRight from '@material-ui/icons/ChevronRight'; import Clear from '@material-ui/icons/Clear'; import DeleteOutline from '@material-ui/icons/DeleteOutline'; import Edit from '@material-ui/icons/Edit'; import FilterList from '@material-ui/icons/FilterList'; import FirstPage from '@material-ui/icons/FirstPage'; import LastPage from '@material-ui/icons/LastPage'; import Remove from '@material-ui/icons/Remove'; import Search from '@material-ui/icons/Search'; import ViewColumn from '@material-ui/icons/ViewColumn'; const ModalStyled = styled(Modal)` background-color: transparent; ` const cellStyle = { border: '1px solid black' }; const headerStyle = { backgroundColor: '#ddd', border: '2px solid black' }; const rowHeaderStyle = {backgroundColor:'#ddd', fontSize: '15pt', text: 'bold', border: '1px solid black'}; var dictList = []; function SelectSubDictType(props) { const {onChange} = props; const selectedValues = (e) => { var options = e.target.options; var SelectedDictTypes = ''; for (var dictType = 0, values = options.length; dictType < values; dictType++) { if (options[dictType].selected) { SelectedDictTypes = SelectedDictTypes.concat(options[dictType].value); SelectedDictTypes = SelectedDictTypes.concat('|'); } } SelectedDictTypes = SelectedDictTypes.slice(0,-1); onChange(SelectedDictTypes); } return( <div> <select multiple={true} onChange={selectedValues}> <option value="string">string</option> <option value="number">number</option> <option value="datetime">datetime</option> <option value="map">map</option> <option value="json">json</option> </select> </div> ) } function SubDict(props) { const {onChange} = props; const subDicts = []; subDicts.push('Default'); for(var item in dictList) { if(dictList[item].secondLevelDictionary === 1) { subDicts.push(dictList[item].name); } }; subDicts.push(''); var optionItems = subDicts.map( (item) => <option key={item}>{item}</option> ); function selectedValue (e) { onChange(e.target.value); } return( <select onChange={selectedValue} > {optionItems} </select> ) } export default class ManageDictionaries extends React.Component { constructor(props, context) { super(props, context); this.handleClose = this.handleClose.bind(this); this.getDictionary = this.getDictionary.bind(this); this.getDictionaryElements = this.getDictionaryElements.bind(this); this.clickHandler = this.clickHandler.bind(this); this.addDictionary = this.addDictionary.bind(this); this.deleteDictionary = this.deleteDictionary.bind(this); this.fileSelectedHandler = this.fileSelectedHandler.bind(this); this.state = { show: true, selectedFile: '', dictNameFlag: false, exportFilename: '', content: null, newDict: '', newDictItem: '', delDictItem: '', addDict: false, delData: '', delDict: false, validImport: false, dictionaryNames: [], dictionaryElements: [], tableIcons: { Add: forwardRef((props, ref) => <AddBox {...props} ref={ref} />), Check: forwardRef((props, ref) => <Check {...props} ref={ref} />), Clear: forwardRef((props, ref) => <Clear {...props} ref={ref} />), Delete: forwardRef((props, ref) => <DeleteOutline {...props} ref={ref} />), DetailPanel: forwardRef((props, ref) => <ChevronRight {...props} ref={ref} />), Edit: forwardRef((props, ref) => <Edit {...props} ref={ref} />), Export: forwardRef((props, ref) => <VerticalAlignBottomIcon {...props} ref={ref} />), Filter: forwardRef((props, ref) => <FilterList {...props} ref={ref} />), FirstPage: forwardRef((props, ref) => <FirstPage {...props} ref={ref} />), LastPage: forwardRef((props, ref) => <LastPage {...props} ref={ref} />), NextPage: forwardRef((props, ref) => <ChevronRight {...props} ref={ref} />), PreviousPage: forwardRef((props, ref) => <ChevronLeft {...props} ref={ref} />), ResetSearch: forwardRef((props, ref) => <Clear {...props} ref={ref} />), Search: forwardRef((props, ref) => <Search {...props} ref={ref} />), SortArrow: forwardRef((props, ref) => <ArrowUpward {...props} ref={ref} />), ThirdStateCheck: forwardRef((props, ref) => <Remove {...props} ref={ref} />), ViewColumn: forwardRef((props, ref) => <ViewColumn {...props} ref={ref} />) }, dictColumns: [ { title: "Dictionary Name", field: "name",editable: 'onAdd', cellStyle: cellStyle, headerStyle: headerStyle }, { title: "Sub Dictionary ?", field: "secondLevelDictionary", lookup: {0: 'No', 1: 'Yes'}, cellStyle: cellStyle, headerStyle: headerStyle }, { title: "Dictionary Type", field: "subDictionaryType",lookup: {string: 'string', number: 'number'}, cellStyle: cellStyle, headerStyle: headerStyle }, { title: "Updated By", field: "updatedBy", editable: 'never', cellStyle: cellStyle, headerStyle: headerStyle }, { title: "Last Updated Date", field: "updatedDate", editable: 'never', cellStyle: cellStyle, headerStyle: headerStyle } ], dictElementColumns: [ { title: "Element Short Name", field: "shortName",editable: 'onAdd', cellStyle: cellStyle, headerStyle: headerStyle }, { title: "Element Name", field: "name", cellStyle: cellStyle, headerStyle: headerStyle }, { title: "Element Description", field: "description", cellStyle: cellStyle, headerStyle: headerStyle }, { title: "Element Type", field: "type", editComponent: props => ( <div> <SelectSubDictType value={props.value} onChange={props.onChange} /> </div> ), cellStyle: cellStyle, headerStyle: headerStyle }, { title: "Sub-Dictionary", field: "subDictionary", editComponent: props => ( <div> <SubDict value={props.value} onChange={props.onChange} /> </div> ), cellStyle: cellStyle, headerStyle: headerStyle }, { title: "Updated By", field: "updatedBy", editable: 'never', cellStyle: cellStyle, headerStyle: headerStyle }, { title: "Updated Date", field: "updatedDate", editable: 'never', cellStyle: cellStyle, headerStyle: headerStyle } ] } } componentWillMount() { this.getDictionary(); } getDictionary() { TemplateMenuService.getDictionary().then(dictionaryNames => { this.setState({ dictionaryNames: dictionaryNames }) }); } getDictionaryElements(dictionaryName) { TemplateMenuService.getDictionaryElements(dictionaryName).then(dictionaryElements => { dictList = this.state.dictionaryNames; this.setState({ dictionaryElements: dictionaryElements.dictionaryElements}); }); } clickHandler(rowData) { this.setState({ dictNameFlag: false, addDict: false, }); } handleClose() { this.setState({ show: false }); this.props.history.push('/'); } addDictionary() { var modifiedData = []; if(this.state.newDict !== '') { modifiedData = this.state.newDict; } else { modifiedData = {"name": this.state.dictionaryName, 'dictionaryElements': this.state.newDictItem}; } if(this.state.newDictItem === '') { TemplateMenuService.insDictionary(modifiedData).then(resp => { }); } else { TemplateMenuService.insDictionaryElements(modifiedData).then(resp => { }); } } deleteDictionary() { var modifiedData = []; if(this.state.delData !== '') { modifiedData = this.state.delData.name; } else { modifiedData = {"name": this.state.dictionaryName, "shortName": this.state.delDictItem.shortName}; } if(this.state.delDictItem === '') { TemplateMenuService.deleteDictionary(modifiedData).then(resp => { }); } else { TemplateMenuService.deleteDictionaryElements(modifiedData).then(resp => { }); } } fileSelectedHandler = (event) => { const text = this; var dictionaryElements = []; if (event.target.files[0].type === 'text/csv' ) { if (event.target.files && event.target.files[0]) { let reader = new FileReader(); reader.onload = function(e) { var dictElems = reader.result.split('\n'); var jsonObj = []; var headers = dictElems[0].split(','); for(var i = 0; i < dictElems.length; i++) { var data = dictElems[i].split(','); var obj = {}; for(var j = 0; j < data.length; j++) { obj[headers[j].trim()] = data[j].trim(); } jsonObj.push(obj); } JSON.stringify(jsonObj); const dictKeys = ['Element Short Name','Element Name','Element Description','Element Type','Sub-Dictionary']; const mandatoryKeys = [ 'Element Short Name', 'Element Name', 'Element Type' ]; const validTypes = ['string','number','datetime','json','map']; if (!dictElems){ text.setState({validData: false}); } else if (headers.length !== dictKeys.length){ text.setState({validImport: false}); } else { var subDictionaries = []; for(var item in dictList) { if(dictList[item].secondLevelDictionary === 1) { subDictionaries.push(dictList[item].name); } }; subDictionaries = subDictionaries.toString(); var row = 0; for (var dictElem of jsonObj){ ++row; for (var itemKey in dictElem){ var value = dictElem[itemKey].trim(); if (dictKeys.indexOf(itemKey) < 0){ var errorMessage = 'unknown field name of, ' + itemKey + ', found in CSV header'; text.setState({validImport: false}); alert(errorMessage); break; } else if (value === "" && mandatoryKeys.indexOf(itemKey) >= 0){ errorMessage = 'value for ' + itemKey + ', at row #, ' + row + ', is empty but required'; text.setState({validImport: false}); alert(errorMessage); break; } else if (itemKey === 'Element Type' && validTypes.indexOf(value) < 0 && row > 1) { errorMessage = 'invalid dictElemenType of ' + value + ' at row #' + row; text.setState({validImport: false}); alert(errorMessage); break; } else if (value !== "" && itemKey === 'Sub-Dictionary' && subDictionaries.indexOf(value) < 0 && row > 1) { errorMessage = 'invalid subDictionary of ' + value + ' at row #' + row; text.setState({validImport: false}); alert(errorMessage); } } } } const headerKeys = ['shortName','name','description','type','subDictionary']; for(i = 1; i < dictElems.length; i++) { data = dictElems[i].split(','); obj = {}; for(j = 0; j < data.length; j++) { obj[headerKeys[j].trim()] = data[j].trim(); } dictionaryElements.push(obj); } text.setState({newDictItem: dictionaryElements, addDict: true}); } reader.readAsText(event.target.files[0]); } this.setState({selectedFile: event.target.files[0]}) } else { text.setState({validImport: false}); alert('Please upload .csv extention files only.'); } } render() { return ( <ModalStyled size="xl" show={this.state.show} onHide={this.handleClose}> <Modal.Header closeButton> <Modal.Title>Manage Dictionaries</Modal.Title> </Modal.Header> <Modal.Body> {!this.state.dictNameFlag? <MaterialTable title={"Dictionary List"} data={this.state.dictionaryNames} columns={this.state.dictColumns} icons={this.state.tableIcons} onRowClick={(event, rowData) => {this.getDictionaryElements(rowData.name);this.setState({dictNameFlag: true, exportFilename: rowData.name, dictionaryName: rowData.name})}} options={{ headerStyle: rowHeaderStyle, }} editable={{ onRowAdd: newData => new Promise((resolve, reject) => { setTimeout(() => { { const dictionaryNames = this.state.dictionaryNames; var validData = true; if(/[^a-zA-Z0-9-_.]/.test(newData.name)) { validData = false; alert('Please enter alphanumberic input. Only allowed special characters are:(period, hyphen, underscore)'); } for (var i = 0; i < this.state.dictionaryNames.length; i++) { if (this.state.dictionaryNames[i].name === newData.name) { validData = false; alert(newData.name + ' dictionary name already exists') } } if(validData){ dictionaryNames.push(newData); this.setState({ dictionaryNames }, () => resolve()); this.setState({addDict: true, newDict: newData}); } } resolve(); }, 1000); }), onRowUpdate: (newData, oldData) => new Promise((resolve, reject) => { setTimeout(() => { { const dictionaryNames = this.state.dictionaryNames; var validData = true; if(/[^a-zA-Z0-9-_.]/.test(newData.name)) { validData = false; alert('Please enter alphanumberic input. Only allowed special characters are:(period, hyphen, underscore)'); } if(validData){ const index = dictionaryNames.indexOf(oldData); dictionaryNames[index] = newData; this.setState({ dictionaryNames }, () => resolve()); this.setState({addDict: true, newDict: newData}); } } resolve(); }, 1000); }), onRowDelete: oldData => new Promise((resolve, reject) => { setTimeout(() => { { let data = this.state.dictionaryNames; const index = data.indexOf(oldData); data.splice(index, 1); this.setState({ data }, () => resolve()); this.setState({delDict: true, delData: oldData}) } resolve() }, 1000) }) }} />:"" } {this.state.dictNameFlag? <MaterialTable title={"Dictionary Elements List"} data={this.state.dictionaryElements} columns={this.state.dictElementColumns} icons={this.state.tableIcons} options={{ exportButton: true, exportFileName: this.state.exportFilename, headerStyle:{backgroundColor:'white', fontSize: '15pt', text: 'bold', border: '1px solid black'} }} components={{ Toolbar: props => ( <div> <MTableToolbar {...props} /> <div> <Grid item container xs={12} alignItems="flex-end" direction="column" justify="flex-end"> <Tooltip title="Import" placement = "bottom"> <IconButton aria-label="import" onClick={() => this.fileUpload.click()}> <VerticalAlignTopIcon /> </IconButton> </Tooltip> </Grid> </div> <input type="file" ref={(fileUpload) => {this.fileUpload = fileUpload;}} style={{ visibility: 'hidden'}} onChange={this.fileSelectedHandler} /> </div> ) }} editable={{ onRowAdd: newData => new Promise((resolve, reject) => { setTimeout(() => { { const dictionaryElements = this.state.dictionaryElements; var validData = true; for (var i = 0; i < this.state.dictionaryElements.length; i++) { if (this.state.dictionaryElements[i].shortName === newData.shortName) { validData = false; alert(newData.shortname + 'short name already exists') } } if(/[^a-zA-Z0-9-_.]/.test(newData.shortName)) { validData = false; alert('Please enter alphanumberic input. Only allowed special characters are:(period, hyphen, underscore)'); } if(!newData.type){ validData = false; alert('Element Type cannot be null'); } if(validData){ dictionaryElements.push(newData); this.setState({ dictionaryElements }, () => resolve()); this.setState({addDict: true, newDictItem: [newData]}); } } resolve(); }, 1000); }), onRowUpdate: (newData, oldData) => new Promise((resolve, reject) => { setTimeout(() => { { const dictionaryElements = this.state.dictionaryElements; var validData = true; if(!newData.type){ validData = false; alert('Element Type cannot be null'); } if(validData){ const index = dictionaryElements.indexOf(oldData); dictionaryElements[index] = newData; this.setState({ dictionaryElements }, () => resolve()); this.setState({addDict: true, newDictItem: [newData]}); } } resolve(); }, 1000); }), onRowDelete: oldData => new Promise((resolve, reject) => { setTimeout(() => { { let data = this.state.dictionaryElements; const index = data.indexOf(oldData); data.splice(index, 1); this.setState({ data }, () => resolve()); this.setState({delDict: true, delDictItem: oldData}) } resolve() }, 1000) }) }} />:"" } {this.state.dictNameFlag?<button onClick={this.clickHandler} style={{marginTop: '25px'}}>Go Back to Dictionaries List</button>:""} {this.state.addDict && this.addDictionary()} {this.state.delDict && this.deleteDictionary()} </Modal.Body> <Modal.Footer> <Button variant="secondary" type="null" onClick={this.handleClose}>Close</Button> </Modal.Footer> </ModalStyled> ); } }