aboutsummaryrefslogtreecommitdiffstats
path: root/ui-react
diff options
context:
space:
mode:
Diffstat (limited to 'ui-react')
-rw-r--r--ui-react/src/LoopUI.js16
-rw-r--r--ui-react/src/LoopUI.test.js9
-rw-r--r--ui-react/src/__snapshots__/LoopUI.test.js.snap17
-rw-r--r--ui-react/src/__snapshots__/OnapClamp.test.js.snap17
-rw-r--r--ui-react/src/api/LoopService.js20
-rw-r--r--ui-react/src/api/UserService.js25
-rw-r--r--ui-react/src/components/dialogs/ConfigurationPolicy/ConfigurationPolicyModal.js127
-rw-r--r--ui-react/src/components/dialogs/ConfigurationPolicy/ConfigurationPolicyModal.test.js78
-rw-r--r--ui-react/src/components/dialogs/ManageDictionaries/ManageDictionaries.js694
-rw-r--r--ui-react/src/components/dialogs/ManageDictionaries/ManageDictionaries.test.js106
-rw-r--r--ui-react/src/components/dialogs/ManageDictionaries/__snapshots__/ManageDictionaries.test.js.snap1
-rw-r--r--ui-react/src/components/dialogs/OperationalPolicy/OperationalPolicyModal.js173
-rw-r--r--ui-react/src/components/dialogs/OperationalPolicy/OperationalPolicyModal.test.js93
-rw-r--r--ui-react/src/components/dialogs/OperationalPolicy/template.json37
-rw-r--r--ui-react/src/utils/CsvToJson.js204
-rw-r--r--ui-react/src/utils/CsvToJson.test.js268
16 files changed, 890 insertions, 995 deletions
diff --git a/ui-react/src/LoopUI.js b/ui-react/src/LoopUI.js
index 6522cc3dd..8624726be 100644
--- a/ui-react/src/LoopUI.js
+++ b/ui-react/src/LoopUI.js
@@ -40,8 +40,6 @@ import { Route } from 'react-router-dom'
import CreateLoopModal from './components/dialogs/Loop/CreateLoopModal';
import OpenLoopModal from './components/dialogs/Loop/OpenLoopModal';
import ModifyLoopModal from './components/dialogs/Loop/ModifyLoopModal';
-import OperationalPolicyModal from './components/dialogs/OperationalPolicy/OperationalPolicyModal';
-import ConfigurationPolicyModal from './components/dialogs/ConfigurationPolicy/ConfigurationPolicyModal';
import PolicyModal from './components/dialogs/Policy/PolicyModal';
import LoopPropertiesModal from './components/dialogs/Loop/LoopPropertiesModal';
import UserInfoModal from './components/dialogs/UserInfoModal';
@@ -116,7 +114,6 @@ export default class LoopUI extends React.Component {
constructor() {
super();
this.getUser = this.getUser.bind(this);
- this.logout = this.logout.bind(this);
this.updateLoopCache = this.updateLoopCache.bind(this);
this.loadLoop = this.loadLoop.bind(this);
this.closeLoop = this.closeLoop.bind(this);
@@ -134,14 +131,6 @@ export default class LoopUI extends React.Component {
this.setState({ userName: user })
});
}
-
- logout() {
- UserService.logout().then(user => {
- this.setState({ userName: user });
- window.location.reload();
- });
-
- }
renderMenuNavBar() {
return (
@@ -154,7 +143,6 @@ export default class LoopUI extends React.Component {
<Navbar.Text>
<StyledLoginInfo>Signed in as: </StyledLoginInfo>
<StyledRouterLink to="/userInfo">{this.state.userName}</StyledRouterLink>
- <StyledRouterLink to="/logout/"> (logout)</StyledRouterLink>
</Navbar.Text>
);
}
@@ -268,10 +256,7 @@ export default class LoopUI extends React.Component {
<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} showSucAlert={this.showSucAlert} showFailAlert={this.showFailAlert}/>)} />
<Route path="/policyModal/:policyInstanceType/:policyName" render={(routeProps) => (<PolicyModal {...routeProps} loopCache={this.getLoopCache()} loadLoopFunction={this.loadLoop}/>)} />
- <Route path="/configurationPolicyModal/:policyName" render={(routeProps) => (<ConfigurationPolicyModal {...routeProps} loopCache={this.getLoopCache()} loadLoopFunction={this.loadLoop}/>)} />
<Route path="/createLoop" render={(routeProps) => (<CreateLoopModal {...routeProps} loadLoopFunction={this.loadLoop} />)} />
<Route path="/openLoop" render={(routeProps) => (<OpenLoopModal {...routeProps} loadLoopFunction={this.loadLoop} />)} />
<Route path="/loopProperties" render={(routeProps) => (<LoopPropertiesModal {...routeProps} loopCache={this.getLoopCache()} loadLoopFunction={this.loadLoop}/>)} />
@@ -286,7 +271,6 @@ export default class LoopUI extends React.Component {
<Route path="/undeploy" render={(routeProps) => (<PerformAction {...routeProps} loopAction="undeploy" loopCache={this.getLoopCache()} updateLoopFunction={this.updateLoopCache} showSucAlert={this.showSucAlert} showFailAlert={this.showFailAlert}/>)} />
<Route path="/deploy" render={(routeProps) => (<DeployLoopModal {...routeProps} loopCache={this.getLoopCache()} updateLoopFunction={this.updateLoopCache} showSucAlert={this.showSucAlert} showFailAlert={this.showFailAlert}/>)} />
<Route path="/refreshStatus" render={(routeProps) => (<RefreshStatus {...routeProps} loopCache={this.getLoopCache()} updateLoopFunction={this.updateLoopCache} showSucAlert={this.showSucAlert} showFailAlert={this.showFailAlert}/>)} />
- <Route path="/logout" render={this.logout} />
<GlobalClampStyle />
{this.renderAlertBar()}
{this.renderNavBar()}
diff --git a/ui-react/src/LoopUI.test.js b/ui-react/src/LoopUI.test.js
index 6885e7932..bfd6376e3 100644
--- a/ui-react/src/LoopUI.test.js
+++ b/ui-react/src/LoopUI.test.js
@@ -83,15 +83,6 @@ describe('Verify LoopUI', () => {
expect(historyMock.push.mock.calls[0]).toEqual([ '/']);
})
- test('Test logout method', async () => {
- const flushPromises = () => new Promise(setImmediate);
- const component = shallow(<LoopUI />)
- const instance = component.instance();
- instance.logout();
- await flushPromises();
- expect(component.state('userName')).toEqual("testUser");
- })
-
test('Test loadLoop method refresh suc', async () => {
const historyMock = { push: jest.fn() };
LoopService.getLoop = jest.fn().mockImplementation(() => {
diff --git a/ui-react/src/__snapshots__/LoopUI.test.js.snap b/ui-react/src/__snapshots__/LoopUI.test.js.snap
index d8b2e7be5..2dfa48091 100644
--- a/ui-react/src/__snapshots__/LoopUI.test.js.snap
+++ b/ui-react/src/__snapshots__/LoopUI.test.js.snap
@@ -21,18 +21,10 @@ exports[`Verify LoopUI Test the render method 1`] = `
render={[Function]}
/>
<Route
- path="/operationalPolicyModal"
- render={[Function]}
- />
- <Route
path="/policyModal/:policyInstanceType/:policyName"
render={[Function]}
/>
<Route
- path="/configurationPolicyModal/:policyName"
- render={[Function]}
- />
- <Route
path="/createLoop"
render={[Function]}
/>
@@ -84,10 +76,6 @@ exports[`Verify LoopUI Test the render method 1`] = `
path="/refreshStatus"
render={[Function]}
/>
- <Route
- path="/logout"
- render={[Function]}
- />
<GlobalStyleComponent />
<div>
<Alert
@@ -165,11 +153,6 @@ exports[`Verify LoopUI Test the render method 1`] = `
>
testUser
</Styled(Link)>
- <Styled(Link)
- to="/logout/"
- >
- (logout)
- </Styled(Link)>
</NavbarText>
</Navbar>
<styled.div>
diff --git a/ui-react/src/__snapshots__/OnapClamp.test.js.snap b/ui-react/src/__snapshots__/OnapClamp.test.js.snap
index 39b544556..56d022fc6 100644
--- a/ui-react/src/__snapshots__/OnapClamp.test.js.snap
+++ b/ui-react/src/__snapshots__/OnapClamp.test.js.snap
@@ -48,18 +48,10 @@ exports[`Verify OnapClamp Test the render method 1`] = `
render={[Function]}
/>
<Route
- path="/operationalPolicyModal"
- render={[Function]}
- />
- <Route
path="/policyModal/:policyInstanceType/:policyName"
render={[Function]}
/>
<Route
- path="/configurationPolicyModal/:policyName"
- render={[Function]}
- />
- <Route
path="/createLoop"
render={[Function]}
/>
@@ -111,10 +103,6 @@ exports[`Verify OnapClamp Test the render method 1`] = `
path="/refreshStatus"
render={[Function]}
/>
- <Route
- path="/logout"
- render={[Function]}
- />
<GlobalStyleComponent />
<div>
<Alert
@@ -190,11 +178,6 @@ exports[`Verify OnapClamp Test the render method 1`] = `
<Styled(Link)
to="/userInfo"
/>
- <Styled(Link)
- to="/logout/"
- >
- (logout)
- </Styled(Link)>
</NavbarText>
</Navbar>
<styled.div>
diff --git a/ui-react/src/api/LoopService.js b/ui-react/src/api/LoopService.js
index 8f9805e33..c45df6c61 100644
--- a/ui-react/src/api/LoopService.js
+++ b/ui-react/src/api/LoopService.js
@@ -79,26 +79,6 @@ export default class LoopService {
});
}
- static getSvg(loopName) {
- return fetch('/restservices/clds/v2/loop/svgRepresentation/' + loopName, {
- method: 'GET',
- credentials: 'same-origin'
- })
- .then(function (response) {
- console.debug("svgRepresentation response received: ", response.status);
- if (response.ok) {
- return response.text();
- } else {
- console.error("svgRepresentation query failed");
- return "";
- }
- })
- .catch(function (error) {
- console.error("svgRepresentation error received", error);
- return "";
- });
- }
-
static setMicroServiceProperties(loopName, jsonData) {
return fetch('/restservices/clds/v2/loop/updateMicroservicePolicy/' + loopName, {
method: 'POST',
diff --git a/ui-react/src/api/UserService.js b/ui-react/src/api/UserService.js
index 5703fdcc7..477ca4269 100644
--- a/ui-react/src/api/UserService.js
+++ b/ui-react/src/api/UserService.js
@@ -47,31 +47,6 @@ export default class UserService {
return UserService.notLoggedUserName;
});
}
-
- static logout() {
- return fetch('/restservices/clds/v1/user/logout', {
- method: 'POST',
- credentials: 'same-origin'
- })
- .then(function (response) {
- console.debug("logout response received, status code:", response.status);
- if (response.ok) {
- return response.text();
- } else {
- console.error("logout response is nok");
- return UserService.notLoggedUserName;
- }
- })
- .then(function (data) {
- console.info ("User disconnected:",data)
- return data;
- })
- .catch(function(error) {
- console.warn("logout error received, user set to: ",UserService.notLoggedUserName);
- console.error("logout error:",error);
- return UserService.notLoggedUserName;
- });
- }
static getUserInfo() {
return fetch('/restservices/clds/v2/clampInformation', {
diff --git a/ui-react/src/components/dialogs/ConfigurationPolicy/ConfigurationPolicyModal.js b/ui-react/src/components/dialogs/ConfigurationPolicy/ConfigurationPolicyModal.js
deleted file mode 100644
index 3ff1ebec7..000000000
--- a/ui-react/src/components/dialogs/ConfigurationPolicy/ConfigurationPolicyModal.js
+++ /dev/null
@@ -1,127 +0,0 @@
-/*-
- * ============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 LoopService from '../../../api/LoopService';
-import JSONEditor from '@json-editor/json-editor';
-
-const ModalStyled = styled(Modal)`
- background-color: transparent;
-`
-
-export default class ConfigurationPolicyModal extends React.Component {
-
- state = {
- show: true,
- loopCache: this.props.loopCache,
- jsonEditor: null,
- policyName: this.props.match.params.policyName
- };
-
- constructor(props, context) {
- super(props, context);
- this.handleClose = this.handleClose.bind(this);
- this.handleSave = this.handleSave.bind(this);
- this.renderJsonEditor = this.renderJsonEditor.bind(this);
- }
-
- handleSave() {
- var errors = this.state.jsonEditor.validate();
- var editorData = this.state.jsonEditor.getValue();
-
- if (errors.length !== 0) {
- console.error("Errors detected during config policy data validation ", errors);
- this.setState({ show: false });
- this.props.history.push('/');
- }
- else {
- console.info("NO validation errors found in config policy data");
- this.state.loopCache.updateMicroServiceProperties(this.state.policyName, editorData[0]);
- LoopService.setMicroServiceProperties(this.state.loopCache.getLoopName(), this.state.loopCache.getMicroServiceForName(this.state.policyName)).then(resp => {
- this.setState({ show: false });
- this.props.history.push('/');
- this.props.loadLoopFunction(this.state.loopCache.getLoopName());
- });
- }
- }
-
- handleClose() {
- this.setState({ show: false });
- this.props.history.push('/');
- }
-
- componentDidMount() {
- this.renderJsonEditor();
- }
-
- renderJsonEditor() {
- console.debug("Rendering ConfigurationPolicyModal ", this.state.policyName);
- var toscaModel = this.state.loopCache.getMicroServiceJsonRepresentationForName(this.state.policyName);
- if (toscaModel == null) {
- return;
- }
- var editorData = this.state.loopCache.getMicroServicePropertiesForName(this.state.policyName);
-
- this.setState({
- jsonEditor: new JSONEditor(document.getElementById("editor"),
- {
- schema: toscaModel,
- startval: editorData,
- theme: 'bootstrap4',
- object_layout: 'grid',
- disable_properties: true,
- disable_edit_json: false,
- disable_array_reorder: true,
- disable_array_delete_last_row: true,
- disable_array_delete_all_rows: false,
- show_errors: 'always'
- })
- })
- }
-
- render() {
- return (
- <ModalStyled size="xl" show={this.state.show} onHide={this.handleClose} backdrop="static" keyboard={false} >
- <Modal.Header closeButton>
- <Modal.Title>Configuration policies</Modal.Title>
- </Modal.Header>
- <Modal.Body>
- <div id="editor" />
-
- </Modal.Body>
- <Modal.Footer>
- <Button variant="secondary" onClick={this.handleClose}>
- Close
- </Button>
- <Button variant="primary" onClick={this.handleSave}>
- Save Changes
- </Button>
- </Modal.Footer>
- </ModalStyled>
-
- );
- }
-} \ No newline at end of file
diff --git a/ui-react/src/components/dialogs/ConfigurationPolicy/ConfigurationPolicyModal.test.js b/ui-react/src/components/dialogs/ConfigurationPolicy/ConfigurationPolicyModal.test.js
deleted file mode 100644
index a19c18c96..000000000
--- a/ui-react/src/components/dialogs/ConfigurationPolicy/ConfigurationPolicyModal.test.js
+++ /dev/null
@@ -1,78 +0,0 @@
-/*-
- * ============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 { mount } from 'enzyme';
-import ConfigurationPolicyModal from './ConfigurationPolicyModal';
-import LoopCache from '../../../api/LoopCache';
-
-describe('Verify ConfigurationPolicyModal', () => {
- beforeEach(() => {
- fetch.resetMocks();
- fetch.mockImplementation(() => {
- return Promise.resolve({
- ok: true,
- status: 200,
- text: () => "OK"
- });
- });
- })
- const loopCache = new LoopCache({
- "name": "LOOP_Jbv1z_v1_0_ResourceInstanceName1_tca",
- "microServicePolicies": [{
- "name": "TCA_h2NMX_v1_0_ResourceInstanceName1_tca",
- "modelType": "onap.policies.monitoring.cdap.tca.hi.lo.app",
- "properties": {"domain": "measurementsForVfScaling"},
- "shared": false,
- "jsonRepresentation": {"schema": {}}
- }]
- });
- const historyMock = { push: jest.fn() };
- const matchMock = { params:{ policyName: "TCA_h2NMX_v1_0_ResourceInstanceName1_tca" } }
- const flushPromises = () => new Promise(setImmediate);
-
- it('Test handleClose', () => {
- const handleClose = jest.spyOn(ConfigurationPolicyModal.prototype,'handleClose');
- const component = mount(<ConfigurationPolicyModal history={historyMock} loopCache={loopCache} match={matchMock}/>)
-
- component.find('[variant="secondary"]').prop('onClick')();
-
- expect(handleClose).toHaveBeenCalledTimes(1);
- expect(component.state('show')).toEqual(false);
- expect(historyMock.push.mock.calls[0]).toEqual([ '/']);
- });
-
- it('Test handleSave', async () => {
- const loadLoopFunction = jest.fn();
- const handleSave = jest.spyOn(ConfigurationPolicyModal.prototype,'handleSave');
- const component = mount(<ConfigurationPolicyModal history={historyMock} match={matchMock} loopCache={loopCache} loadLoopFunction={loadLoopFunction}/>)
-
- component.find('[variant="primary"]').prop('onClick')();
- await flushPromises();
- component.update();
-
- expect(handleSave).toHaveBeenCalledTimes(1);
- expect(component.state('show')).toEqual(false);
- expect(component.state('policyName')).toEqual("TCA_h2NMX_v1_0_ResourceInstanceName1_tca");
- expect(historyMock.push.mock.calls[0]).toEqual([ '/']);
- });
-}); \ No newline at end of file
diff --git a/ui-react/src/components/dialogs/ManageDictionaries/ManageDictionaries.js b/ui-react/src/components/dialogs/ManageDictionaries/ManageDictionaries.js
index 54ac6411b..58cb9c6c3 100644
--- a/ui-react/src/components/dialogs/ManageDictionaries/ManageDictionaries.js
+++ b/ui-react/src/components/dialogs/ManageDictionaries/ManageDictionaries.js
@@ -24,12 +24,14 @@
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>
);
}
-}
+}
diff --git a/ui-react/src/components/dialogs/ManageDictionaries/ManageDictionaries.test.js b/ui-react/src/components/dialogs/ManageDictionaries/ManageDictionaries.test.js
index 13a6035a3..d1d4aa662 100644
--- a/ui-react/src/components/dialogs/ManageDictionaries/ManageDictionaries.test.js
+++ b/ui-react/src/components/dialogs/ManageDictionaries/ManageDictionaries.test.js
@@ -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"
});
}
});
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
index 71cc393b8..40914aee6 100644
--- a/ui-react/src/components/dialogs/ManageDictionaries/__snapshots__/ManageDictionaries.test.js.snap
+++ b/ui-react/src/components/dialogs/ManageDictionaries/__snapshots__/ManageDictionaries.test.js.snap
@@ -88,7 +88,6 @@ exports[`Verify ManageDictionaries Test API Successful 1`] = `
},
]
}
- data={Array []}
editable={
Object {
"onRowAdd": [Function],
diff --git a/ui-react/src/components/dialogs/OperationalPolicy/OperationalPolicyModal.js b/ui-react/src/components/dialogs/OperationalPolicy/OperationalPolicyModal.js
deleted file mode 100644
index 77dce1656..000000000
--- a/ui-react/src/components/dialogs/OperationalPolicy/OperationalPolicyModal.js
+++ /dev/null
@@ -1,173 +0,0 @@
-/*-
- * ============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 LoopCache from '../../../api/LoopCache';
-import LoopService from '../../../api/LoopService';
-import JSONEditor from '@json-editor/json-editor';
-
-const ModalStyled = styled(Modal)`
- background-color: transparent;
-`
-
-export default class OperationalPolicyModal extends React.Component {
-
- state = {
- show: true,
- loopCache: this.props.loopCache,
- jsonEditor: null
- };
-
- constructor(props, context) {
- super(props, context);
- this.handleClose = this.handleClose.bind(this);
- this.handleSave = this.handleSave.bind(this);
- this.renderJsonEditor = this.renderJsonEditor.bind(this);
- this.handleRefresh = this.handleRefresh.bind(this);
- this.setDefaultJsonEditorOptions();
- }
-
- handleSave() {
- var errors = this.state.jsonEditor.validate();
- var editorData = this.state.jsonEditor.getValue();
-
- if (errors.length !== 0) {
- console.error("Errors detected during config policy data validation ", errors);
- this.props.showFailAlert(errors);
- }
- else {
- console.info("NO validation errors found in config policy data");
- this.state.loopCache.updateOperationalPolicyProperties(editorData);
- LoopService.setOperationalPolicyProperties(this.state.loopCache.getLoopName(), this.state.loopCache.getOperationalPolicies()).then(resp => {
- this.setState({ show: false });
- this.props.history.push('/');
- this.props.loadLoopFunction(this.state.loopCache.getLoopName());
- });
- }
- }
-
- handleClose() {
- this.setState({ show: false });
- this.props.history.push('/');
- }
-
- componentDidMount() {
- this.renderJsonEditor();
- }
-
- setDefaultJsonEditorOptions() {
- JSONEditor.defaults.themes.myBootstrap4 = JSONEditor.defaults.themes.bootstrap4.extend({
- getTab: function(text,tabId) {
- var liel = document.createElement('li');
- liel.classList.add('nav-item');
- var ael = document.createElement("a");
- ael.classList.add("nav-link");
- ael.setAttribute("style",'padding:10px;max-width:160px;');
- ael.setAttribute("href", "#" + tabId);
- ael.setAttribute('data-toggle', 'tab');
- text.setAttribute("style",'word-wrap:break-word;');
- ael.appendChild(text);
- liel.appendChild(ael);
- return liel;
- }
- });
- }
-
- renderJsonEditor() {
- console.debug("Rendering OperationalPolicyModal");
- var schema_json = this.state.loopCache.getOperationalPolicyJsonSchema();
-
- if (schema_json == null) {
- console.error("NO Operational policy schema found");
- return;
- }
- var operationalPoliciesData = this.state.loopCache.getOperationalPoliciesNoJsonSchema();
-
- this.setState({
- jsonEditor: new JSONEditor(document.getElementById("editor"),
- {
- schema: schema_json.schema,
- startval: operationalPoliciesData,
- theme: 'myBootstrap4',
- object_layout: 'grid',
- disable_properties: true,
- disable_edit_json: false,
- disable_array_reorder: true,
- disable_array_delete_last_row: true,
- disable_array_delete_all_rows: false,
- array_controls_top: true,
- show_errors: 'always',
- keep_oneof_values: false,
- collapsed:true
- })
- })
- }
-
- handleRefresh() {
- LoopService.refreshOperationalPolicyJson(this.state.loopCache.getLoopName(), this.state.loopCache.getOperationalPolicies()[0]).then(data => {
- var newLoopCache = new LoopCache(data);
- var schema_json = newLoopCache.getOperationalPolicyJsonSchema();
- var operationalPoliciesData = newLoopCache.getOperationalPoliciesNoJsonSchema();
- document.getElementById("editor").innerHTML = "";
- this.setState({
- loopCache: newLoopCache,
- jsonEditor: new JSONEditor(document.getElementById("editor"),
- { schema: schema_json.schema, startval: operationalPoliciesData })
- })
- this.props.updateLoopFunction(data);
-
- })
- .catch(error => {
- console.error("Error while refreshing the Operational Policy Json Representation");
- });
- }
-
- render() {
- return (
- <ModalStyled size="xl" show={this.state.show} onHide={this.handleClose} backdrop="static" keyboard={false} >
- <Modal.Header closeButton>
- <Modal.Title>Operational policies</Modal.Title>
- </Modal.Header>
- <Modal.Body>
- <div id="editor" />
-
- </Modal.Body>
- <Modal.Footer>
- <Button variant="secondary" onClick={this.handleClose}>
- Close
- </Button>
- <Button variant="secondary" onClick={this.handleRefresh}>
- Refresh
- </Button>
- <Button variant="primary" onClick={this.handleSave}>
- Save Changes
- </Button>
- </Modal.Footer>
- </ModalStyled>
-
- );
- }
-} \ No newline at end of file
diff --git a/ui-react/src/components/dialogs/OperationalPolicy/OperationalPolicyModal.test.js b/ui-react/src/components/dialogs/OperationalPolicy/OperationalPolicyModal.test.js
deleted file mode 100644
index 4c11ce535..000000000
--- a/ui-react/src/components/dialogs/OperationalPolicy/OperationalPolicyModal.test.js
+++ /dev/null
@@ -1,93 +0,0 @@
-/*-
- * ============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 { mount } from 'enzyme';
-import OperationalPolicyModal from './OperationalPolicyModal';
-import LoopCache from '../../../api/LoopCache';
-
-describe('Verify OperationalPolicyModal', () => {
- beforeEach(() => {
- fetch.resetMocks();
- fetch.mockImplementation(() => {
- return Promise.resolve({
- ok: true,
- status: 200,
- text: () => "OK"
- });
- });
- })
- const loopCache = new LoopCache({
- "name": "LOOP_Jbv1z_v1_0_ResourceInstanceName1_tca",
- "operationalPolicies": [{
- "name": "OPERATIONAL_h2NMX_v1_0_ResourceInstanceName1_tca",
- "configurationsJson": {
- "operational_policy": {
- "controlLoop": {},
- "policies": []
- }
- },
- "jsonRepresentation" : {"schema": {}}
- }]
- });
- const historyMock = { push: jest.fn() };
- const flushPromises = () => new Promise(setImmediate);
-
- it('Test handleClose', () => {
- const handleClose = jest.spyOn(OperationalPolicyModal.prototype,'handleClose');
- const component = mount(<OperationalPolicyModal history={historyMock} loopCache={loopCache}/>)
-
- component.find('[variant="secondary"]').get(0).props.onClick();
-
- expect(handleClose).toHaveBeenCalledTimes(1);
- expect(component.state('show')).toEqual(false);
- expect(historyMock.push.mock.calls[0]).toEqual([ '/']);
- });
-
- it('Test handleSave', async () => {
- const loadLoopFunction = jest.fn();
- const handleSave = jest.spyOn(OperationalPolicyModal.prototype,'handleSave');
- const component = mount(<OperationalPolicyModal history={historyMock}
- loopCache={loopCache} loadLoopFunction={loadLoopFunction} />)
-
- component.find('[variant="primary"]').prop('onClick')();
- await flushPromises();
- component.update();
-
- expect(handleSave).toHaveBeenCalledTimes(1);
- expect(component.state('show')).toEqual(false);
- expect(historyMock.push.mock.calls[0]).toEqual([ '/']);
- });
-
- it('Test handleRefresh', async () => {
- const updateLoopFunction = jest.fn();
- const handleRefresh = jest.spyOn(OperationalPolicyModal.prototype,'handleRefresh');
- const component = mount(<OperationalPolicyModal loopCache={loopCache} updateLoopFunction={updateLoopFunction} />)
-
- component.find('[variant="secondary"]').get(1).props.onClick();
- await flushPromises();
- component.update();
-
- expect(handleRefresh).toHaveBeenCalledTimes(1);
- expect(component.state('show')).toEqual(true);
- });
-}); \ No newline at end of file
diff --git a/ui-react/src/components/dialogs/OperationalPolicy/template.json b/ui-react/src/components/dialogs/OperationalPolicy/template.json
deleted file mode 100644
index 7c9dc0cdd..000000000
--- a/ui-react/src/components/dialogs/OperationalPolicy/template.json
+++ /dev/null
@@ -1,37 +0,0 @@
-{
- "operationalPolicies": [
- {
- "name": "OPERATIONAL_LOOP_NAME",
- "configurationsJson": {
- "operational_policy": {
- "controlLoop": {
- "trigger_policy": "new",
- "timeout": "0",
- "abatement": "false",
- "controlLoopName": "LOOP_h2NMX_v1_0_ResourceInstanceName1_tca"
- },
- "policies": [
- {
- "id": "new",
- "recipe": "",
- "retry": "0",
- "timeout": "0",
- "actor": "",
- "payload": "",
- "success": "",
- "failure": "",
- "failure_timeout": "",
- "failure_retries": "",
- "failure_exception": "",
- "failure_guard": "",
- "target": {
- "type": "VM",
- "resourceID": ""
- }
- }
- ]
- }
- }
- }
- ]
-} \ No newline at end of file
diff --git a/ui-react/src/utils/CsvToJson.js b/ui-react/src/utils/CsvToJson.js
new file mode 100644
index 000000000..5ec19c9e2
--- /dev/null
+++ b/ui-react/src/utils/CsvToJson.js
@@ -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
index 000000000..88fa7a472
--- /dev/null
+++ b/ui-react/src/utils/CsvToJson.test.js
@@ -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);
+ });
+})