Address Manage Dictionary issues 85/109085/2
authorTed Humphrey <Thomas.Humphrey@att.com>
Fri, 12 Jun 2020 07:17:35 +0000 (03:17 -0400)
committerTed Humphrey <Thomas.Humphrey@att.com>
Fri, 12 Jun 2020 20:06:39 +0000 (16:06 -0400)
A variety of bugs are addressed. See JIRA.

Issue-ID: CLAMP-849
Change-Id: I620c1ec774fdcec484f40fc638551960a98973c8
Signed-off-by: Ted Humphrey <Thomas.Humphrey@att.com>
ui-react-lib/libIndex.js
ui-react/src/components/dialogs/ManageDictionaries/ManageDictionaries.js
ui-react/src/components/dialogs/ManageDictionaries/ManageDictionaries.test.js
ui-react/src/components/dialogs/ManageDictionaries/__snapshots__/ManageDictionaries.test.js.snap
ui-react/src/utils/CsvToJson.js [new file with mode: 0644]
ui-react/src/utils/CsvToJson.test.js [new file with mode: 0644]

index 16728ef..f090b61 100755 (executable)
@@ -21,6 +21,7 @@
  *
  */
 
+export { default as CsvToJson } from './src/utils/CsvToJson';
 export { default as CreateLoopModal } from './src/components/dialogs/Loop/CreateLoopModal';
 export { default as DeployLoopModal } from './src/components/dialogs/Loop/DeployLoopModal';
 export { default as LoopActionService } from './src/api/LoopActionService';
index 54ac641..58cb9c6 100644 (file)
 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 Grid from '@material-ui/core/Grid';
 import AddBox from '@material-ui/icons/AddBox';
 import ArrowUpward from '@material-ui/icons/ArrowUpward';
 import Check from '@material-ui/icons/Check';
