diff options
9 files changed, 1180 insertions, 17 deletions
diff --git a/ui-react/src/LoopUI.js b/ui-react/src/LoopUI.js index 471e8720..bc3f2355 100644 --- a/ui-react/src/LoopUI.js +++ b/ui-react/src/LoopUI.js @@ -49,6 +49,7 @@ import LoopService from './api/LoopService'; import UploadToscaPolicyModal from './components/dialogs/Tosca/UploadToscaPolicyModal'; import ViewToscaPolicyModal from './components/dialogs/Tosca/ViewToscaPolicyModal'; import ViewLoopTemplatesModal from './components/dialogs/Tosca/ViewLoopTemplatesModal'; +import ManageDictionaries from './components/dialogs/ManageDictionaries/ManageDictionaries'; import PerformAction from './components/dialogs/PerformActions'; import RefreshStatus from './components/dialogs/RefreshStatus'; import DeployLoopModal from './components/dialogs/Loop/DeployLoopModal'; @@ -255,6 +256,7 @@ export default class LoopUI extends React.Component { <Route path="/uploadToscaPolicyModal" render={(routeProps) => (<UploadToscaPolicyModal {...routeProps} />)} /> <Route path="/viewToscaPolicyModal" render={(routeProps) => (<ViewToscaPolicyModal {...routeProps} />)} /> <Route path="/ViewLoopTemplatesModal" render={(routeProps) => (<ViewLoopTemplatesModal {...routeProps} />)} /> + <Route path="/ManageDictionaries" render={(routeProps) => (<ManageDictionaries {...routeProps} />)} /> <Route path="/operationalPolicyModal" render={(routeProps) => (<OperationalPolicyModal {...routeProps} loopCache={this.getLoopCache()} loadLoopFunction={this.loadLoop} updateLoopFunction={this.updateLoopCache} showAlert={this.showAlert}/>)} /> <Route path="/policyModal/:policyInstanceType/:policyName" render={(routeProps) => (<PolicyModal {...routeProps} loopCache={this.getLoopCache()} loadLoopFunction={this.loadLoop}/>)} /> diff --git a/ui-react/src/__snapshots__/LoopUI.test.js.snap b/ui-react/src/__snapshots__/LoopUI.test.js.snap index 7d2c4467..9de232dd 100644 --- a/ui-react/src/__snapshots__/LoopUI.test.js.snap +++ b/ui-react/src/__snapshots__/LoopUI.test.js.snap @@ -17,6 +17,10 @@ exports[`Verify LoopUI Test the render method 1`] = ` render={[Function]} /> <Route + path="/ManageDictionaries" + render={[Function]} + /> + <Route path="/operationalPolicyModal" render={[Function]} /> diff --git a/ui-react/src/__snapshots__/OnapClamp.test.js.snap b/ui-react/src/__snapshots__/OnapClamp.test.js.snap index e195523b..91812b7c 100644 --- a/ui-react/src/__snapshots__/OnapClamp.test.js.snap +++ b/ui-react/src/__snapshots__/OnapClamp.test.js.snap @@ -42,6 +42,10 @@ exports[`Verify OnapClamp Test the render method 1`] = ` render={[Function]} /> <Route + path="/ManageDictionaries" + render={[Function]} + /> + <Route path="/operationalPolicyModal" render={[Function]} /> diff --git a/ui-react/src/api/TemplateService.js b/ui-react/src/api/TemplateService.js index 6a65d9a0..124d29c2 100644 --- a/ui-react/src/api/TemplateService.js +++ b/ui-react/src/api/TemplateService.js @@ -38,20 +38,159 @@ export default class TemplateService { }); } - static getBlueprintMicroServiceTemplates() { - return fetch('restservices/clds/v2/templates', { method: 'GET', credentials: 'same-origin', }) - .then(function (response) { - console.debug("getBlueprintMicroServiceTemplates response received: ", response.status); - if (response.ok) { - return response.json(); - } else { - console.error("getBlueprintMicroServiceTemplates query failed"); - return {}; - } - }) - .catch(function (error) { - console.error("getBlueprintMicroServiceTemplates error received", error); - return {}; - }); - } -} + static getBlueprintMicroServiceTemplates() { + return fetch('restservices/clds/v2/templates', { method: 'GET', credentials: 'same-origin', }) + .then(function (response) { + console.debug("getBlueprintMicroServiceTemplates response received: ", response.status); + if (response.ok) { + return response.json(); + } else { + console.error("getBlueprintMicroServiceTemplates query failed"); + return {}; + } + }) + .catch(function (error) { + console.error("getBlueprintMicroServiceTemplates error received", error); + return {}; + }); + } + + static getDictionary() { + return fetch('restservices/clds/v2/dictionary/', { method: 'GET', credentials: 'same-origin', }) + .then(function (response) { + console.debug("getDictionary response received: ", response.status); + if (response.ok) { + return response.json(); + } else { + console.error("getDictionary query failed"); + return {}; + } + }) + .catch(function (error) { + console.error("getDictionary error received", error); + return {}; + }); + } + + static getDictionaryElements(dictionaryName) { + return fetch('restservices/clds/v2/dictionary/' + dictionaryName, { + method: 'GET', + headers: { + "Content-Type": "application/json", + }, + credentials: 'same-origin', + }) + .then(function (response) { + console.debug("getDictionaryElements response received: ", response.status); + if (response.ok) { + return response.json(); + } else { + console.error("getDictionaryElements query failed"); + return {}; + } + }) + .catch(function (error) { + console.error("getDictionaryElements error received", error); + return {}; + }); + } + + static insDictionary(jsonData) { + console.log("dictionaryName is", jsonData.name) + return fetch('/restservices/clds/v2/dictionary/', { + method: 'PUT', + credentials: 'same-origin', + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(jsonData) + }) + .then(function (response) { + console.debug("insDictionary response received: ", response.status); + if (response.ok) { + return response.status; + } else { + var errorMessage = response.status; + console.error("insDictionary query failed", response.status); + return errorMessage; + } + }) + .catch(function (error) { + console.error("insDictionary error received", error); + return ""; + }); + } + + static insDictionaryElements(jsonData) { + console.log("dictionaryName is", jsonData.name) + return fetch('/restservices/clds/v2/dictionary/' + jsonData.name, { + method: 'PUT', + credentials: 'same-origin', + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(jsonData) + }) + .then(function (response) { + console.debug("insDictionary response received: ", response.status); + if (response.ok) { + return response.status; + } else { + var errorMessage = response.status; + console.error("insDictionary query failed", response.status); + return errorMessage; + } + }) + .catch(function (error) { + console.error("insDictionary error received", error); + return ""; + }); + } + + static deleteDictionary(dictionaryName) { + console.log("inside templaemenu service", dictionaryName) + return fetch('restservices/clds/v2/dictionary/' + dictionaryName, { + method: 'DELETE', + headers: { + "Content-Type": "application/json", + }, + credentials: 'same-origin', + }) + .then(function (response) { + console.debug("deleteDictionary response received: ", response.status); + if (response.ok) { + return response.status; + } else { + console.error("deleteDictionary query failed"); + return {}; + } + }) + .catch(function (error) { + console.error("deleteDictionary error received", error); + return {}; + }); + } + + static deleteDictionaryElements(dictionaryData) { + return fetch('restservices/clds/v2/dictionary/' + dictionaryData.name + '/elements/' + dictionaryData.shortName , { + method: 'DELETE', + headers: { + "Content-Type": "application/json", + }, + credentials: 'same-origin', + }) + .then(function (response) { + console.debug("deleteDictionary response received: ", response.status); + if (response.ok) { + return response.status; + } else { + console.error("deleteDictionary query failed"); + return {}; + } + }) + .catch(function (error) { + console.error("deleteDictionary error received", error); + return {}; + }); + } + } diff --git a/ui-react/src/components/dialogs/ManageDictionaries/ManageDictionaries.js b/ui-react/src/components/dialogs/ManageDictionaries/ManageDictionaries.js new file mode 100644 index 00000000..18952376 --- /dev/null +++ b/ui-react/src/components/dialogs/ManageDictionaries/ManageDictionaries.js @@ -0,0 +1,559 @@ +/*- + * ============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 }) + }); + var dictNamesingetDict = this.state.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> + ); + } +} diff --git a/ui-react/src/components/dialogs/ManageDictionaries/ManageDictionaries.test.js b/ui-react/src/components/dialogs/ManageDictionaries/ManageDictionaries.test.js new file mode 100644 index 00000000..4363da92 --- /dev/null +++ b/ui-react/src/components/dialogs/ManageDictionaries/ManageDictionaries.test.js @@ -0,0 +1,202 @@ +/*- + * ============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 { shallow } from 'enzyme'; +import { mount } from 'enzyme'; +import { render } from 'enzyme'; +import ManageDictionaries from './ManageDictionaries'; +import TemplateMenuService from '../../../api/TemplateService' + +describe('Verify ManageDictionaries', () => { + beforeEach(() => { + fetch.resetMocks(); + }); + + it('Test API Successful', () => { + fetch.mockImplementationOnce(() => { + return Promise.resolve({ + ok: true, + status: 200, + json: () => { + return Promise.resolve({ + "name": "vtest", + "secondLevelDictionary": "1", + "subDictionaryType": "string", + "updatedBy": "test", + "updatedDate": "05-07-2019 19:09:42" + }); + } + }); + }); + const component = shallow(<ManageDictionaries />); + expect(component).toMatchSnapshot(); + }); + + it('Test API Exception', () => { + fetch.mockImplementationOnce(() => { + return Promise.resolve({ + ok: false, + status: 500, + json: () => { + return Promise.resolve({ + "name": "vtest", + "secondLevelDictionary": "1", + "subDictionaryType": "string", + "updatedBy": "test", + "updatedDate": "05-07-2019 19:09:42" + }); + } + }); + }); + const component = shallow(<ManageDictionaries />); + }); + + it('Test API Rejection', () => { + const myMockFunc = fetch.mockImplementationOnce(() => Promise.reject('error')); + setTimeout( () => myMockFunc().catch(e => { + console.log(e); + }), + 100 + ); + new Promise(resolve => setTimeout(resolve, 200)); + const component = shallow(<ManageDictionaries />); + expect(myMockFunc.mock.calls.length).toBe(1); + }); + + it('Test Table icons', () => { + fetch.mockImplementationOnce(() => { + return Promise.resolve({ + ok: true, + status: 200, + json: () => { + return Promise.resolve({ + "name": "vtest", + "secondLevelDictionary": "1", + "subDictionaryType": "string", + "updatedBy": "test", + "updatedDate": "05-07-2019 19:09:42" + }); + } + }); + }); + const component = mount(<ManageDictionaries />); + expect(component.find('[className="MuiSelect-icon MuiTablePagination-selectIcon"]')).toBeTruthy(); + }); + + test('Test get dictionaryNames/dictionaryElements, add/delete dictionary functions', async () => { + const historyMock = { push: jest.fn() }; + TemplateMenuService.getDictionary = jest.fn().mockImplementation(() => { + return Promise.resolve("test"); + }); + TemplateMenuService.getDictionaryElements = jest.fn().mockImplementation(() => { + return Promise.resolve({dictionaryElements:"testitem"}); + }); + TemplateMenuService.insDictionary = jest.fn().mockImplementation(() => { + return Promise.resolve(200); + }); + TemplateMenuService.deleteDictionary = jest.fn().mockImplementation(() => { + return Promise.resolve(200); + }); + const flushPromises = () => new Promise(setImmediate); + const component = shallow(<ManageDictionaries history={historyMock} />) + component.setState({ newDict: { + "name": "test", + "secondLevelDictionary": "0", + "subDictionaryType": "string" + } + }); + component.setState({ delData: { + "name": "test", + "secondLevelDictionary": "0", + "subDictionaryType": "string" + } + }); + const instance = component.instance(); + instance.getDictionaryElements("test"); + instance.clickHandler(); + instance.addDictionary(); + instance.deleteDictionary(); + await flushPromises(); + expect(component.state('dictionaryNames')).toEqual("test"); + expect(component.state('dictionaryElements')).toEqual("testitem"); + expect(component.state('dictNameFlag')).toEqual(false); + }); + + test('Test adding and deleting dictionaryelements', async () => { + const historyMock = { push: jest.fn() }; + TemplateMenuService.getDictionary = jest.fn().mockImplementation(() => { + return Promise.resolve("test"); + }); + TemplateMenuService.insDictionaryElements = jest.fn().mockImplementation(() => { + return Promise.resolve(200); + }); + TemplateMenuService.deleteDictionaryElements = jest.fn().mockImplementation(() => { + return Promise.resolve(200); + }); + const flushPromises = () => new Promise(setImmediate); + const component = shallow(<ManageDictionaries history={historyMock}/>) + component.setState({ newDictItem: { + "name": "test", + "dictionaryElements" : { + "shortName": "shorttest", + } + }}); + component.setState({ delDictItem: { + "name": "test", + "dictionaryElements" : { + "shortName": "shortTest", + } + }}); + const instance = component.instance(); + instance.addDictionary(); + instance.deleteDictionary(); + await flushPromises(); + expect(component.state('dictionaryNames')).toEqual("test"); + }); + + it('Test handleClose', () => { + fetch.mockImplementationOnce(() => { + return Promise.resolve({ + ok: true, + status: 200, + json: () => { + return Promise.resolve({ + "name": "vtest", + "secondLevelDictionary": "1", + "subDictionaryType": "string", + "updatedBy": "test", + "updatedDate": "05-07-2019 19:09:42" + }); + } + }); + }); + const historyMock = { push: jest.fn() }; + const handleClose = jest.spyOn(ManageDictionaries.prototype,'handleClose'); + const component = shallow(<ManageDictionaries history={historyMock} />) + component.find('[variant="secondary"]').prop('onClick')(); + expect(handleClose).toHaveBeenCalledTimes(1); + expect(component.state('show')).toEqual(false); + expect(historyMock.push.mock.calls[0]).toEqual([ '/']); + handleClose.mockClear(); + }); +}); diff --git a/ui-react/src/components/dialogs/ManageDictionaries/__snapshots__/ManageDictionaries.test.js.snap b/ui-react/src/components/dialogs/ManageDictionaries/__snapshots__/ManageDictionaries.test.js.snap new file mode 100644 index 00000000..e7829221 --- /dev/null +++ b/ui-react/src/components/dialogs/ManageDictionaries/__snapshots__/ManageDictionaries.test.js.snap @@ -0,0 +1,195 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Verify ManageDictionaries Test API Successful 1`] = ` +<Styled(Bootstrap(Modal)) + onHide={[Function]} + show={true} + size="xl" +> + <ModalHeader + closeButton={true} + closeLabel="Close" + > + <ModalTitle> + Manage Dictionaries + </ModalTitle> + </ModalHeader> + <ModalBody> + <WithStyles(Component) + columns={ + Array [ + Object { + "cellStyle": Object { + "border": "1px solid black", + }, + "editable": "onAdd", + "field": "name", + "headerStyle": Object { + "backgroundColor": "#ddd", + "border": "2px solid black", + }, + "title": "Dictionary Name", + }, + Object { + "cellStyle": Object { + "border": "1px solid black", + }, + "field": "secondLevelDictionary", + "headerStyle": Object { + "backgroundColor": "#ddd", + "border": "2px solid black", + }, + "lookup": Object { + "0": "No", + "1": "Yes", + }, + "title": "Sub Dictionary ?", + }, + Object { + "cellStyle": Object { + "border": "1px solid black", + }, + "field": "subDictionaryType", + "headerStyle": Object { + "backgroundColor": "#ddd", + "border": "2px solid black", + }, + "lookup": Object { + "number": "number", + "string": "string", + }, + "title": "Dictionary Type", + }, + Object { + "cellStyle": Object { + "border": "1px solid black", + }, + "editable": "never", + "field": "updatedBy", + "headerStyle": Object { + "backgroundColor": "#ddd", + "border": "2px solid black", + }, + "title": "Updated By", + }, + Object { + "cellStyle": Object { + "border": "1px solid black", + }, + "editable": "never", + "field": "updatedDate", + "headerStyle": Object { + "backgroundColor": "#ddd", + "border": "2px solid black", + }, + "title": "Last Updated Date", + }, + ] + } + data={Array []} + editable={ + Object { + "onRowAdd": [Function], + "onRowDelete": [Function], + "onRowUpdate": [Function], + } + } + icons={ + Object { + "Add": Object { + "$$typeof": Symbol(react.forward_ref), + "render": [Function], + }, + "Check": Object { + "$$typeof": Symbol(react.forward_ref), + "render": [Function], + }, + "Clear": Object { + "$$typeof": Symbol(react.forward_ref), + "render": [Function], + }, + "Delete": Object { + "$$typeof": Symbol(react.forward_ref), + "render": [Function], + }, + "DetailPanel": Object { + "$$typeof": Symbol(react.forward_ref), + "render": [Function], + }, + "Edit": Object { + "$$typeof": Symbol(react.forward_ref), + "render": [Function], + }, + "Export": Object { + "$$typeof": Symbol(react.forward_ref), + "render": [Function], + }, + "Filter": Object { + "$$typeof": Symbol(react.forward_ref), + "render": [Function], + }, + "FirstPage": Object { + "$$typeof": Symbol(react.forward_ref), + "render": [Function], + }, + "LastPage": Object { + "$$typeof": Symbol(react.forward_ref), + "render": [Function], + }, + "NextPage": Object { + "$$typeof": Symbol(react.forward_ref), + "render": [Function], + }, + "PreviousPage": Object { + "$$typeof": Symbol(react.forward_ref), + "render": [Function], + }, + "ResetSearch": Object { + "$$typeof": Symbol(react.forward_ref), + "render": [Function], + }, + "Search": Object { + "$$typeof": Symbol(react.forward_ref), + "render": [Function], + }, + "SortArrow": Object { + "$$typeof": Symbol(react.forward_ref), + "render": [Function], + }, + "ThirdStateCheck": Object { + "$$typeof": Symbol(react.forward_ref), + "render": [Function], + }, + "ViewColumn": Object { + "$$typeof": Symbol(react.forward_ref), + "render": [Function], + }, + } + } + onRowClick={[Function]} + options={ + Object { + "headerStyle": Object { + "backgroundColor": "#ddd", + "border": "1px solid black", + "fontSize": "15pt", + "text": "bold", + }, + } + } + title="Dictionary List" + /> + </ModalBody> + <ModalFooter> + <Button + active={false} + disabled={false} + onClick={[Function]} + type="null" + variant="secondary" + > + Close + </Button> + </ModalFooter> +</Styled(Bootstrap(Modal))> +`; diff --git a/ui-react/src/components/menu/MenuBar.js b/ui-react/src/components/menu/MenuBar.js index 92380ab6..4eafaa44 100644 --- a/ui-react/src/components/menu/MenuBar.js +++ b/ui-react/src/components/menu/MenuBar.js @@ -94,6 +94,9 @@ export default class MenuBar extends React.Component { <NavDropdown.Item as={StyledLink} to="/uploadToscaPolicyModal">Upload Tosca Model</NavDropdown.Item> <NavDropdown.Item as={StyledLink} to="/viewToscaPolicyModal">View Tosca Models</NavDropdown.Item> </StyledNavDropdown> + <StyledNavDropdown title="Dictionaries"> + <NavDropdown.Item as={StyledLink} to="/ManageDictionaries">Manage Dictionaries</NavDropdown.Item> + </StyledNavDropdown> <StyledNavDropdown title="Loop Instance"> <NavDropdown.Item as={StyledLink} to="/createLoop">Create</NavDropdown.Item> <NavDropdown.Item as={StyledLink} to="/openLoop">Open</NavDropdown.Item> diff --git a/ui-react/src/components/menu/__snapshots__/MenuBar.test.js.snap b/ui-react/src/components/menu/__snapshots__/MenuBar.test.js.snap index dfc6a568..9070e87e 100644 --- a/ui-react/src/components/menu/__snapshots__/MenuBar.test.js.snap +++ b/ui-react/src/components/menu/__snapshots__/MenuBar.test.js.snap @@ -164,6 +164,61 @@ exports[`Verify MenuBar Test the render method 1`] = ` </DropdownItem> </Styled(NavDropdown)> <Styled(NavDropdown) + title="Dictionaries" + > + <DropdownItem + as={ + Object { + "$$typeof": Symbol(react.forward_ref), + "attrs": Array [], + "componentStyle": ComponentStyle { + "componentId": "sc-bdVaJa", + "isStatic": false, + "rules": Array [ + " + color: ", + [Function], + "; + background-color: ", + [Function], + "; + font-weight: normal; + display: block; + width: 100%; + padding: .25rem 1.5rem; + clear: both; + text-align: inherit; + white-space: nowrap; + border: 0; + :hover { + text-decoration: none; + background-color: ", + [Function], + "; + color: ", + [Function], + "; + } +", + ], + }, + "displayName": "Styled(Link)", + "foldedComponentIds": Array [], + "render": [Function], + "styledComponentId": "sc-bdVaJa", + "target": [Function], + "toString": [Function], + "warnTooManyClasses": [Function], + "withComponent": [Function], + } + } + disabled={false} + to="/ManageDictionaries" + > + Manage Dictionaries + </DropdownItem> + </Styled(NavDropdown)> + <Styled(NavDropdown) title="Loop Instance" > <DropdownItem |