/*- * ============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, { forwardRef } from 'react'; import Button from 'react-bootstrap/Button'; import Modal from 'react-bootstrap/Modal'; import Row from 'react-bootstrap/Row'; import Col from 'react-bootstrap/Col'; import styled from 'styled-components'; import TemplateMenuService from '../../../api/TemplateService'; import CsvToJson from '../../../utils/CsvToJson'; import MaterialTable, { MTableToolbar } from "material-table"; import IconButton from '@material-ui/core/IconButton'; import Tooltip from '@material-ui/core/Tooltip'; 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)` @media (min-width: 1200px) { .modal-xl { max-width: 96%; } } background-color: transparent; ` const MTableToolbarStyled = styled(MTableToolbar)` display: flex; flex-direction: row; align-items: center; ` const ColPullLeftStyled = styled(Col)` display: flex; flex-direction: row; align-items: center; margin-left: -40px; ` 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' }; let dictList = []; let subDictFlag = false; function SelectSubDictType(props) { const { onChange } = props; const selectedValues = (e) => { let options = e.target.options; let SelectedDictTypes = ''; for (let 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); } // When the subDictFlag is true, we need to disable selection of element "type" return (
); } function SubDict(props) { const { onChange } = props; const subDicts = []; subDicts.push('none'); if (dictList !== undefined && dictList.length > 0) { let item; for (item in dictList) { if (dictList[item].secondLevelDictionary === 1) { subDicts.push(dictList[item].name); } } } let optionItems = []; for (let i = 0; i < subDicts.length; ++i) { if (i === 0) { optionItems.push(); } else { optionItems.push(); } } function selectedValue(e) { onChange(e.target.value); } // When the subDictFlag is true, we need to disable selection of // the sub-dictionary flag return ( ); } export default class ManageDictionaries extends React.Component { constructor(props, context) { super(props, context); this.addDictionaryElementRow = this.addDictionaryElementRow.bind(this); this.addDictionaryRow = this.addDictionaryRow.bind(this); this.addReplaceDictionaryRequest = this.addReplaceDictionaryRequest.bind(this); this.clickHandler = this.clickHandler.bind(this); this.deleteDictionaryElementRow = this.deleteDictionaryElementRow.bind(this); this.deleteDictionaryRequest = this.deleteDictionaryRequest.bind(this); this.deleteDictionaryRow = this.deleteDictionaryRow.bind(this); this.fileSelectedHandler = this.fileSelectedHandler.bind(this); this.getDictionaries = this.getDictionaries.bind(this); this.getDictionaryElements = this.getDictionaryElements.bind(this); this.handleClose = this.handleClose.bind(this); this.handleDictionaryRowClick = this.handleDictionaryRowClick.bind(this); this.importCsvData = this.importCsvData.bind(this); this.updateDictionaryElementRow = this.updateDictionaryElementRow.bind(this); this.updateDictionaryElementsRequest = this.updateDictionaryElementsRequest.bind(this); this.updateDictionaryRow = this.updateDictionaryRow.bind(this); this.readOnly = props.readOnly !== undefined ? props.readOnly : false; this.state = { show: true, currentSelectedDictionary: null, exportFilename: '', content: null, dictionaryElements: [], tableIcons: { Add: forwardRef((props, ref) => ), Delete: forwardRef((props, ref) => ), DetailPanel: forwardRef((props, ref) => ), Edit: forwardRef((props, ref) => ), Check: forwardRef((props, ref) => ), Clear: forwardRef((props, ref) => ), Export: forwardRef((props, ref) => ), Filter: forwardRef((props, ref) => ), FirstPage: forwardRef((props, ref) => ), LastPage: forwardRef((props, ref) => ), NextPage: forwardRef((props, ref) => ), PreviousPage: forwardRef((props, ref) => ), ResetSearch: forwardRef((props, ref) => ), Search: forwardRef((props, ref) => ), SortArrow: forwardRef((props, ref) => ), ThirdStateCheck: forwardRef((props, ref) => ), ViewColumn: forwardRef((props, 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 => (
), cellStyle: cellStyle, headerStyle: headerStyle }, { title: "Sub-Dictionary", field: "subDictionary", editComponent: props => (
), 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 } ] } } componentDidMount() { this.getDictionaries(); } getDictionaries() { TemplateMenuService.getDictionary().then(arrayOfdictionaries => { this.setState({ dictionaries: arrayOfdictionaries, currentSelectedDictionary: null }) // global variable setting used functional components in this file dictList = arrayOfdictionaries; }).catch(() => { console.error('Failed to retrieve dictionaries'); this.setState({ dictionaries: [], currentSelectedDictionary: null }) }); } getDictionaryElements(dictionaryName) { TemplateMenuService.getDictionaryElements(dictionaryName).then(dictionaryElements => { this.setState({ dictionaryElements: dictionaryElements.dictionaryElements }); this.setState({ currentSelectDictionary: dictionaryName }); }).catch(() => console.error('Failed to retrieve dictionary elements')) } clickHandler(rowData) { this.getDictionaries(); } handleClose() { this.setState({ show: false }); this.props.history.push('/'); } addReplaceDictionaryRequest(dictionaryEntry) { TemplateMenuService.insDictionary(dictionaryEntry) .then(resp => { this.getDictionaries(); }) .catch(() => console.error('Failed to insert new dictionary elements')); } updateDictionaryElementsRequest(dictElements) { let reqData = { "name": this.state.currentSelectedDictionary, 'dictionaryElements': dictElements }; TemplateMenuService.insDictionaryElements(reqData) .then(resp => { this.getDictionaryElements(this.state.currentSelectedDictionary) }) .catch(() => console.error('Failed to update dictionary elements')); } deleteDictionaryRequest(dictionaryName) { TemplateMenuService.deleteDictionary(dictionaryName) .then(resp => { this.getDictionaries(); }) .catch(() => console.error('Failed to delete dictionary')); } deleteDictionaryElementRequest(dictionaryName, elemenetShortName) { TemplateMenuService.deleteDictionaryElements({ 'name': dictionaryName, 'shortName': elemenetShortName }) .then(resp => { this.getDictionaryElements(dictionaryName); }) .catch(() => console.error('Failed to delete dictionary elements')); } fileSelectedHandler = (event) => { if (event.target.files[0].type === 'text/csv' || event.target.files[0].type === 'application/vnd.ms-excel') { if (event.target.files && event.target.files[0]) { const reader = new FileReader(); reader.onload = (e) => { let errorMessages = this.importCsvData(reader.result); if (errorMessages !== '') { alert(errorMessages); } } reader.readAsText(event.target.files[0]); } } else { alert('Please upload .csv extention files only.'); } } importCsvData(rawCsvData) { const jsonKeyNames = ['shortName', 'name', 'description', 'type', 'subDictionary']; const userHeaderNames = ['Element Short Name', 'Element Name', 'Element Description', 'Element Type', 'Sub-Dictionary']; const validTypes = ['string', 'number', 'datetime', 'json', 'map']; let mandatory; if (subDictFlag) { mandatory = [true, true, true, false, false]; } else { mandatory = [true, true, true, true, false]; } let result = CsvToJson(rawCsvData, ',', '||||', userHeaderNames, jsonKeyNames, mandatory); let errorMessages = result.errorMessages; let jsonObjArray = result.jsonObjArray; let validTypesErrorMesg = ''; for (let i = 0; i < validTypes.length; ++i) { if (i === 0) { validTypesErrorMesg = validTypes[i]; } else { validTypesErrorMesg += ',' + validTypes[i]; } } if (errorMessages !== '') { return errorMessages; } // Perform further checks on data that is now in JSON form let subDictionaries = []; // NOTE: dictList is a global variable maintained faithfully // by the getDictionaries() method outside this import // functionality. let item; for (item in dictList) { if (dictList[item].secondLevelDictionary === 1) { subDictionaries.push(dictList[item].name); } } ; // Check for valid Sub-Dictionary and Element Type values subDictionaries = subDictionaries.toString(); let row = 2; let dictElem; for (dictElem of jsonObjArray) { let itemKey; for (itemKey in dictElem) { let value = dictElem[itemKey].trim(); let keyIndex = jsonKeyNames.indexOf(itemKey); if (itemKey === 'shortName' && /[^a-zA-Z0-9-_.]/.test(value)) { errorMessages += '\n' + userHeaderNames[keyIndex] + ' at row #' + row + ' can only contain alphanumeric characters and periods, hyphens or underscores'; } if (itemKey === 'type' && validTypes.indexOf(value) < 0) { errorMessages += '\nInvalid value of "' + value + '" for "' + userHeaderNames[keyIndex] + '" at row #' + row; errorMessages += '\nValid types are: ' + validTypesErrorMesg; } if (value !== "" && itemKey === 'subDictionary' && subDictionaries.indexOf(value) < 0) { errorMessages += '\nInvalid Sub-Dictionary value of "' + value + '" at row #' + row; } } ++row; } if (errorMessages === '') { // We made it through all the checks. Send it to back end this.updateDictionaryElementsRequest(jsonObjArray); } return errorMessages; } addDictionaryRow(newData) { let validData = true; return new Promise((resolve, reject) => { setTimeout(() => { if (/[^a-zA-Z0-9-_.]/.test(newData.name)) { validData = false; alert('Please enter alphanumeric input. Only allowed special characters are:(period, hyphen, underscore)'); reject(); } for (let i = 0; i < this.state.dictionaries.length; i++) { if (this.state.dictionaries[i].name === newData.name) { validData = false; alert(newData.name + ' dictionary name already exists') reject(); } } if (validData) { this.addReplaceDictionaryRequest(newData); } resolve(); }, 1000); }); } updateDictionaryRow(newData, oldData) { let validData = true; return new Promise((resolve, reject) => { setTimeout(() => { if (/[^a-zA-Z0-9-_.]/.test(newData.name)) { validData = false; alert('Please enter alphanumberic input. Only allowed special characters are:(period, hyphen, underscore)'); reject(); } if (validData) { this.addReplaceDictionaryRequest(newData); } resolve(); }, 1000); }); } deleteDictionaryRow(oldData) { return new Promise((resolve, reject) => { setTimeout(() => { this.deleteDictionaryRequest(oldData.name); resolve(); }, 1000); }); } addDictionaryElementRow(newData) { return new Promise((resolve, reject) => { setTimeout(() => { let dictionaryElements = this.state.dictionaryElements; let errorMessages = ''; for (let i = 0; i < this.state.dictionaryElements.length; i++) { if (this.state.dictionaryElements[i].shortName === newData.shortName) { alert('Short Name "' + newData.shortName + '" already exists'); reject(""); } } // MaterialTable returns no property at all if the user has not touched a // new column, so we want to add the property with an emptry string // for several cases if that is the case to simplify other checks. if (newData.description === undefined) { newData.description = ""; } if (newData.subDictionary === undefined) { newData.subDictionary = null; } if (newData.type === undefined) { newData.type = ""; } if (!newData.shortName && /[^a-zA-Z0-9-_.]/.test(newData.shortName)) { errorMessages += '\nShort Name is limited to alphanumeric characters and also period, hyphen, and underscore'; } if (!newData.shortName) { errorMessages += '\nShort Name must be specified'; } if (!newData.name) { errorMessages += '\nElement Name must be specified'; } if (!newData.type && !subDictFlag) { errorMessages += '\nElement Type must be specified'; } if (errorMessages === '') { dictionaryElements.push(newData); this.updateDictionaryElementsRequest([newData]); resolve(); } else { alert(errorMessages); reject(""); } }, 1000); }); } updateDictionaryElementRow(newData, oldData) { return new Promise((resolve, reject) => { setTimeout(() => { let dictionaryElements = this.state.dictionaryElements; let validData = true; if (!newData.type) { validData = false; alert('Element Type cannot be null'); reject(); } if (validData) { const index = dictionaryElements.indexOf(oldData); dictionaryElements[index] = newData; this.updateDictionaryElementsRequest([newData]); } resolve(); }, 1000); }); } deleteDictionaryElementRow(oldData) { return new Promise((resolve) => { setTimeout(() => { this.deleteDictionaryElementRequest(this.state.currentSelectedDictionary, oldData.shortName); resolve(); }, 1000); }); } handleDictionaryRowClick(event, rowData) { subDictFlag = rowData.secondLevelDictionary === 1 ? true : false; this.setState({ currentSelectedDictionary: rowData.name, exportFilename: rowData.name }) this.getDictionaryElements(rowData.name); } render() { return ( Manage Dictionaries { this.state.currentSelectedDictionary === null ? : null } { this.state.currentSelectedDictionary !== null ? ( this.fileUpload.click() }> { this.fileUpload = fileUpload; } } style={ { visibility: 'hidden', width: '1px' } } onChange={ this.fileSelectedHandler }/> ) } } editable={ !this.readOnly ? { onRowAdd: this.addDictionaryElementRow, onRowUpdate: this.updateDictionaryElementRow, onRowDelete: this.deleteDictionaryElementRow } : undefined } /> : null } { this.state.currentSelectedDictionary !== null ? : "" } ); } }