@@ -49,19 +51,37 @@ 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'};
-var dictList = [];
+let 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++) {
+               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('|');
@@ -87,15 +107,16 @@ function SubDict(props) {
        const {onChange} = props;
        const subDicts = [];
        subDicts.push('Default');
-       if (dictList != "undefined"  && dictList.length > 0) {
-        for(var item in dictList) {
+       if (dictList !== undefined  && dictList.length > 0) {
+       let item;
+        for(item in dictList) {
             if(dictList[item].secondLevelDictionary === 1) {
                 subDicts.push(dictList[item].name);
             }
         };
        }
        subDicts.push('');
-       var optionItems = subDicts.map(
+       let optionItems = subDicts.map(
                (item) => <option key={item}>{item}</option>
          );
        function selectedValue (e) {
@@ -112,46 +133,45 @@ 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.getDictionaries = this.getDictionaries.bind(this);
+               this.getDictionaryElements = this.getDictionaryElements.bind(this);
+               this.addReplaceDictionaryRequest = this.addReplaceDictionaryRequest.bind(this);
+               this.deleteDictionaryRequest = this.deleteDictionaryRequest.bind(this);
+               this.updateDictionaryElementsRequest = this.updateDictionaryElementsRequest.bind(this);
+               this.addDictionaryRow = this.addDictionaryRow.bind(this);
+               this.updateDictionaryRow = this.updateDictionaryRow.bind(this);
+               this.deleteDictionaryRow = this.deleteDictionaryRow.bind(this);
+               this.addDictionaryElementRow = this.addDictionaryElementRow.bind(this);
+               this.deleteDictionaryElementRow = this.deleteDictionaryElementRow.bind(this);
+               this.updateDictionaryElementRow = this.updateDictionaryElementRow.bind(this);
                this.fileSelectedHandler = this.fileSelectedHandler.bind(this);
                this.state = {
                        show: true,
                        selectedFile: '',
-                       dictNameFlag: false,
+                       currentSelectedDictionary: null,
                        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} />)
-      },
+                       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',
@@ -185,7 +205,7 @@ export default class ManageDictionaries extends React.Component {
                                        cellStyle: cellStyle,
                                        headerStyle: headerStyle
                                },
-        {
+                               {
                                        title: "Element Name", field: "name",
                                        cellStyle: cellStyle,
                                        headerStyle: headerStyle
@@ -194,8 +214,8 @@ export default class ManageDictionaries extends React.Component {
                                        title: "Element Description", field: "description",
                                        cellStyle: cellStyle,
                                        headerStyle: headerStyle
-                                },
-                                {
+                               },
+                               {
                                        title: "Element Type", field: "type",
                                        editComponent: props => (
                                                <div>
@@ -204,8 +224,8 @@ export default class ManageDictionaries extends React.Component {
                                        ),
                                        cellStyle: cellStyle,
                                        headerStyle: headerStyle
-                                },
-                                {
+                               },
+                               {
                                    title: "Sub-Dictionary", field: "subDictionary",
                                      editComponent: props => (
                                                 <div>
@@ -214,8 +234,8 @@ export default class ManageDictionaries extends React.Component {
                                      ),
                                    cellStyle: cellStyle,
                                    headerStyle: headerStyle
-                                },
-                               {   
+                               },
+                               {
                                        title: "Updated By", field: "updatedBy", editable: 'never',
                                        cellStyle: cellStyle,
                                        headerStyle: headerStyle
@@ -229,325 +249,325 @@ export default class ManageDictionaries extends React.Component {
                }
        }
 
-       componentWillMount() {
-        this.getDictionary();
-    }
+       componentDidMount() {
+               this.getDictionaries();
+       }
 
-    getDictionary() {
-        TemplateMenuService.getDictionary().then(dictionaryNames => {
-            this.setState({ dictionaryNames: dictionaryNames })
-        });
-    }
+       getDictionaries() {
+               TemplateMenuService.getDictionary().then(arrayOfdictionaries => {
+                       this.setState({ dictionaries: arrayOfdictionaries, currentSelectedDictionary: null })
+               });
+       }
 
-    getDictionaryElements(dictionaryName) {
-        TemplateMenuService.getDictionaryElements(dictionaryName).then(dictionaryElements => {
-            dictList = this.state.dictionaryNames;
-            this.setState({ dictionaryElements: dictionaryElements.dictionaryElements});
-        });
-    }
+       getDictionaryElements(dictionaryName) {
+               TemplateMenuService.getDictionaryElements(dictionaryName).then(dictionaryElements => {
+                       dictList = this.state.dictionaries;
+                       this.setState({ dictionaryElements: dictionaryElements.dictionaryElements} );
+               });
+       }
 
-    clickHandler(rowData)   {
-        this.setState({
-            dictNameFlag: false,
-            addDict: false,
-    });
-    }
+       clickHandler(rowData) {
+               this.getDictionaries();
+       }
 
-    handleClose() {
-        this.setState({ show: false });
-        this.props.history.push('/');
-    }
+       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 => {
-            });
-        }
-    }
+       addReplaceDictionaryRequest(dictionaryEntry) {
+               TemplateMenuService.insDictionary(dictionaryEntry)
+               .then(resp => {})
+               .then(() => {this.getDictionaries()});
+       }
 
-    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 => {
-            });
-        }
-    }
+       updateDictionaryElementsRequest(dictElements) {
+               let reqData = { "name": this.state.currentSelectedDictionary, 'dictionaryElements': dictElements };
+               TemplateMenuService.insDictionaryElements(reqData)
+               .then(resp => {})
+               .then(() => { this.getDictionaryElements(this.state.currentSelectedDictionary) });
+       }
 
-    fileSelectedHandler = (event) => {
-        const text = this;
-        var dictionaryElements = [];
-        if (event.target.files[0].type === 'text/csv' ) {
-            if (event.target.files && event.target.files[0]) {
-                const 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);
-                                }
+       deleteDictionaryRequest(dictionaryName) {
+               TemplateMenuService.deleteDictionary(dictionaryName)
+               .then(resp => { this.getDictionaries() });
+       }
+
+       deleteDictionaryElementRequest(dictionaryName, elemenetShortName) {
+               TemplateMenuService.deleteDictionaryElements({ 'name': dictionaryName, 'shortName': elemenetShortName })
+               .then(resp => {
+                       this.getDictionaryElements(dictionaryName);
+               });
+       }
+
+       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) => {
+
+                               const jsonKeyNames = [ 'shortName', 'name', 'description', 'type', 'subDictionary' ];
+                               const userHeaderNames = [ 'Element Short Name', 'Element Name', 'Element Description', 'Element Type', 'Sub-Dictionary'  ];
+                               const mandatory = [ true, true, true, true, false ];
+                               const validTypes = ['string','number','datetime','json','map'];
+
+                               let result = CsvToJson(reader.result, ',', '||||', 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 !== '') {
+                                       alert(errorMessages);
+                                       return;
+                               }
+
+                               // 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;
                     }
-                    const headerKeys = ['shortName','name','description','type','subDictionary'];
+                                       if (errorMessages) {
+                                               alert(errorMessages);
+                                               return;
+                                       }
 
-                    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.');
-        }
+                                       // We made it through all the checks. Send it to back end
+                                       this.updateDictionaryElementsRequest(jsonObjArray);
+                               }
+                               reader.readAsText(event.target.files[0]);
+                       }
+                       this.setState({selectedFile: event.target.files[0]})
+               } else {
+                       alert('Please upload .csv extention files only.');
+               }
+       }
 
