aboutsummaryrefslogtreecommitdiffstats
path: root/ui-react/src
diff options
context:
space:
mode:
authorSébastien Determe <sebastien.determe@intl.att.com>2020-03-09 09:20:33 +0000
committerGerrit Code Review <gerrit@onap.org>2020-03-09 09:20:33 +0000
commit5c6d23852ea949335eb7f85d787eb24ac6dbab2a (patch)
tree96210751458999b8afdc4abe0357f13d8157f1b9 /ui-react/src
parent897a3e004a858ef68d989dad15dde91a69e151a5 (diff)
parent5032095d221292ad2909f0cb9c9c99d3d5111d0c (diff)
Merge "Adding manage dictionary UI feature"
Diffstat (limited to 'ui-react/src')
-rw-r--r--ui-react/src/LoopUI.js2
-rw-r--r--ui-react/src/__snapshots__/LoopUI.test.js.snap4
-rw-r--r--ui-react/src/__snapshots__/OnapClamp.test.js.snap4
-rw-r--r--ui-react/src/api/TemplateService.js173
-rw-r--r--ui-react/src/components/dialogs/ManageDictionaries/ManageDictionaries.js559
-rw-r--r--ui-react/src/components/dialogs/ManageDictionaries/ManageDictionaries.test.js202
-rw-r--r--ui-react/src/components/dialogs/ManageDictionaries/__snapshots__/ManageDictionaries.test.js.snap195
-rw-r--r--ui-react/src/components/menu/MenuBar.js3
-rw-r--r--ui-react/src/components/menu/__snapshots__/MenuBar.test.js.snap55
9 files changed, 1180 insertions, 17 deletions
diff --git a/ui-react/src/LoopUI.js b/ui-react/src/LoopUI.js
index 471e87200..bc3f2355e 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 7d2c4467e..9de232dd6 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 e195523b9..91812b7c5 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 6a65d9a04..124d29c27 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 000000000..189523765
--- /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 000000000..4363da923
--- /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 000000000..e78292216
--- /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 92380ab6c..4eafaa448 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 dfc6a5682..9070e87e9 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