summaryrefslogtreecommitdiffstats
path: root/gui-clamp/ui-react/src/components/dialogs/Loop
diff options
context:
space:
mode:
Diffstat (limited to 'gui-clamp/ui-react/src/components/dialogs/Loop')
-rw-r--r--gui-clamp/ui-react/src/components/dialogs/Loop/CreateLoopModal.js193
-rw-r--r--gui-clamp/ui-react/src/components/dialogs/Loop/CreateLoopModal.test.js144
-rw-r--r--gui-clamp/ui-react/src/components/dialogs/Loop/DeployLoopModal.js183
-rw-r--r--gui-clamp/ui-react/src/components/dialogs/Loop/DeployLoopModal.test.js114
-rw-r--r--gui-clamp/ui-react/src/components/dialogs/Loop/LoopPropertiesModal.js118
-rw-r--r--gui-clamp/ui-react/src/components/dialogs/Loop/LoopPropertiesModal.test.js110
-rw-r--r--gui-clamp/ui-react/src/components/dialogs/Loop/ModifyLoopModal.js269
-rw-r--r--gui-clamp/ui-react/src/components/dialogs/Loop/ModifyLoopModal.test.js109
-rw-r--r--gui-clamp/ui-react/src/components/dialogs/Loop/OpenLoopModal.js139
-rw-r--r--gui-clamp/ui-react/src/components/dialogs/Loop/OpenLoopModal.test.js93
-rw-r--r--gui-clamp/ui-react/src/components/dialogs/Loop/__snapshots__/CreateLoopModal.test.js.snap167
-rw-r--r--gui-clamp/ui-react/src/components/dialogs/Loop/__snapshots__/DeployLoopModal.test.js.snap83
-rw-r--r--gui-clamp/ui-react/src/components/dialogs/Loop/__snapshots__/LoopPropertiesModal.test.js.snap61
-rw-r--r--gui-clamp/ui-react/src/components/dialogs/Loop/__snapshots__/OpenLoopModal.test.js.snap137
14 files changed, 1920 insertions, 0 deletions
diff --git a/gui-clamp/ui-react/src/components/dialogs/Loop/CreateLoopModal.js b/gui-clamp/ui-react/src/components/dialogs/Loop/CreateLoopModal.js
new file mode 100644
index 0000000..690dcbb
--- /dev/null
+++ b/gui-clamp/ui-react/src/components/dialogs/Loop/CreateLoopModal.js
@@ -0,0 +1,193 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP CLAMP
+ * ================================================================================
+ * Copyright (C) 2020 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 Select from 'react-select';
+import Button from 'react-bootstrap/Button';
+import Modal from 'react-bootstrap/Modal';
+import Form from 'react-bootstrap/Form';
+import Row from 'react-bootstrap/Row';
+import Col from 'react-bootstrap/Col';
+import styled from 'styled-components';
+import LoopService from '../../../api/LoopService';
+import TemplateService from '../../../api/TemplateService';
+import LoopCache from '../../../api/LoopCache';
+import SvgGenerator from '../../loop_viewer/svg/SvgGenerator';
+
+const ModalStyled = styled(Modal)`
+ background-color: transparent;
+`
+
+const ErrMsgStyled = styled.div`
+ color: red;
+`
+
+export default class CreateLoopModal extends React.Component {
+ constructor(props, context) {
+ super(props, context);
+
+ this.getAllLoopTemplates = this.getAllLoopTemplates.bind(this);
+ this.handleCreate = this.handleCreate.bind(this);
+ this.handleModelName = this.handleModelName.bind(this);
+ this.handleClose = this.handleClose.bind(this);
+ this.handleDropDownListChange = this.handleDropDownListChange.bind(this);
+ this.renderSvg = this.renderSvg.bind(this);
+ this.state = {
+ show: true,
+ chosenTemplateName: '',
+ modelInputErrMsg: '',
+ modelName: '',
+ templateNames: [],
+ fakeLoopCacheWithTemplate: new LoopCache({})
+ };
+ }
+
+ async componentDidMount() {
+ await this.getAllLoopTemplates();
+ await this.getModelNames();
+ }
+
+ handleClose() {
+ this.setState({ show: false });
+ this.props.history.push('/');
+ }
+
+ handleDropDownListChange(e) {
+ if (typeof e.value !== "undefined") {
+ this.setState({
+ fakeLoopCacheWithTemplate:
+ new LoopCache({
+ "loopTemplate": e.templateObject,
+ "name": "fakeLoop"
+ }),
+ chosenTemplateName: e.value
+ })
+ } else {
+ this.setState({ fakeLoopCacheWithTemplate: new LoopCache({}) })
+ }
+ }
+
+ getAllLoopTemplates() {
+ TemplateService.getAllLoopTemplates().then(templatesData => {
+ const templateOptions = templatesData.map((templateData) => {
+ return { label: templateData.name, value: templateData.name, templateObject: templateData }
+ });
+ this.setState({
+ templateNames: templateOptions
+ })
+ });
+ }
+
+ getModelNames() {
+ TemplateService.getLoopNames().then(loopNames => {
+ if (!loopNames) {
+ loopNames = [];
+ }
+ // Remove LOOP_ prefix
+ let trimmedLoopNames = loopNames.map(str => str.replace('LOOP_', ''));
+ this.setState({ modelNames: trimmedLoopNames });
+ });
+ }
+
+ handleCreate() {
+ if (!this.state.modelName) {
+ alert("A model name is required");
+ return;
+ }
+ console.debug("Create Model " + this.state.modelName + ", Template " + this.state.chosenTemplateName + " is chosen");
+ this.setState({ show: false });
+ LoopService.createLoop("LOOP_" + this.state.modelName, this.state.chosenTemplateName).then(text => {
+ console.debug("CreateLoop response received: ", text);
+ try {
+ this.props.history.push('/');
+ this.props.loadLoopFunction("LOOP_" + this.state.modelName);
+ } catch (err) {
+ alert(text);
+ this.props.history.push('/');
+ }
+ })
+ .catch(error => {
+ console.debug("Create Loop failed");
+ });
+ }
+
+ handleModelName(event) {
+ if (this.state.modelNames.includes(event.target.value)) {
+ this.setState({
+ modelInputErrMsg: 'A model named "' + event.target.value + '" already exists. Please pick another name.',
+ modelName: event.target.value
+ });
+ return;
+ } else {
+ this.setState({
+ modelInputErrMsg: '',
+ modelName: event.target.value
+ });
+ }
+ }
+
+ renderSvg() {
+ return (
+ <SvgGenerator loopCache={ this.state.fakeLoopCacheWithTemplate } clickable={ false } generatedFrom={ SvgGenerator.GENERATED_FROM_TEMPLATE }/>
+ );
+ }
+
+ render() {
+ return (
+ <ModalStyled size="xl" show={ this.state.show } onHide={ this.handleClose } backdrop="static" keyboard={ false }>
+ <Modal.Header closeButton>
+ <Modal.Title>Create Model</Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Form.Group as={ Row } controlId="formPlaintextEmail">
+ <Form.Label column sm="2">Template Name:</Form.Label>
+ <Col sm="10">
+ <Select onChange={ this.handleDropDownListChange } options={ this.state.templateNames }/>
+ </Col>
+ </Form.Group>
+ <Form.Group as={ Row } style={ { alignItems: 'center' } } controlId="formSvgPreview">
+ <Form.Label column sm="2">Model Preview:</Form.Label>
+ <Col sm="10">
+ { this.renderSvg() }
+ </Col>
+ </Form.Group>
+ <Form.Group as={ Row } controlId="formPlaintextEmail">
+ <Form.Label column sm="2">Model Name:</Form.Label>
+ <input sm="5" type="text" style={ { width: '50%', marginLeft: '1em' } }
+ value={ this.state.modelName }
+ onChange={ this.handleModelName }
+ />
+ <span sm="5"/>
+ </Form.Group>
+ <Form.Group as={ Row } controlId="formPlaintextEmail">
+ <Form.Label column sm="2"> </Form.Label>
+ <ErrMsgStyled>{ this.state.modelInputErrMsg }</ErrMsgStyled>
+ </Form.Group>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button variant="secondary" type="null" onClick={ this.handleClose }>Cancel</Button>
+ <Button variant="primary" type="submit" onClick={ this.handleCreate }>Create</Button>
+ </Modal.Footer>
+ </ModalStyled>
+ );
+ }
+}
diff --git a/gui-clamp/ui-react/src/components/dialogs/Loop/CreateLoopModal.test.js b/gui-clamp/ui-react/src/components/dialogs/Loop/CreateLoopModal.test.js
new file mode 100644
index 0000000..401bb6a
--- /dev/null
+++ b/gui-clamp/ui-react/src/components/dialogs/Loop/CreateLoopModal.test.js
@@ -0,0 +1,144 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP CLAMP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights
+ * reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END============================================
+ * ===================================================================
+ *
+ */
+import React from 'react';
+import { shallow } from 'enzyme';
+import CreateLoopModal from './CreateLoopModal';
+import LoopService from '../../../api/LoopService';
+import TemplateService from '../../../api/TemplateService';
+
+describe('Verify CreateLoopModal', () => {
+
+ it('Test the render method', async () => {
+ const flushPromises = () => new Promise(setImmediate);
+ TemplateService.getAllLoopTemplates = jest.fn().mockImplementation(() => {
+ return Promise.resolve([{ "name": "template1" }, { "name": "template2" }]);
+ });
+ TemplateService.getLoopNames = jest.fn().mockImplementation(() => {
+ return Promise.resolve([]);
+ });
+
+ const component = shallow(<CreateLoopModal/>);
+ expect(component).toMatchSnapshot();
+ await flushPromises();
+ component.update();
+ expect(component.state('templateNames')).toStrictEqual([{ "label": "template1", "value": "template1", "templateObject": { "name": "template1" } }, {
+ "label": "template2",
+ "value": "template2",
+ "templateObject": { "name": "template2" }
+ }]);
+ });
+
+ it('handleDropdownListChange event', async () => {
+ const flushPromises = () => new Promise(setImmediate);
+
+ const component = shallow(<CreateLoopModal/>);
+ component.find('StateManager').simulate('change', { value: 'template1', templateObject: { "name": "template1" } });
+ await flushPromises();
+ component.update();
+ expect(component.state('chosenTemplateName')).toEqual("template1");
+ expect(component.state('fakeLoopCacheWithTemplate').getLoopTemplate()['name']).toEqual("template1");
+ expect(component.state('fakeLoopCacheWithTemplate').getLoopName()).toEqual("fakeLoop");
+
+ component.find('StateManager').simulate('change', { value: 'template2', templateObject: { "name": "template2" } });
+ await flushPromises();
+ component.update();
+ expect(component.state('chosenTemplateName')).toEqual("template2");
+ expect(component.state('fakeLoopCacheWithTemplate').getLoopTemplate()['name']).toEqual("template2");
+ expect(component.state('fakeLoopCacheWithTemplate').getLoopName()).toEqual("fakeLoop");
+ });
+
+ it('handleModelName event', async () => {
+ const flushPromises = () => new Promise(setImmediate);
+ TemplateService.getAllLoopTemplates = jest.fn().mockImplementation(() => {
+ return Promise.resolve([{ "name": "template1" }, { "name": "template2" }]);
+ });
+ TemplateService.getLoopNames = jest.fn().mockImplementation(() => {
+ return Promise.resolve([]);
+ });
+ const event = { target: { value: "model1" } };
+ const component = shallow(<CreateLoopModal/>);
+ await flushPromises();
+ component.find('input').simulate('change', event);
+ component.update();
+ expect(component.state('modelName')).toEqual("model1");
+ });
+
+ it('Test handleClose', () => {
+ const historyMock = { push: jest.fn() };
+ const handleClose = jest.spyOn(CreateLoopModal.prototype, 'handleClose');
+ const component = shallow(<CreateLoopModal history={ historyMock }/>)
+
+ component.find('[variant="secondary"]').prop('onClick')();
+
+ expect(handleClose).toHaveBeenCalledTimes(1);
+ expect(component.state('show')).toEqual(false);
+ expect(historyMock.push.mock.calls[0]).toEqual(['/']);
+
+ handleClose.mockClear();
+ });
+
+ it('Test handleCreate Fail', () => {
+ const handleCreate = jest.spyOn(CreateLoopModal.prototype, 'handleCreate');
+ const component = shallow(<CreateLoopModal/>)
+
+ component.find('[variant="primary"]').prop('onClick')();
+
+ expect(handleCreate).toHaveBeenCalledTimes(1);
+ expect(component.state('show')).toEqual(true);
+
+ handleCreate.mockClear();
+ });
+
+ it('Test handleCreate Suc', async () => {
+ const flushPromises = () => new Promise(setImmediate);
+ const historyMock = { push: jest.fn() };
+ const loadLoopFunction = jest.fn();
+
+ LoopService.createLoop = jest.fn().mockImplementation(() => {
+ return Promise.resolve({
+ ok: true,
+ status: 200,
+ json: () => {
+ }
+ });
+ });
+
+ const handleCreate = jest.spyOn(CreateLoopModal.prototype, 'handleCreate');
+ const component = shallow(<CreateLoopModal history={ historyMock } loadLoopFunction={ loadLoopFunction }/>)
+ component.setState({
+ modelName: "modelNameTest",
+ chosenTemplateName: "template1"
+ });
+
+ component.find('[variant="primary"]').prop('onClick')();
+ await flushPromises();
+ component.update();
+
+ expect(handleCreate).toHaveBeenCalledTimes(1);
+ expect(component.state('show')).toEqual(false);
+ expect(historyMock.push.mock.calls[0]).toEqual(['/']);
+
+ handleCreate.mockClear();
+ });
+
+});
diff --git a/gui-clamp/ui-react/src/components/dialogs/Loop/DeployLoopModal.js b/gui-clamp/ui-react/src/components/dialogs/Loop/DeployLoopModal.js
new file mode 100644
index 0000000..b7ab8a5
--- /dev/null
+++ b/gui-clamp/ui-react/src/components/dialogs/Loop/DeployLoopModal.js
@@ -0,0 +1,183 @@
+/*-
+ * ============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 LoopActionService from '../../../api/LoopActionService';
+import LoopService from '../../../api/LoopService';
+import Button from 'react-bootstrap/Button';
+import Modal from 'react-bootstrap/Modal';
+import Form from 'react-bootstrap/Form';
+import Tabs from 'react-bootstrap/Tabs';
+import Tab from 'react-bootstrap/Tab';
+import styled from 'styled-components';
+import Spinner from 'react-bootstrap/Spinner'
+
+const StyledSpinnerDiv = styled.div`
+ justify-content: center !important;
+ display: flex !important;
+`;
+
+const ModalStyled = styled(Modal)`
+ background-color: transparent;
+`
+const FormStyled = styled(Form.Group)`
+ padding: .25rem 1.5rem;
+`
+export default class DeployLoopModal extends React.Component {
+
+ constructor(props, context) {
+ super(props, context);
+
+ this.handleSave = this.handleSave.bind(this);
+ this.handleClose = this.handleClose.bind(this);
+ this.handleChange = this.handleChange.bind(this);
+ this.refreshStatus = this.refreshStatus.bind(this);
+ this.renderDeployParam = this.renderDeployParam.bind(this);
+ this.renderSpinner = this.renderSpinner.bind(this);
+
+ const propertiesJson = JSON.parse(JSON.stringify(this.props.loopCache.getGlobalProperties()));
+ this.state = {
+ loopCache: this.props.loopCache,
+ temporaryPropertiesJson: propertiesJson,
+ show: true,
+ key: this.getInitialKeyValue(propertiesJson)
+ };
+ }
+
+ getInitialKeyValue(temporaryPropertiesJson) {
+ const deployJsonList = temporaryPropertiesJson["dcaeDeployParameters"];
+ let initialKey;
+ Object.keys(deployJsonList)
+ .filter((obj) => Object.keys(deployJsonList).indexOf(obj) === 0)
+ .map(obj =>
+ initialKey = obj
+ );
+ return initialKey;
+ }
+
+ componentWillReceiveProps(newProps) {
+ this.setState({
+ loopName: newProps.loopCache.getLoopName(),
+ show: true
+ });
+ }
+
+ handleClose() {
+ this.setState({ show: false });
+ this.props.history.push('/');
+ }
+
+ renderSpinner() {
+ if (this.state.deploying) {
+ return (
+ <StyledSpinnerDiv>
+ <Spinner animation="border" role="status">
+ <span className="sr-only">Loading...</span>
+ </Spinner>
+ </StyledSpinnerDiv>
+ );
+ } else {
+ return (<div></div>);
+ }
+ }
+
+ handleSave() {
+ const loopName = this.props.loopCache.getLoopName();
+ // save the global propserties
+ this.setState({ deploying: true });
+ LoopService.updateGlobalProperties(loopName, this.state.temporaryPropertiesJson).then(resp => {
+ LoopActionService.performAction(loopName, "deploy").then(pars => {
+ this.props.showSucAlert("Action deploy successfully performed");
+ // refresh status and update loop logs
+ this.refreshStatus(loopName);
+ })
+ .catch(error => {
+ this.props.showFailAlert("Action deploy failed");
+ // refresh status and update loop logs
+ this.refreshStatus(loopName);
+ });
+ });
+ }
+
+ refreshStatus(loopName) {
+ LoopActionService.refreshStatus(loopName).then(data => {
+ this.props.updateLoopFunction(data);
+ this.setState({ show: false, deploying: false });
+ this.props.history.push('/');
+ })
+ .catch(error => {
+ this.props.showFailAlert("Refresh status failed");
+ this.setState({ show: false, deploying: false });
+ this.props.history.push('/');
+ });
+ }
+
+ handleChange(event) {
+ let deploymentParam = this.state.temporaryPropertiesJson["dcaeDeployParameters"];
+ deploymentParam[this.state.key][event.target.name] = event.target.value;
+
+ this.setState({ temporaryPropertiesJson: { dcaeDeployParameters: deploymentParam } });
+ }
+
+ renderDeployParamTabs() {
+ if (typeof (this.state.temporaryPropertiesJson) === "undefined") {
+ return "";
+ }
+
+ const deployJsonList = this.state.temporaryPropertiesJson["dcaeDeployParameters"];
+ var indents = [];
+ Object.keys(deployJsonList).map((item, key) =>
+ indents.push(<Tab eventKey={ item } title={ item }>
+ { this.renderDeployParam(deployJsonList[item]) }
+ </Tab>)
+ );
+ return indents;
+ }
+
+ renderDeployParam(deployJson) {
+ var indents = [];
+ Object.keys(deployJson).map((item, key) =>
+ indents.push(<FormStyled>
+ <Form.Label>{ item }</Form.Label>
+ <Form.Control type="text" name={ item } onChange={ this.handleChange } defaultValue={ deployJson[item] }></Form.Control>
+ </FormStyled>));
+ return indents;
+ }
+
+ render() {
+ return (
+ <ModalStyled size="lg" show={ this.state.show } onHide={ this.handleClose } backdrop="static" keyboard={ false }>
+ <Modal.Header closeButton>
+ <Modal.Title>Deployment parameters</Modal.Title>
+ </Modal.Header>
+ <Tabs id="controlled-tab-example" activeKey={ this.state.key } onSelect={ key => this.setState({ key }) }>
+ { this.renderDeployParamTabs() }
+ </Tabs>
+ { this.renderSpinner() }
+ <Modal.Footer>
+ <Button variant="secondary" type="null" onClick={ this.handleClose }>Cancel</Button>
+ <Button variant="primary" type="submit" onClick={ this.handleSave }>Deploy</Button>
+ </Modal.Footer>
+ </ModalStyled>
+ );
+ }
+}
diff --git a/gui-clamp/ui-react/src/components/dialogs/Loop/DeployLoopModal.test.js b/gui-clamp/ui-react/src/components/dialogs/Loop/DeployLoopModal.test.js
new file mode 100644
index 0000000..0c68f23
--- /dev/null
+++ b/gui-clamp/ui-react/src/components/dialogs/Loop/DeployLoopModal.test.js
@@ -0,0 +1,114 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP CLAMP
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights
+ * reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END============================================
+ * ===================================================================
+ *
+ */
+import React from 'react';
+import { shallow } from 'enzyme';
+import DeployLoopModal from './DeployLoopModal';
+import LoopCache from '../../../api/LoopCache';
+import LoopActionService from '../../../api/LoopActionService';
+import LoopService from '../../../api/LoopService';
+
+describe('Verify DeployLoopModal', () => {
+ const loopCache = new LoopCache({
+ "name": "LOOP_Jbv1z_v1_0_ResourceInstanceName1_tca",
+ "globalPropertiesJson": {
+ "dcaeDeployParameters": {
+ "testMs": {
+ "location_id": "",
+ "policy_id": "TCA_h2NMX_v1_0_ResourceInstanceName1_tca"
+ }
+ }
+ }
+ });
+
+ it('Test the render method', () => {
+ const component = shallow(
+ <DeployLoopModal loopCache={ loopCache }/>
+ )
+
+ expect(component).toMatchSnapshot();
+ });
+
+ it('Test handleClose', () => {
+ const historyMock = { push: jest.fn() };
+ const handleClose = jest.spyOn(DeployLoopModal.prototype, 'handleClose');
+ const component = shallow(<DeployLoopModal history={ historyMock } loopCache={ loopCache }/>)
+
+ component.find('[variant="secondary"]').prop('onClick')();
+
+ expect(handleClose).toHaveBeenCalledTimes(1);
+ expect(historyMock.push.mock.calls[0]).toEqual(['/']);
+ });
+
+ it('Test handleSave successful', async () => {
+ const flushPromises = () => new Promise(setImmediate);
+ const historyMock = { push: jest.fn() };
+ const updateLoopFunction = jest.fn();
+ const showSucAlert = jest.fn();
+ const showFailAlert = jest.fn();
+ const handleSave = jest.spyOn(DeployLoopModal.prototype, 'handleSave');
+ LoopService.updateGlobalProperties = jest.fn().mockImplementation(() => {
+ return Promise.resolve({
+ ok: true,
+ status: 200,
+ text: () => "OK"
+ });
+ });
+ LoopActionService.performAction = jest.fn().mockImplementation(() => {
+ return Promise.resolve({
+ ok: true,
+ status: 200,
+ json: () => {
+ }
+ });
+ });
+ LoopActionService.refreshStatus = jest.fn().mockImplementation(() => {
+ return Promise.resolve({
+ ok: true,
+ status: 200,
+ json: () => {
+ }
+ });
+ });
+
+ const component = shallow(<DeployLoopModal history={ historyMock }
+ loopCache={ loopCache } updateLoopFunction={ updateLoopFunction } showSucAlert={ showSucAlert } showFailAlert={ showFailAlert }/>)
+
+ 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(['/']);
+ handleSave.mockClear();
+ });
+
+ it('Onchange event', () => {
+ const event = { target: { name: "location_id", value: "testLocation" } };
+ const component = shallow(<DeployLoopModal loopCache={ loopCache }/>);
+
+ component.find('[name="location_id"]').simulate('change', event);
+ component.update();
+ expect(component.state('temporaryPropertiesJson').dcaeDeployParameters.testMs.location_id).toEqual("testLocation");
+ });
+});
diff --git a/gui-clamp/ui-react/src/components/dialogs/Loop/LoopPropertiesModal.js b/gui-clamp/ui-react/src/components/dialogs/Loop/LoopPropertiesModal.js
new file mode 100644
index 0000000..6917713
--- /dev/null
+++ b/gui-clamp/ui-react/src/components/dialogs/Loop/LoopPropertiesModal.js
@@ -0,0 +1,118 @@
+/*-
+ * ============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 Form from 'react-bootstrap/Form';
+import styled from 'styled-components';
+import LoopService from '../../../api/LoopService';
+
+const ModalStyled = styled(Modal)`
+ background-color: transparent;
+`
+export default class LoopPropertiesModal extends React.Component {
+
+ state = {
+ show: true,
+ loopCache: this.props.loopCache,
+ temporaryPropertiesJson: JSON.parse(JSON.stringify(this.props.loopCache.getGlobalProperties()))
+ };
+
+ constructor(props, context) {
+ super(props, context);
+
+ this.handleClose = this.handleClose.bind(this);
+ this.handleSave = this.handleSave.bind(this);
+ this.handleChange = this.handleChange.bind(this);
+
+ this.renderDcaeParameters = this.renderDcaeParameters.bind(this);
+ this.renderAllParameters = this.renderAllParameters.bind(this);
+ this.getDcaeParameters = this.getDcaeParameters.bind(this);
+ this.readOnly = props.readOnly !== undefined ? props.readOnly : false;
+ }
+
+ componentWillReceiveProps(newProps) {
+ this.setState({
+ loopCache: newProps.loopCache,
+ temporaryPropertiesJson: JSON.parse(JSON.stringify(newProps.loopCache.getGlobalProperties()))
+ });
+ }
+
+ handleClose() {
+ this.props.history.push('/');
+ }
+
+ handleSave(event) {
+ LoopService.updateGlobalProperties(this.state.loopCache.getLoopName(), this.state.temporaryPropertiesJson).then(resp => {
+ this.setState({ show: false });
+ this.props.history.push('/');
+ this.props.loadLoopFunction(this.state.loopCache.getLoopName());
+ });
+ }
+
+ handleChange(event) {
+ this.setState({ temporaryPropertiesJson: { [event.target.name]: JSON.parse(event.target.value) } });
+ }
+
+ renderAllParameters() {
+ return (<Modal.Body>
+ <Form>
+ { this.renderDcaeParameters() }
+ </Form>
+ </Modal.Body>
+ );
+ }
+
+ getDcaeParameters() {
+ if (typeof (this.state.temporaryPropertiesJson) !== "undefined") {
+ return JSON.stringify(this.state.temporaryPropertiesJson["dcaeDeployParameters"]);
+ } else {
+ return "";
+ }
+
+ }
+
+ renderDcaeParameters() {
+ return (
+ <Form.Group>
+ <Form.Label>Deploy Parameters</Form.Label>
+ <Form.Control as="textarea" rows="3" name="dcaeDeployParameters" onChange={ this.handleChange } defaultValue={ this.getDcaeParameters() }></Form.Control>
+ </Form.Group>
+ );
+ }
+
+ render() {
+ return (
+ <ModalStyled size="lg" show={ this.state.show } onHide={ this.handleClose } backdrop="static" keyboard={ false }>
+ <Modal.Header closeButton>
+ <Modal.Title>Model Properties</Modal.Title>
+ </Modal.Header>
+ { this.renderAllParameters() }
+ <Modal.Footer>
+ <Button variant="secondary" type="null" onClick={ this.handleClose }>Cancel</Button>
+ <Button variant="primary" type="submit" disabled={ this.readOnly } onClick={ this.handleSave }>Save Changes</Button>
+ </Modal.Footer>
+ </ModalStyled>
+ );
+ }
+}
diff --git a/gui-clamp/ui-react/src/components/dialogs/Loop/LoopPropertiesModal.test.js b/gui-clamp/ui-react/src/components/dialogs/Loop/LoopPropertiesModal.test.js
new file mode 100644
index 0000000..a9c5903
--- /dev/null
+++ b/gui-clamp/ui-react/src/components/dialogs/Loop/LoopPropertiesModal.test.js
@@ -0,0 +1,110 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP CLAMP
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights
+ * reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END============================================
+ * ===================================================================
+ *
+ */
+import React from 'react';
+import { shallow } from 'enzyme';
+import LoopPropertiesModal from './LoopPropertiesModal';
+import LoopCache from '../../../api/LoopCache';
+import LoopService from '../../../api/LoopService';
+
+describe('Verify LoopPropertiesModal', () => {
+ const loopCache = new LoopCache({
+ "name": "LOOP_Jbv1z_v1_0_ResourceInstanceName1_tca",
+ "globalPropertiesJson": {
+ "dcaeDeployParameters": {
+ "location_id": "",
+ "policy_id": "TCA_h2NMX_v1_0_ResourceInstanceName1_tca"
+ }
+ }
+ });
+
+ it('Test the render method', () => {
+ const component = shallow(
+ <LoopPropertiesModal loopCache={ loopCache }/>
+ )
+ component.setState({
+ show: true,
+ temporaryPropertiesJson: {
+ "dcaeDeployParameters": {
+ "location_id": "",
+ "policy_id": "TCA_h2NMX_v1_0_ResourceInstanceName1_tca"
+ }
+ }
+ });
+
+ expect(component.state('temporaryPropertiesJson')).toEqual({
+ "dcaeDeployParameters": {
+ "location_id": "",
+ "policy_id": "TCA_h2NMX_v1_0_ResourceInstanceName1_tca"
+ }
+ });
+ expect(component.state('show')).toEqual(true);
+
+ expect(component).toMatchSnapshot();
+ });
+
+ it('Test handleClose', () => {
+ const historyMock = { push: jest.fn() };
+ const handleClose = jest.spyOn(LoopPropertiesModal.prototype, 'handleClose');
+ const component = shallow(<LoopPropertiesModal history={ historyMock } loopCache={ loopCache }/>)
+
+ component.find('[variant="secondary"]').prop('onClick')();
+
+ expect(handleClose).toHaveBeenCalledTimes(1);
+ expect(historyMock.push.mock.calls[0]).toEqual(['/']);
+ });
+
+ it('Test handleSave successful', async () => {
+ const flushPromises = () => new Promise(setImmediate);
+ const historyMock = { push: jest.fn() };
+ const loadLoopFunction = jest.fn();
+ const handleSave = jest.spyOn(LoopPropertiesModal.prototype, 'handleSave');
+ LoopService.updateGlobalProperties = jest.fn().mockImplementation(() => {
+ return Promise.resolve({
+ ok: true,
+ status: 200,
+ text: () => "OK"
+ });
+ });
+
+ const component = shallow(<LoopPropertiesModal 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('Onchange event', () => {
+ const event = { target: { name: "dcaeDeployParameters", value: "{\"location_id\": \"testLocation\",\"policy_id\": \"TCA_h2NMX_v1_0_ResourceInstanceName1_tca\"}" } };
+ const component = shallow(<LoopPropertiesModal loopCache={ loopCache }/>);
+
+ component.find('FormControl').simulate('change', event);
+ component.update();
+
+ expect(component.state('temporaryPropertiesJson').dcaeDeployParameters.location_id).toEqual("testLocation");
+ });
+});
diff --git a/gui-clamp/ui-react/src/components/dialogs/Loop/ModifyLoopModal.js b/gui-clamp/ui-react/src/components/dialogs/Loop/ModifyLoopModal.js
new file mode 100644
index 0000000..8f8b74d
--- /dev/null
+++ b/gui-clamp/ui-react/src/components/dialogs/Loop/ModifyLoopModal.js
@@ -0,0 +1,269 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP CLAMP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights
+ * reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END============================================
+ * ===================================================================
+ *
+ */
+
+import React, { forwardRef } from 'react'
+import MaterialTable from "material-table";
+import Button from 'react-bootstrap/Button';
+import Modal from 'react-bootstrap/Modal';
+import styled from 'styled-components';
+import PolicyToscaService from '../../../api/PolicyToscaService';
+import ArrowUpward from '@material-ui/icons/ArrowUpward';
+import ChevronLeft from '@material-ui/icons/ChevronLeft';
+import ChevronRight from '@material-ui/icons/ChevronRight';
+import Clear from '@material-ui/icons/Clear';
+import FirstPage from '@material-ui/icons/FirstPage';
+import LastPage from '@material-ui/icons/LastPage';
+import Search from '@material-ui/icons/Search';
+import LoopService from '../../../api/LoopService';
+import Tabs from 'react-bootstrap/Tabs';
+import Tab from 'react-bootstrap/Tab';
+import Alert from 'react-bootstrap/Alert';
+
+const ModalStyled = styled(Modal)`
+ background-color: transparent;
+`
+const TextModal = styled.textarea`
+ margin-top: 20px;
+ white-space: pre;
+ background-color: ${ props => props.theme.toscaTextareaBackgroundColor };
+ text-align: justify;
+ font-size: ${ props => props.theme.toscaTextareaFontSize };
+ width: 100%;
+ height: 300px;
+`
+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' };
+
+export default class ModifyLoopModal extends React.Component {
+
+ state = {
+ show: true,
+ loopCache: this.props.loopCache,
+ content: 'Please select Tosca model to view the details',
+ selectedRowData: {},
+ toscaPolicyModelsData: [],
+ selectedPolicyModelsData: [],
+ key: 'add',
+ showFailAlert: false,
+ toscaColumns: [
+ {
+ title: "#", field: "index", render: rowData => rowData.tableData.id + 1,
+ cellStyle: cellStyle,
+ headerStyle: headerStyle
+ },
+ {
+ title: "Policy Model Type", field: "policyModelType",
+ cellStyle: cellStyle,
+ headerStyle: headerStyle
+ },
+ {
+ title: "Policy Acronym", field: "policyAcronym",
+ cellStyle: cellStyle,
+ headerStyle: headerStyle
+ },
+ {
+ title: "Policy Name", field: "policyName",
+ cellStyle: cellStyle,
+ headerStyle: headerStyle
+ },
+ {
+ title: "Version", field: "version",
+ cellStyle: cellStyle,
+ headerStyle: headerStyle
+ },
+ {
+ title: "Uploaded By", field: "updatedBy",
+ cellStyle: cellStyle,
+ headerStyle: headerStyle
+ },
+ {
+ title: "Uploaded Date", field: "updatedDate", editable: 'never',
+ cellStyle: cellStyle,
+ headerStyle: headerStyle
+ },
+ {
+ title: "Created Date", field: "createdDate", editable: 'never',
+ cellStyle: cellStyle,
+ headerStyle: headerStyle
+ }
+ ],
+ tableIcons: {
+ 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 }/>)
+ }
+ };
+
+ constructor(props, context) {
+ super(props, context);
+ this.handleClose = this.handleClose.bind(this);
+ this.initializeToscaPolicyModelsInfo = this.initializeToscaPolicyModelsInfo.bind(this);
+ this.handleYamlContent = this.handleYamlContent.bind(this);
+ this.getToscaPolicyModelYaml = this.getToscaPolicyModelYaml.bind(this);
+ this.handleAdd = this.handleAdd.bind(this);
+ this.handleRemove = this.handleRemove.bind(this);
+ this.initializeToscaPolicyModelsInfo();
+ }
+
+ componentWillReceiveProps(newProps) {
+ this.setState({
+ loopCache: newProps.loopCache,
+ temporaryPropertiesJson: JSON.parse(JSON.stringify(newProps.loopCache.getGlobalProperties()))
+ });
+ }
+
+ initializeToscaPolicyModelsInfo() {
+ var operationalPolicies = this.state.loopCache.getOperationalPolicies();
+ var selectedPolicyModels = [];
+ for (var policy in operationalPolicies) {
+ var newRow = operationalPolicies[policy]["policyModel"];
+ newRow["policyName"] = operationalPolicies[policy].name;
+ selectedPolicyModels.push(newRow);
+ }
+
+ PolicyToscaService.getToscaPolicyModels().then(allToscaModels => {
+ this.setState({
+ toscaPolicyModelsData: allToscaModels,
+ selectedPolicyModelsData: selectedPolicyModels
+ });
+ });
+ }
+
+ getToscaPolicyModelYaml(policyModelType, policyModelVersion) {
+ if (typeof policyModelType !== "undefined") {
+ PolicyToscaService.getToscaPolicyModelYaml(policyModelType, policyModelVersion).then(toscaYaml => {
+ if (toscaYaml.length !== 0) {
+ this.setState({ content: toscaYaml })
+ } else {
+ this.setState({ content: 'No Tosca model Yaml available' })
+ }
+ });
+ } else {
+ this.setState({ content: 'Please select Tosca model to view the details' })
+ }
+ }
+
+ handleYamlContent(event) {
+ this.setState({ content: event.target.value });
+ }
+
+ handleClose() {
+ this.setState({ show: false });
+ this.props.history.push('/');
+ }
+
+ renderAlert() {
+ return (
+ <div>
+ <Alert variant="danger" show={ this.state.showFailAlert } onClose={ this.disableAlert } dismissible>
+ { this.state.showMessage }
+ </Alert>
+ </div>
+ );
+ }
+
+ handleAdd() {
+ LoopService.addOperationalPolicyType(this.state.loopCache.getLoopName(), this.state.selectedRowData.policyModelType, this.state.selectedRowData.version)
+ .then(pars => {
+ this.props.loadLoopFunction(this.state.loopCache.getLoopName());
+ this.handleClose();
+ })
+ .catch(error => {
+ this.setState({ showFailAlert: true, showMessage: "Adding failed with error: " + error.message });
+ });
+ }
+
+ handleRemove() {
+ LoopService.removeOperationalPolicyType(this.state.loopCache.getLoopName(), this.state.selectedRowData.policyModelType, this.state.selectedRowData.version, this.state.selectedRowData.policyName);
+ this.props.loadLoopFunction(this.state.loopCache.getLoopName());
+ this.handleClose();
+ }
+
+ render() {
+ return (
+ <ModalStyled size="xl" show={ this.state.show } onHide={ this.handleClose } backdrop="static" keyboard={ false }>
+ <Modal.Header closeButton>
+ <Modal.Title>Modify Loop Operational Policies</Modal.Title>
+ </Modal.Header>
+ <Tabs id="controlled-tab-example" activeKey={ this.state.key } onSelect={ key => this.setState({ key, selectedRowData: {} }) }>
+ <Tab eventKey="add" title="Add Operational Policies">
+ <Modal.Body>
+ <MaterialTable
+ title={ "View Tosca Policy Models" }
+ data={ this.state.toscaPolicyModelsData }
+ columns={ this.state.toscaColumns }
+ icons={ this.state.tableIcons }
+ onRowClick={ (event, rowData) => {
+ this.getToscaPolicyModelYaml(rowData.policyModelType, rowData.version);
+ this.setState({ selectedRowData: rowData })
+ } }
+ options={ {
+ headerStyle: rowHeaderStyle,
+ rowStyle: rowData => ({
+ backgroundColor: (this.state.selectedRowData !== {} && this.state.selectedRowData.tableData !== undefined
+ && this.state.selectedRowData.tableData.id === rowData.tableData.id) ? '#EEE' : '#FFF'
+ })
+ } }
+ />
+ <div>
+ <TextModal value={ this.state.content } onChange={ this.handleYamlContent }/>
+ </div>
+ </Modal.Body>
+ { this.renderAlert() }
+ </Tab>
+ <Tab eventKey="remove" title="Remove Operational Policies">
+ <Modal.Body>
+ <MaterialTable
+ title={ "Tosca Policy Models already added" }
+ data={ this.state.selectedPolicyModelsData }
+ columns={ this.state.toscaColumns }
+ icons={ this.state.tableIcons }
+ onRowClick={ (event, rowData) => {
+ this.setState({ selectedRowData: rowData })
+ } }
+ options={ {
+ headerStyle: rowHeaderStyle,
+ rowStyle: rowData => ({
+ backgroundColor: (this.state.selectedRowData !== {} && this.state.selectedRowData.tableData !== undefined
+ && this.state.selectedRowData.tableData.id === rowData.tableData.id) ? '#EEE' : '#FFF'
+ })
+ } }
+ />
+ </Modal.Body>
+ </Tab>
+ </Tabs>
+ <Modal.Footer>
+ <Button variant="secondary" type="null" onClick={ this.handleClose }>Cancel</Button>
+ <Button variant="primary" disabled={ (this.state.key === "remove") } type="submit" onClick={ this.handleAdd }>Add</Button>
+ <Button variant="primary" disabled={ (this.state.key === "add") } type="submit" onClick={ this.handleRemove }>Remove</Button>
+ </Modal.Footer>
+
+ </ModalStyled>
+ );
+ }
+}
diff --git a/gui-clamp/ui-react/src/components/dialogs/Loop/ModifyLoopModal.test.js b/gui-clamp/ui-react/src/components/dialogs/Loop/ModifyLoopModal.test.js
new file mode 100644
index 0000000..79267af
--- /dev/null
+++ b/gui-clamp/ui-react/src/components/dialogs/Loop/ModifyLoopModal.test.js
@@ -0,0 +1,109 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP CLAMP
+ * ================================================================================
+ * Copyright (C) 2020 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 ModifyLoopModal from './ModifyLoopModal';
+import LoopCache from '../../../api/LoopCache';
+import LoopService from '../../../api/LoopService';
+import PolicyToscaService from '../../../api/PolicyToscaService';
+
+describe('Verify ModifyLoopModal', () => {
+ beforeEach(() => {
+ PolicyToscaService.getToscaPolicyModels = jest.fn().mockImplementation(() => {
+ return Promise.resolve([{
+ "policyModelType": "test",
+ "policyAcronym": "test",
+ "version": "1.0.0",
+ "updatedBy": "",
+ "updatedDate": ""
+ }]);
+ });
+ PolicyToscaService.getToscaPolicyModelYaml = jest.fn().mockImplementation(() => {
+ return Promise.resolve("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": {} }
+ }],
+ "globalPropertiesJson": {
+ "dcaeDeployParameters": {
+ "testMs": {
+ "location_id": "",
+ "policy_id": "TCA_h2NMX_v1_0_ResourceInstanceName1_tca"
+ }
+ }
+ }
+ });
+ const historyMock = { push: jest.fn() };
+ const flushPromises = () => new Promise(setImmediate);
+
+ it('Test handleClose', () => {
+ const handleClose = jest.spyOn(ModifyLoopModal.prototype, 'handleClose');
+ const component = mount(<ModifyLoopModal 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 getToscaPolicyModelYaml', async () => {
+ const flushPromises = () => new Promise(setImmediate);
+ const component = mount(<ModifyLoopModal history={ historyMock } loopCache={ loopCache }/>)
+ component.setState({
+ "selectedRowData": { "tableData": { "id": 0 } }
+ });
+ const instance = component.instance();
+
+ instance.getToscaPolicyModelYaml("", "1.0.0");
+ expect(component.state('content')).toEqual("Please select Tosca model to view the details");
+
+ instance.getToscaPolicyModelYaml("test", "1.0.0");
+ await flushPromises();
+ expect(component.state('content')).toEqual("OK");
+
+ PolicyToscaService.getToscaPolicyModelYaml = jest.fn().mockImplementation(() => {
+ return Promise.resolve("");
+ });
+ instance.getToscaPolicyModelYaml("test", "1.0.0");
+ await flushPromises();
+ expect(component.state('content')).toEqual("No Tosca model Yaml available");
+ });
+
+ it('Test handleYamlContent', async () => {
+ const component = mount(<ModifyLoopModal loopCache={ loopCache }/>)
+ const instance = component.instance();
+
+ const event = { "target": { "value": "testValue" } }
+ instance.handleYamlContent(event);
+ expect(component.state('content')).toEqual("testValue");
+ });
+});
diff --git a/gui-clamp/ui-react/src/components/dialogs/Loop/OpenLoopModal.js b/gui-clamp/ui-react/src/components/dialogs/Loop/OpenLoopModal.js
new file mode 100644
index 0000000..b6407fb
--- /dev/null
+++ b/gui-clamp/ui-react/src/components/dialogs/Loop/OpenLoopModal.js
@@ -0,0 +1,139 @@
+/*-
+ * ============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 Select from 'react-select';
+import Button from 'react-bootstrap/Button';
+import Modal from 'react-bootstrap/Modal';
+import Form from 'react-bootstrap/Form';
+import Row from 'react-bootstrap/Row';
+import Col from 'react-bootstrap/Col';
+import FormCheck from 'react-bootstrap/FormCheck'
+import styled from 'styled-components';
+import LoopService from '../../../api/LoopService';
+import SvgGenerator from '../../loop_viewer/svg/SvgGenerator';
+import LoopCache from '../../../api/LoopCache';
+
+const ModalStyled = styled(Modal)`
+ background-color: transparent;
+`
+const CheckBoxStyled = styled(FormCheck.Input)`
+ margin-left: 3rem;
+`
+
+export default class OpenLoopModal extends React.Component {
+ constructor(props, context) {
+ super(props, context);
+
+ this.getLoopNames = this.getLoopNames.bind(this);
+ this.handleOpen = this.handleOpen.bind(this);
+ this.handleClose = this.handleClose.bind(this);
+ this.handleDropDownListChange = this.handleDropDownListChange.bind(this);
+ this.renderSvg = this.renderSvg.bind(this);
+ this.showReadOnly = props.showReadOnly !== undefined ? props.showReadOnly : true;
+ this.state = {
+ show: true,
+ chosenLoopName: '',
+ loopNames: [],
+ loopCacheOpened: new LoopCache({})
+ };
+ }
+
+ componentWillMount() {
+ this.getLoopNames();
+ }
+
+ handleClose() {
+ this.setState({ show: false });
+ this.props.history.push('/');
+ }
+
+ handleDropDownListChange(e) {
+ LoopService.getLoop(e.value).then(loop => {
+ this.setState({
+ chosenLoopName: e.value,
+ loopCacheOpened: new LoopCache(loop)
+ });
+ });
+ }
+
+ getLoopNames() {
+ LoopService.getLoopNames().then(loopNames => {
+ if (Object.entries(loopNames).length !== 0) {
+ const loopOptions = loopNames.filter(loopName => loopName !== 'undefined').map((loopName) => {
+ return { label: loopName, value: loopName }
+ });
+ this.setState({ loopNames: loopOptions })
+ }
+ });
+ }
+
+ handleOpen() {
+ console.info("Loop " + this.state.chosenLoopName + " is chosen");
+ this.handleClose();
+ this.props.loadLoopFunction(this.state.chosenLoopName);
+ }
+
+ renderSvg() {
+ return (
+ <SvgGenerator loopCache={ this.state.loopCacheOpened } clickable={ false } generatedFrom={ SvgGenerator.GENERATED_FROM_INSTANCE }/>
+ );
+ }
+
+ render() {
+ return (
+ <ModalStyled size="xl" show={ this.state.show } onHide={ this.handleClose } backdrop="static" keyboard={ false }>
+ <Modal.Header closeButton>
+ <Modal.Title>Open Model</Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Form.Group as={ Row } controlId="formPlaintextEmail">
+ <Form.Label column sm="2">Model Name:</Form.Label>
+ <Col sm="10">
+ <Select onChange={ this.handleDropDownListChange }
+ options={ this.state.loopNames }/>
+ </Col>
+ </Form.Group>
+ <Form.Group as={ Row } style={ { alignItems: 'center' } } controlId="formSvgPreview">
+ <Form.Label column sm="2">Model Preview:</Form.Label>
+ <Col sm="10">
+ { this.renderSvg() }
+ </Col>
+ </Form.Group>
+ { this.showReadOnly === true ?
+ <Form.Group as={ Row } controlId="formBasicCheckbox">
+ <Form.Check>
+ <FormCheck.Label>Read Only Mode:</FormCheck.Label>
+ <CheckBoxStyled style={ { marginLeft: '3.5em' } } type="checkbox"/>
+ </Form.Check>
+ </Form.Group>
+ : null }
+ </Modal.Body>
+ <Modal.Footer>
+ <Button variant="secondary" type="null" onClick={ this.handleClose }>Cancel</Button>
+ <Button variant="primary" type="submit" onClick={ this.handleOpen }>Open</Button>
+ </Modal.Footer>
+ </ModalStyled>
+
+ );
+ }
+}
diff --git a/gui-clamp/ui-react/src/components/dialogs/Loop/OpenLoopModal.test.js b/gui-clamp/ui-react/src/components/dialogs/Loop/OpenLoopModal.test.js
new file mode 100644
index 0000000..6b2dc4a
--- /dev/null
+++ b/gui-clamp/ui-react/src/components/dialogs/Loop/OpenLoopModal.test.js
@@ -0,0 +1,93 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP CLAMP
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights
+ * reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END============================================
+ * ===================================================================
+ *
+ */
+import React from 'react';
+import { shallow } from 'enzyme';
+import OpenLoopModal from './OpenLoopModal';
+import LoopService from '../../../api/LoopService';
+
+describe('Verify OpenLoopModal', () => {
+
+ beforeEach(() => {
+ fetch.resetMocks();
+ fetch.mockResponse(JSON.stringify([
+ "LOOP_gmtAS_v1_0_ResourceInstanceName1_tca",
+ "LOOP_gmtAS_v1_0_ResourceInstanceName1_tca_3",
+ "LOOP_gmtAS_v1_0_ResourceInstanceName2_tca_2"
+ ]));
+ });
+
+ it('Test the render method', () => {
+
+ const component = shallow(<OpenLoopModal/>);
+ expect(component).toMatchSnapshot();
+ });
+
+ it('Onchange event', async () => {
+ const flushPromises = () => new Promise(setImmediate);
+ LoopService.getLoop = jest.fn().mockImplementation(() => {
+ return Promise.resolve({
+ ok: true,
+ status: 200,
+ json: () => {
+ }
+ });
+ });
+ const event = { value: 'LOOP_gmtAS_v1_0_ResourceInstanceName1_tca_3' };
+ const component = shallow(<OpenLoopModal/>);
+ component.find('StateManager').simulate('change', event);
+ await flushPromises();
+ component.update();
+ expect(component.state('chosenLoopName')).toEqual("LOOP_gmtAS_v1_0_ResourceInstanceName1_tca_3");
+ });
+
+
+ it('Test handleClose', () => {
+ const historyMock = { push: jest.fn() };
+ const handleClose = jest.spyOn(OpenLoopModal.prototype, 'handleClose');
+ const component = shallow(<OpenLoopModal history={ historyMock }/>)
+
+ component.find('[variant="secondary"]').prop('onClick')();
+
+ expect(handleClose).toHaveBeenCalledTimes(1);
+ expect(component.state('show')).toEqual(false);
+ expect(historyMock.push.mock.calls[0]).toEqual(['/']);
+
+ handleClose.mockClear();
+ });
+
+ it('Test handleSubmit', () => {
+ const historyMock = { push: jest.fn() };
+ const loadLoopFunction = jest.fn();
+ const handleOpen = jest.spyOn(OpenLoopModal.prototype, 'handleOpen');
+ const component = shallow(<OpenLoopModal history={ historyMock } loadLoopFunction={ loadLoopFunction }/>)
+
+ component.find('[variant="primary"]').prop('onClick')();
+
+ expect(handleOpen).toHaveBeenCalledTimes(1);
+ expect(component.state('show')).toEqual(false);
+ expect(historyMock.push.mock.calls[0]).toEqual(['/']);
+
+ handleOpen.mockClear();
+ });
+
+});
diff --git a/gui-clamp/ui-react/src/components/dialogs/Loop/__snapshots__/CreateLoopModal.test.js.snap b/gui-clamp/ui-react/src/components/dialogs/Loop/__snapshots__/CreateLoopModal.test.js.snap
new file mode 100644
index 0000000..1ba9dba
--- /dev/null
+++ b/gui-clamp/ui-react/src/components/dialogs/Loop/__snapshots__/CreateLoopModal.test.js.snap
@@ -0,0 +1,167 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Verify CreateLoopModal Test the render method 1`] = `
+<Styled(Modal)
+ backdrop="static"
+ keyboard={false}
+ onHide={[Function]}
+ show={true}
+ size="xl"
+>
+ <ModalHeader
+ closeButton={true}
+ closeLabel="Close"
+ >
+ <ModalTitle>
+ Create Model
+ </ModalTitle>
+ </ModalHeader>
+ <ModalBody>
+ <FormGroup
+ as={
+ Object {
+ "$$typeof": Symbol(react.forward_ref),
+ "defaultProps": Object {
+ "noGutters": false,
+ },
+ "render": [Function],
+ }
+ }
+ controlId="formPlaintextEmail"
+ >
+ <FormLabel
+ column={true}
+ sm="2"
+ srOnly={false}
+ >
+ Template Name:
+ </FormLabel>
+ <Col
+ sm="10"
+ >
+ <StateManager
+ defaultInputValue=""
+ defaultMenuIsOpen={false}
+ defaultValue={null}
+ onChange={[Function]}
+ options={Array []}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ as={
+ Object {
+ "$$typeof": Symbol(react.forward_ref),
+ "defaultProps": Object {
+ "noGutters": false,
+ },
+ "render": [Function],
+ }
+ }
+ controlId="formSvgPreview"
+ style={
+ Object {
+ "alignItems": "center",
+ }
+ }
+ >
+ <FormLabel
+ column={true}
+ sm="2"
+ srOnly={false}
+ >
+ Model Preview:
+ </FormLabel>
+ <Col
+ sm="10"
+ >
+ <withRouter(SvgGenerator)
+ clickable={false}
+ generatedFrom="TEMPLATE"
+ loopCache={
+ LoopCache {
+ "loopJsonCache": Object {},
+ }
+ }
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ as={
+ Object {
+ "$$typeof": Symbol(react.forward_ref),
+ "defaultProps": Object {
+ "noGutters": false,
+ },
+ "render": [Function],
+ }
+ }
+ controlId="formPlaintextEmail"
+ >
+ <FormLabel
+ column={true}
+ sm="2"
+ srOnly={false}
+ >
+ Model Name:
+ </FormLabel>
+ <input
+ onChange={[Function]}
+ sm="5"
+ style={
+ Object {
+ "marginLeft": "1em",
+ "width": "50%",
+ }
+ }
+ type="text"
+ value=""
+ />
+ <span
+ sm="5"
+ />
+ </FormGroup>
+ <FormGroup
+ as={
+ Object {
+ "$$typeof": Symbol(react.forward_ref),
+ "defaultProps": Object {
+ "noGutters": false,
+ },
+ "render": [Function],
+ }
+ }
+ controlId="formPlaintextEmail"
+ >
+ <FormLabel
+ column={true}
+ sm="2"
+ srOnly={false}
+ >
+
+ </FormLabel>
+ <styled.div />
+ </FormGroup>
+ </ModalBody>
+ <ModalFooter>
+ <Button
+ active={false}
+ disabled={false}
+ onClick={[Function]}
+ type="null"
+ variant="secondary"
+ >
+ Cancel
+ </Button>
+ <Button
+ active={false}
+ disabled={false}
+ onClick={[Function]}
+ type="submit"
+ variant="primary"
+ >
+ Create
+ </Button>
+ </ModalFooter>
+</Styled(Modal)>
+`;
diff --git a/gui-clamp/ui-react/src/components/dialogs/Loop/__snapshots__/DeployLoopModal.test.js.snap b/gui-clamp/ui-react/src/components/dialogs/Loop/__snapshots__/DeployLoopModal.test.js.snap
new file mode 100644
index 0000000..4779ced
--- /dev/null
+++ b/gui-clamp/ui-react/src/components/dialogs/Loop/__snapshots__/DeployLoopModal.test.js.snap
@@ -0,0 +1,83 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Verify DeployLoopModal Test the render method 1`] = `
+<Styled(Modal)
+ backdrop="static"
+ keyboard={false}
+ onHide={[Function]}
+ show={true}
+ size="lg"
+>
+ <ModalHeader
+ closeButton={true}
+ closeLabel="Close"
+ >
+ <ModalTitle>
+ Deployment parameters
+ </ModalTitle>
+ </ModalHeader>
+ <Tabs
+ activeKey="testMs"
+ id="controlled-tab-example"
+ mountOnEnter={false}
+ onSelect={[Function]}
+ unmountOnExit={false}
+ variant="tabs"
+ >
+ <Tab
+ eventKey="testMs"
+ title="testMs"
+ >
+ <Styled(FormGroup)>
+ <FormLabel
+ column={false}
+ srOnly={false}
+ >
+ location_id
+ </FormLabel>
+ <FormControl
+ defaultValue=""
+ name="location_id"
+ onChange={[Function]}
+ type="text"
+ />
+ </Styled(FormGroup)>
+ <Styled(FormGroup)>
+ <FormLabel
+ column={false}
+ srOnly={false}
+ >
+ policy_id
+ </FormLabel>
+ <FormControl
+ defaultValue="TCA_h2NMX_v1_0_ResourceInstanceName1_tca"
+ name="policy_id"
+ onChange={[Function]}
+ type="text"
+ />
+ </Styled(FormGroup)>
+ </Tab>
+ </Tabs>
+ <div />
+ <ModalFooter>
+ <Button
+ active={false}
+ disabled={false}
+ onClick={[Function]}
+ type="null"
+ variant="secondary"
+ >
+ Cancel
+ </Button>
+ <Button
+ active={false}
+ disabled={false}
+ onClick={[Function]}
+ type="submit"
+ variant="primary"
+ >
+ Deploy
+ </Button>
+ </ModalFooter>
+</Styled(Modal)>
+`;
diff --git a/gui-clamp/ui-react/src/components/dialogs/Loop/__snapshots__/LoopPropertiesModal.test.js.snap b/gui-clamp/ui-react/src/components/dialogs/Loop/__snapshots__/LoopPropertiesModal.test.js.snap
new file mode 100644
index 0000000..3baaa57
--- /dev/null
+++ b/gui-clamp/ui-react/src/components/dialogs/Loop/__snapshots__/LoopPropertiesModal.test.js.snap
@@ -0,0 +1,61 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Verify LoopPropertiesModal Test the render method 1`] = `
+<Styled(Modal)
+ backdrop="static"
+ keyboard={false}
+ onHide={[Function]}
+ show={true}
+ size="lg"
+>
+ <ModalHeader
+ closeButton={true}
+ closeLabel="Close"
+ >
+ <ModalTitle>
+ Model Properties
+ </ModalTitle>
+ </ModalHeader>
+ <ModalBody>
+ <Form
+ inline={false}
+ >
+ <FormGroup>
+ <FormLabel
+ column={false}
+ srOnly={false}
+ >
+ Deploy Parameters
+ </FormLabel>
+ <FormControl
+ as="textarea"
+ defaultValue="{\\"location_id\\":\\"\\",\\"policy_id\\":\\"TCA_h2NMX_v1_0_ResourceInstanceName1_tca\\"}"
+ name="dcaeDeployParameters"
+ onChange={[Function]}
+ rows="3"
+ />
+ </FormGroup>
+ </Form>
+ </ModalBody>
+ <ModalFooter>
+ <Button
+ active={false}
+ disabled={false}
+ onClick={[Function]}
+ type="null"
+ variant="secondary"
+ >
+ Cancel
+ </Button>
+ <Button
+ active={false}
+ disabled={false}
+ onClick={[Function]}
+ type="submit"
+ variant="primary"
+ >
+ Save Changes
+ </Button>
+ </ModalFooter>
+</Styled(Modal)>
+`;
diff --git a/gui-clamp/ui-react/src/components/dialogs/Loop/__snapshots__/OpenLoopModal.test.js.snap b/gui-clamp/ui-react/src/components/dialogs/Loop/__snapshots__/OpenLoopModal.test.js.snap
new file mode 100644
index 0000000..581fd0e
--- /dev/null
+++ b/gui-clamp/ui-react/src/components/dialogs/Loop/__snapshots__/OpenLoopModal.test.js.snap
@@ -0,0 +1,137 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Verify OpenLoopModal Test the render method 1`] = `
+<Styled(Modal)
+ backdrop="static"
+ keyboard={false}
+ onHide={[Function]}
+ show={true}
+ size="xl"
+>
+ <ModalHeader
+ closeButton={true}
+ closeLabel="Close"
+ >
+ <ModalTitle>
+ Open Model
+ </ModalTitle>
+ </ModalHeader>
+ <ModalBody>
+ <FormGroup
+ as={
+ Object {
+ "$$typeof": Symbol(react.forward_ref),
+ "defaultProps": Object {
+ "noGutters": false,
+ },
+ "render": [Function],
+ }
+ }
+ controlId="formPlaintextEmail"
+ >
+ <FormLabel
+ column={true}
+ sm="2"
+ srOnly={false}
+ >
+ Model Name:
+ </FormLabel>
+ <Col
+ sm="10"
+ >
+ <StateManager
+ defaultInputValue=""
+ defaultMenuIsOpen={false}
+ defaultValue={null}
+ onChange={[Function]}
+ options={Array []}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ as={
+ Object {
+ "$$typeof": Symbol(react.forward_ref),
+ "defaultProps": Object {
+ "noGutters": false,
+ },
+ "render": [Function],
+ }
+ }
+ controlId="formSvgPreview"
+ style={
+ Object {
+ "alignItems": "center",
+ }
+ }
+ >
+ <FormLabel
+ column={true}
+ sm="2"
+ srOnly={false}
+ >
+ Model Preview:
+ </FormLabel>
+ <Col
+ sm="10"
+ >
+ <withRouter(SvgGenerator)
+ clickable={false}
+ generatedFrom="INSTANCE"
+ loopCache={
+ LoopCache {
+ "loopJsonCache": Object {},
+ }
+ }
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
+ as={
+ Object {
+ "$$typeof": Symbol(react.forward_ref),
+ "defaultProps": Object {
+ "noGutters": false,
+ },
+ "render": [Function],
+ }
+ }
+ controlId="formBasicCheckbox"
+ >
+ <FormCheck>
+ <FormCheckLabel>
+ Read Only Mode:
+ </FormCheckLabel>
+ <Styled(FormCheckInput)
+ style={
+ Object {
+ "marginLeft": "3.5em",
+ }
+ }
+ type="checkbox"
+ />
+ </FormCheck>
+ </FormGroup>
+ </ModalBody>
+ <ModalFooter>
+ <Button
+ active={false}
+ disabled={false}
+ onClick={[Function]}
+ type="null"
+ variant="secondary"
+ >
+ Cancel
+ </Button>
+ <Button
+ active={false}
+ disabled={false}
+ onClick={[Function]}
+ type="submit"
+ variant="primary"
+ >
+ Open
+ </Button>
+ </ModalFooter>
+</Styled(Modal)>
+`;