-    }
-   
-    render() {
-        return (
-            <ModalStyled size="xl" show={this.state.show} onHide={this.handleClose} backdrop="static" keyboard={false} >
-                <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(() => {
-                                    {
-                                        const 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)
-                })
-                        }}
-                        />:""
+       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(oldData, newData) {
+               let validData = true;
+               return new Promise((resolve) => {
+                       setTimeout(() => {
+                               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) {
+                                       this.addReplaceDictionaryRequest(newData);
+                               }
+                               resolve();
+                       }, 1000);
+               });
+       }
+
+       deleteDictionaryRow(oldData) {
+               return new Promise((resolve) => {
+                       setTimeout(() => {
+                               this.deleteDictionaryRequest(oldData.name);
+                               resolve();
+                       }, 1000);
+               });
+       }
+
+       addDictionaryElementRow(newData) {
+               return new Promise((resolve, reject) => {
+                       setTimeout(() => {
+                               let dictionaryElements = this.state.dictionaryElements;
+                               let errorMessage = '';
+                               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(() => {});
+                                       }
+                               }
+                               if (newData.shortName !== '' && /[^a-zA-Z0-9-_.]/.test(newData.shortName)) {
+                                       errorMessage += '\nShort Name is limited to alphanumeric characters and also period, hyphen, and underscore';
+                               }
+                               if (!newData.shortName){
+                                       errorMessage += '\nShort Name must be specified';
+                               }
+                               if (!newData.name){
+                                       errorMessage += '\nElement Name must be specified';
+                               }
+                               if (!newData.type){
+                                       errorMessage += '\nElement Type must be specified';
+                               }
+                               if (!newData.description){
+                                       errorMessage += '\nElement Description must be specified';
+                               }
+                               if (errorMessage === '') {
+                                       dictionaryElements.push(newData);
+                                       this.updateDictionaryElementsRequest(dictionaryElements);
+                                       resolve();
+                               } else {
+                                       alert(errorMessage);
+                                       reject(() => {});
+                               }
+                       }, 1000);
+               });
+       }
+
+       updateDictionaryElementRow(newData, oldData) {
+               return new Promise((resolve) => {
+                       setTimeout(() => {
+                               let dictionaryElements = this.state.dictionaryElements;
+                               let validData =  true;
+                               if (!newData.type) {
+                                       validData = false;
+                                       alert('Element Type cannot be null');
+                               }
+                               if (validData) {
+                                       const index = dictionaryElements.indexOf(oldData);
+                                       dictionaryElements[index] = newData;
+                                       this.updateDictionaryElementsRequest(dictionaryElements);
+                               }
+                               resolve();
+                       }, 1000);
+               });
+       }
+
+
+       deleteDictionaryElementRow(oldData) {
+               return new Promise((resolve) => {
+                       setTimeout(() => {
+                               this.deleteDictionaryElementRequest(this.state.currentSelectedDictionary, oldData.shortName);
+                               resolve();
+                       }, 1000);
+               });
+       }
+
+       render() {
+               return (
+                       <ModalStyled size="xl" show={this.state.show} onHide={this.handleClose} backdrop="static" keyboard={false} >
+                               <Modal.Header closeButton>
+                                       <Modal.Title>Manage Dictionaries</Modal.Title>
+                               </Modal.Header>
+                               <Modal.Body>
+                                       {this.state.currentSelectedDictionary === null ? <MaterialTable
+                               title={"Dictionary List"}
+                               data={this.state.dictionaries}
+                               columns={this.state.dictColumns}
+                               icons={this.state.tableIcons}
+                               onRowClick={(event, rowData) => {
+                                                               this.setState({
+                                                                       currentSelectedDictionary : rowData.name,
+                                                                       exportFilename: rowData.name
+                                                               })
+                                                               this.getDictionaryElements(rowData.name);
+                                                       }}
+                               options={{
+                               headerStyle: rowHeaderStyle,
+                               }}
+                               editable={{
+                               onRowAdd: this.addDictionaryRow,
+                               onRowUpdate: this.updateDictionaryRow,
+                               onRowDelete: this.deleteDictionaryRow
+                                                       }}
+                                               /> : null
                     }
-                    {this.state.dictNameFlag? <MaterialTable
-                        title={"Dictionary Elements List"}
+                    {this.state.currentSelectedDictionary !== null ? <MaterialTable
+                        title={'Dictionary Elements List for "' + this.state.currentSelectedDictionary + '"'}
                         data={this.state.dictionaryElements}
                         columns={this.state.dictElementColumns}
                         icons={this.state.tableIcons}
                         options={{
+                                                       exportAllData: true,
                             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">
+                                                               <Row>
+                                                                       <Col sm="11">
+                                       <MTableToolbarStyled {...props} />
+                                                                       </Col>
+                                                                       <ColPullLeftStyled sm="1">
                                         <Tooltip title="Import" placement = "bottom">
-                                            <IconButton aria-label="import" onClick={() => this.fileUpload.click()}>
-                                                <VerticalAlignTopIcon />
-                                            </IconButton>
+                                        <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>
+                                               <input type="file" ref={(fileUpload) => {this.fileUpload = fileUpload;}}
+                                                                                       style={{ visibility: 'hidden', width: '1px' }} onChange={this.fileSelectedHandler} />
+                                                                       </ColPullLeftStyled>
+                                </Row>
                             )
                         }}
                         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)
-                })
+                            onRowAdd: this.addDictionaryElementRow,
+                            onRowUpdate: this.updateDictionaryElementRow,
+                            onRowDelete: this.deleteDictionaryElementRow
                         }}
-                        />:""
+                        /> : null
                     }
-                    {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()}
+                    {this.state.currentSelectedDictionary !== null ? <button onClick={this.clickHandler} style={{marginTop: '25px'}}>Go Back to Dictionaries List</button>:""}
                 </Modal.Body>
                 <Modal.Footer>
                     <Button variant="secondary" type="null" onClick={this.handleClose}>Close</Button>
@@ -555,4 +575,4 @@ export default class ManageDictionaries extends React.Component {
             </ModalStyled>
         );
     }
-}                                      
+}
index 13a6035..d1d4aa6 100644 (file)
@@ -60,10 +60,10 @@ describe('Verify ManageDictionaries', () => {
                                json: () => {
                                        return Promise.resolve({
                                                "name": "vtest",
-            "secondLevelDictionary": "1",
+                                               "secondLevelDictionary": "1",
                                                "subDictionaryType": "string",
-                       "updatedBy": "test",
-            "updatedDate": "05-07-2019 19:09:42"
+                                               "updatedBy": "test",
+                                               "updatedDate": "05-07-2019 19:09:42"
                                        });
                                }
                        });
@@ -90,10 +90,10 @@ describe('Verify ManageDictionaries', () => {
                                json: () => {
                                        return Promise.resolve({
                                                "name": "vtest",
-                 "secondLevelDictionary": "1",
+                                               "secondLevelDictionary": "1",
                                                "subDictionaryType": "string",
-                       "updatedBy": "test",
-                 "updatedDate": "05-07-2019 19:09:42"
+                                               "updatedBy": "test",
+                                               "updatedDate": "05-07-2019 19:09:42"
                                        });
                                }
                        });
@@ -103,12 +103,33 @@ describe('Verify ManageDictionaries', () => {
        });
 
        test('Test get dictionaryNames/dictionaryElements, add/delete dictionary functions', async () => {
+               const dictionaries = [
+                       {
+                               name: "DefaultActors",
+                               secondLevelDictionary: 0,
+                               subDictionaryType: "",
+                               dictionaryElements: [
+                                       {
+                                               "shortName": "SDNR",
+                                               "name": "SDNR Change",
+                                               "description": "SDNR component",
+                                               "type": "string",
+                                               "createdDate": "2020-06-07T18:57:18.130858Z",
+                                               "updatedDate": "2020-06-11T13:10:52.239282Z",
+                                               "updatedBy": "admin"
+                                       }
+                               ],
+                               createdDate: "2020-06-07T22:21:08.428742Z",
+                               updatedDate: "2020-06-10T00:41:49.122908Z",
+                               updatedBy: "Not found"
+                       }
+               ];
                const historyMock = { push: jest.fn() };
                TemplateMenuService.getDictionary = jest.fn().mockImplementation(() => {
-                       return Promise.resolve("test");
+                       return Promise.resolve(dictionaries);
                });
                TemplateMenuService.getDictionaryElements = jest.fn().mockImplementation(() => {
-                       return Promise.resolve({dictionaryElements:"testitem"});
+                       return Promise.resolve(dictionaries[0]);
                });
                TemplateMenuService.insDictionary = jest.fn().mockImplementation(() => {
                        return Promise.resolve(200);
@@ -118,33 +139,40 @@ describe('Verify ManageDictionaries', () => {
                });
                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.getDictionaryElements("DefaultActors");
                instance.clickHandler();
-               instance.addDictionary();
-               instance.deleteDictionary();
+               instance.addReplaceDictionaryRequest();
+               instance.deleteDictionaryRequest();
                await flushPromises();
-               expect(component.state('dictionaryNames')).toEqual("test");
-               expect(component.state('dictionaryElements')).toEqual("testitem");
-               expect(component.state('dictNameFlag')).toEqual(false);
+               expect(component.state('dictionaries')).toEqual(dictionaries);
        });
 
        test('Test adding and deleting dictionaryelements', async () => {
                const historyMock = { push: jest.fn() };
+               const dictionaries = [
+                       {
+                               name: "DefaultActors",
+                               secondLevelDictionary: 0,
+                               subDictionaryType: "",
+                               dictionaryElements: [
+                                       {
+                                               "shortName": "SDNR",
+                                               "name": "SDNR Change",
+                                               "description": "SDNR component",
+                                               "type": "string",
+                                               "createdDate": "2020-06-07T18:57:18.130858Z",
+                                               "updatedDate": "2020-06-11T13:10:52.239282Z",
+                                               "updatedBy": "admin"
+                                       }
+                               ],
+                               createdDate: "2020-06-07T22:21:08.428742Z",
+                               updatedDate: "2020-06-10T00:41:49.122908Z",
+                               updatedBy: "Not found"
+                       }
+               ];
                TemplateMenuService.getDictionary = jest.fn().mockImplementation(() => {
-                       return Promise.resolve("test");
+                       return Promise.resolve(dictionaries);
                });
                TemplateMenuService.insDictionaryElements = jest.fn().mockImplementation(() => {
                        return Promise.resolve(200);
@@ -154,23 +182,11 @@ describe('Verify ManageDictionaries', () => {
                });
                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();
+               instance.addReplaceDictionaryRequest({ name: "EventDictionary", secondLevelDictionary: "0", subDictionaryType: "string"} );
+               instance.deleteDictionaryRequest('EventDictionary');
                await flushPromises();
-               expect(component.state('dictionaryNames')).toEqual("test");
+               expect(component.state('currentSelectedDictionary')).toEqual(null);
        });
 
        it('Test handleClose', () => {
@@ -181,10 +197,10 @@ describe('Verify ManageDictionaries', () => {
                                json: () => {
                                        return Promise.resolve({
                                                "name": "vtest",
-                       "secondLevelDictionary": "1",
+                                               "secondLevelDictionary": "1",
                                                "subDictionaryType": "string",
-                       "updatedBy": "test",
-                       "updatedDate": "05-07-2019 19:09:42"
+                                               "updatedBy": "test",
+                                               "updatedDate": "05-07-2019 19:09:42"
                                        });
                                }
                        });
index 71cc393..40914ae 100644 (file)
@@ -88,7 +88,6 @@ exports[`Verify ManageDictionaries Test API Successful 1`] = `
           },
         ]
       }
-      data={Array []}
       editable={
         Object {
           "onRowAdd": [Function],
diff --git a/ui-react/src/utils/CsvToJson.js b/ui-react/src/utils/CsvToJson.js
new file mode 100644 (file)
index 0000000..5ec19c9
--- /dev/null
@@ -0,0 +1,204 @@
+/*-
+ * ============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============================================
+ * ===================================================================
+ *
+ */
+
+export default function CsvToJson(rawCsvData, delimiter, internalDelimiter, csvHeaderNames, jsonKeyNames, mandatory) {
+
+       let printDictKeys = '';
+       let result = { jsonObjArray: [], errorMessages: '' };
+
+       // Validate that all parallel arrays passed in have same number of elements;
+       // this would be a developer error.
+
+       let checkLength = csvHeaderNames.length;
+
+       if (checkLength !== jsonKeyNames.length || checkLength !== mandatory.length) {
+               result.errorMessages = 'interanl error: csvHeaderNames, jsonKeyNames, and mandatory arrays parameters are not the same length';
+               return result;
+       }
+
+       if (checkLength < 1) {
+               result.errorMessages = 'interanl error: csvHeaderNames, jsonKeyNames, and mandatory arrays have no entries';
+               return result;
+       }
+
+       // Make a nice string to print in the error case to tell user what is the
+       //  required heaer row format
+
+       for (let i=0; i < csvHeaderNames.length; ++i) {
+               if (i === 0) {
+                       printDictKeys = csvHeaderNames[i];
+               } else {
+                       printDictKeys += ',' +  csvHeaderNames[i];
+               }
+       }
+
+       let dictElems = rawCsvData.split('\n');
+       let numColumns = 0;
+       let filteredDictElems = [];
+
+       // The task of the following loop is to convert raw CSV rows into easily parseable
+       // and streamlined versions of the rows with an internalDelimiter replacing the standard
+       // comma; it is presumed (and checked) that the internalDelimiter cannot exist as a valid
+       // sequence of characters in the user's data.
+
+       // This conversion process also strips leading and trailing whitespace from each row,
+       // discards empty rows, correctly interprets and removes all double quotes that programs like
+       // Excel use to support user columns that contain special characters, most notably, the comma
+       // delimiter. A double-quote that is contained within a double-quoted column value 
+       // must appear in this raw data as a sequence of two double quotes. Furthermore, any column
+       // value in the raw CSV data that does not contain a delimiter may or may not be enclosed in
+       // double quotes. It is the Excel convention to not use double qoutes unless necessary, and
+       // there is no reasonable way to tell Excel to surround every column value with double quotes. 
+       // Any files that were directly "exported" by CLAMP itself from the Managing Dictionaries
+       // capability, surround all columns with double quotes.
+
+       for (let i = 0; i < dictElems.length; i++) {
+
+               let oneRow = dictElems[i].trim();
+               let j = 0;
+               let inQuote = false
+               let nextChar = undefined;
+               let prevChar = null;
+
+               
+               if (oneRow === '') {
+                       continue; // Skip blank rows
+               } else if (oneRow.indexOf(internalDelimiter) !== -1) {
+                       result.errorMessages += '\nRow #' + i + ' contains illegal sequence of characters (' + internalDelimiter + ')';
+                       break;
+               } else {
+                       nextChar = oneRow[1];
+               }
+
+               let newStr = '';
+               numColumns = 1;
+
+               // This "while loop" performs the very meticulous task of removing double quotes that
+               // are used by Excel to encase special characters as user string value data,
+               // and manages to correctly identify columns that are defined with or without
+               // double quotes and to process the comma delimiter correctly when encountered
+               // as a user value within a column. Such a column would have to be encased in
+               // double quotes; a comma found outside double quotes IS a delimiter.
+
+               while (j < oneRow.length) {
+                       if (oneRow[j] === '"') {
+                               if (inQuote === false) {
+                                       if (prevChar !== delimiter && prevChar !== null) {
+                                               result.errorMessages += '\nMismatched double quotes or illegal whitespace around delimiter at row #' + (i + 1) + ' near column #' + numColumns;
+                                               break;
+                                       } else {
+                                               inQuote = true;
+                                       }
+                               } else {
+                                       if (nextChar === '"') {
+                                               newStr += '"';
+                                               ++j;
+                                       } else if ((nextChar !== delimiter) && (nextChar !== undefined)) {
+                                               result.errorMessages += '\nRow #' + (i + 1) + ' is badly formatted at column #' + numColumns + '. Perhaps an unescaped double quote.';
+                                               break;
+                                       } else if (nextChar === delimiter) {
+                                               ++numColumns;
+                                               inQuote = false;
+                                               newStr += internalDelimiter;
+                                               prevChar = delimiter;
+                                               j += 2;
+                                               nextChar = oneRow[j+1];
+                                               continue;
+                                       } else {
+                                               ++numColumns;
+                                               inQuote = false;
+                                               break;
+                                       }
+                               }
+                       } else {
+                               if (oneRow[j] === delimiter && inQuote === false) {
+                                       newStr += internalDelimiter;
+                                       ++numColumns;
+                               } else {
+                                       newStr += oneRow[j];
+                               }
+                       }
+                       prevChar = oneRow[j];
+                       ++j;
+                       nextChar = oneRow[j+1]; // can result in undefined at the end
+               }
+
+               if (result.errorMessages === '' && inQuote !== false) {
+                       result.errorMessages += '\nMismatched double quotes at row #' + (i + 1);
+                       break;
+               } else if (result.errorMessages === '' && numColumns < jsonKeyNames.length) {
+                       result.errorMessages += '\nNot enough columns (' + jsonKeyNames.length + ') at row #' + (i + 1);
+                       break;
+               }
+
+               filteredDictElems.push(newStr);
+       }
+
+       if (result.errorMessages !== '') {
+               return result;
+       }
+
+       // Perform further checks on data that is now in JSON form
+       if (filteredDictElems.length < 2) {
+               result.errorMessages += '\nNot enough row data found in import file. Need at least a header row and one row of data';
+               return result;
+       }
+
+       // Now that we have something reliably parsed into sanitized columns lets run some checks
+       // and convert it all into an array of JSON objects to push to the back end if all the
+       // checks pass.
+
+       let headers = filteredDictElems[0].split(internalDelimiter);
+
+       // check that headers are included in proper order
+       for (let i=0; i < jsonKeyNames.length; ++i) {
+               if (csvHeaderNames[i] !== headers[i]) {
+                       result.errorMessages += 'Row 1 header key at column #' + (i + 1) + ' is a mismatch. Expected row header must contain at least:\n' + printDictKeys; 
+                       return result;
+               }
+       }
+
+       // Convert the ASCII rows of data into an array of JSON obects that omit the header
+       // row which is not sent to the back end.
+
+       for (let i = 1; i < filteredDictElems.length; i++) {
+               let data = filteredDictElems[i].split(internalDelimiter);
+               let obj = {};
+               for (let j = 0; j < data.length && j < jsonKeyNames.length; j++) {
+                       let value = data[j].trim();
+                       if (mandatory[j] === true && value === '') {
+                               result.errorMessages += '\n' + csvHeaderNames[j] + ' at row #' + (i+1) + ' is empty but requires a value.';
+                       }
+                       obj[jsonKeyNames[j]] = value;
+               }
+               result.jsonObjArray.push(obj);
+       }
+
+       if (result.errorMessages !== '') {
+               // If we have errors, return empty parse result even though some things
+               // may have parsed properly. We do not want to encourage the caller
+               // to think the data is good for use.
+               result.jsonObjArray = [];
+       }
+
+       return result;
+}
diff --git a/ui-react/src/utils/CsvToJson.test.js b/ui-react/src/utils/CsvToJson.test.js
new file mode 100644 (file)
index 0000000..88fa7a4
--- /dev/null
@@ -0,0 +1,268 @@
+/*-
+ * ============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 CsvToJson from './CsvToJson'
+
+describe('Verify CsvToJson', () => {
+
+       const hdrNames= [ 
+               "Element Short Name",
+               "Element Name",
+               "Element Description",
+               "Element Type",
+               "Sub-Dictionary"
+       ];
+
+       const jsonKeyNames = [
+               "shortName",
+               "name",
+               "description",
+               "type",
+               "subDictionary"
+       ];
+
+       const mandatory = [ true, true, true, true, false ];
+
+       it('Test CsvToJson No Error Case, Quoted Columns', () => {
+
+               let rawCsv = '"Element Short Name","Element Name","Element Description","Element Type","Sub-Dictionary"\n';
+               rawCsv += '"alertType","Alert Type","Type of Alert","string","","admin","2020-06-11T13:56:14.927437Z"';
+                               
+               let expectedResult = {
+                        errorMessages: '',
+                        jsonObjArray: [
+                               {
+                                       description: "Type of Alert",
+                                       name: "Alert Type",
+                                       shortName: "alertType",
+                                       subDictionary: "",
+                                       type: "string"
+                               }
+                       ]
+               };
+
+               expect(CsvToJson(rawCsv, ',', '|', hdrNames, jsonKeyNames, mandatory)).toEqual(expectedResult);
+       });
+
+       it('Test CsvToJson No Error Case, Unquoted Columns', () => {
+
+               let rawCsv = 'Element Short Name,Element Name,Element Description,Element Type,Sub-Dictionary\n';
+               rawCsv += 'alertType,Alert Type,Type of Alert,string,,admin,2020-06-11T13:56:14.927437Z';
+                               
+               let expectedResult = {
+                        errorMessages: '',
+                        jsonObjArray: [
+                               {
+                                       description: "Type of Alert",
+                                       name: "Alert Type",
+                                       shortName: "alertType",
+                                       subDictionary: "",
+                                       type: "string"
+                               }
+                       ]
+               };
+
+               expect(CsvToJson(rawCsv, ',', '|', hdrNames, jsonKeyNames, mandatory)).toEqual(expectedResult);
+       });
+
+       it('Test CsvToJson Properly Escaped Double Quote and Delimiter', () => {
+
+               let rawCsv = '"Element Short Name","Element Name","Element Description","Element Type","Sub-Dictionary"\n';
+               rawCsv += '"alertType","Alert ""Type""","Type of Alert, Varies","string","","admin","2020-06-11T13:56:14.927437Z"';
+               
+               let errorMessage = '';
+                               
+               let expectedResult = {
+                       errorMessages: errorMessage,
+                       jsonObjArray: [
+                               {
+                                       description: "Type of Alert, Varies",
+                                       name: 'Alert "Type"',
+                                       shortName: 'alertType',
+                                       subDictionary: "",
+                                       type: "string",
+                               }
+
+                       ]
+               };
+
+               expect(CsvToJson(rawCsv, ',', '|', hdrNames, jsonKeyNames, mandatory)).toEqual(expectedResult);
+       });
+
+
+       it('Test CsvToJson Error Header Mismatch Error Case', () => {
+
+               let rawCsv = '"Element Short Names","Element Name","Element Description","Element Type","Sub-Dictionary"\n';
+               rawCsv += '"alertType","Alert Type","Type of Alert","string","","admin","2020-06-11T13:56:14.927437Z"';
+               
+               let errorMessage = 'Row 1 header key at column #1 is a mismatch. Expected row header must contain at least:\n';
+               errorMessage += 'Element Short Name,Element Name,Element Description,Element Type,Sub-Dictionary';
+                               
+               let expectedResult = {
+                        errorMessages: errorMessage,
+                        jsonObjArray: []
+               };
+
+               expect(CsvToJson(rawCsv, ',', '|', hdrNames, jsonKeyNames, mandatory)).toEqual(expectedResult);
+       });
+
+       it('Test CsvToJson Error Mismatched Double Quotes in Column', () => {
+
+               let rawCsv = '"Element Short Name","Element Name","Element Description","Element Type","Sub-Dictionary"\n';
+               rawCsv += '"alert"Type","Alert Type","Type of Alert","string","","admin","2020-06-11T13:56:14.927437Z"';
+               
+               let errorMessage = '\nRow #2 is badly formatted at column #1. Perhaps an unescaped double quote.'
+                               
+               let expectedResult = {
+                        errorMessages: errorMessage,
+                        jsonObjArray: []
+               };
+
+               expect(CsvToJson(rawCsv, ',', '|', hdrNames, jsonKeyNames, mandatory)).toEqual(expectedResult);
+       });
+
+       it('Test CsvToJson Error Illegal Whitespace', () => {
+
+               let rawCsv = '"Element Short Name","Element Name","Element Description","Element Type","Sub-Dictionary"\n';
+               rawCsv += 'alertType ,  "Alert Type","Type of Alert","string","","admin","2020-06-11T13:56:14.927437Z"';
+               
+               let errorMessage = '\nMismatched double quotes or illegal whitespace around delimiter at row #2 near column #2';
+                               
+               let expectedResult = {
+                        errorMessages: errorMessage,
+                        jsonObjArray: []
+               };
+
+               expect(CsvToJson(rawCsv, ',', '|', hdrNames, jsonKeyNames, mandatory)).toEqual(expectedResult);
+       });
+
+       it('Test CsvToJson Error Too Few Data Columns', () => {
+
+               let rawCsv = '"Element Short Name","Element Name","Element Description","Element Type","Sub-Dictionary"\n';
+               rawCsv += '"alertType","Alert Type","Type of Alert"';
+               
+               let errorMessage = '\nNot enough columns (5) at row #2';
+                               
+               let expectedResult = {
+                       errorMessages: errorMessage,
+                       jsonObjArray: []
+               };
+
+               expect(CsvToJson(rawCsv, ',', '|', hdrNames, jsonKeyNames, mandatory)).toEqual(expectedResult);
+       });
+
+       it('Test CsvToJson Error Wrong Header Column Order', () => {
+
+               let rawCsv = '"Element Name","Element Short Name","Element Description","Element Type","Sub-Dictionary"\n';
+               rawCsv += '"alertType","Alert Type","Type of Alert","string","","admin","2020-06-11T13:56:14.927437Z"';
+               
+               let errorMessage = 'Row 1 header key at column #1 is a mismatch. Expected row header must contain at least:\n';
+               errorMessage += 'Element Short Name,Element Name,Element Description,Element Type,Sub-Dictionary';
+                               
+               let expectedResult = {
+                       errorMessages: errorMessage,
+                       jsonObjArray: []
+               };
+
+               expect(CsvToJson(rawCsv, ',', '|', hdrNames, jsonKeyNames, mandatory)).toEqual(expectedResult);
+       });
+
+       it('Test CsvToJson Error Not Enough Rows', () => {
+
+               let rawCsv = '"Element Short Name","Element Name","Element Description","Element Type","Sub-Dictionary"\n';
+               
+               let errorMessage = '\nNot enough row data found in import file. Need at least a header row and one row of data';
+                               
+               let expectedResult = {
+                       errorMessages: errorMessage,
+                       jsonObjArray: []
+               };
+
+               expect(CsvToJson(rawCsv, ',', '|', hdrNames, jsonKeyNames, mandatory)).toEqual(expectedResult);
+       });
+
+       it('Test CsvToJson Error Mandatory Field Is Empty', () => {
+
+               let rawCsv = '"Element Short Name","Element Name","Element Description","Element Type","Sub-Dictionary"\n';
+               rawCsv += '"","Alert Type","Type of Alert","string","","admin","2020-06-11T13:56:14.927437Z"';
+                               
+               let expectedResult = {
+                        errorMessages: '\nElement Short Name at row #2 is empty but requires a value.',
+                        jsonObjArray: []
+               };
+
+               expect(CsvToJson(rawCsv, ',', '|', hdrNames, jsonKeyNames, mandatory)).toEqual(expectedResult);
+       });
+
+       it('Test CsvToJson Error Mismatched Double Quotes At End', () => {
+
+               let rawCsv = '"Element Short Name","Element Name","Element Description","Element Type","Sub-Dictionary"\n';
+               rawCsv += '"alertType","Alert Type","Alert Type Description","string","admin","2020-06-11T13:56:14.927437Z';
+                               
+               let expectedResult = {
+                        errorMessages: '\nMismatched double quotes at row #2',
+                        jsonObjArray: []
+               };
+
+               expect(CsvToJson(rawCsv, ',', '||', hdrNames, jsonKeyNames, mandatory)).toEqual(expectedResult);
+       });
+
+       it('Test CsvToJson Error Mismatched Mandatory Array Parameters', () => {
+
+               let rawCsv = '"Element Short Name","Element Name","Element Description","Element Type","Sub-Dictionary"\n';
+               rawCsv += '"alertType","Alert Type","Alert Type Description","string","admin","2020-06-11T13:56:14.927437Z';
+                               
+               let expectedResult = {
+                        errorMessages: 'interanl error: csvHeaderNames, jsonKeyNames, and mandatory arrays parameters are not the same length',
+                        jsonObjArray: []
+               };
+
+               expect(CsvToJson(rawCsv, ',', '||', hdrNames, jsonKeyNames, [ true ])).toEqual(expectedResult);
+       });
+
+       it('Test CsvToJson Error Empty Mandatory Array Parameters', () => {
+
+               let rawCsv = '"Element Short Name","Element Name","Element Description","Element Type","Sub-Dictionary"\n';
+               rawCsv += '"alertType","Alert Type","Alert Type Description","string","admin","2020-06-11T13:56:14.927437Z';
+                               
+               let expectedResult = {
+                        errorMessages: 'interanl error: csvHeaderNames, jsonKeyNames, and mandatory arrays have no entries',
+                        jsonObjArray: []
+               };
+
+               expect(CsvToJson(rawCsv, ',', '||', [], [], [])).toEqual(expectedResult);
+       });
+
+       it('Test CsvToJson Error Illegal Data Contains Internal Delimiter', () => {
+
+               let rawCsv = '"Element Short Name","Element Name","Element Description","Element Type","Sub-Dictionary"\n';
+               rawCsv += '"alertType","Alert Type","Alert Type||Description","string","admin","2020-06-11T13:56:14.927437Z';
+
+               let expectedResult = {
+                        errorMessages: '\nRow #1 contains illegal sequence of characters (||)',
+                        jsonObjArray: []
+               };
+
+               expect(CsvToJson(rawCsv, ',', '||', hdrNames, jsonKeyNames, mandatory)).toEqual(expectedResult);
+       });
+})