Add Upload Control Loop Instantiation 83/122583/2
authorbrunomilitzer <bruno.militzer@est.tech>
Thu, 8 Jul 2021 14:33:55 +0000 (15:33 +0100)
committerbrunomilitzer <bruno.militzer@est.tech>
Tue, 13 Jul 2021 14:20:41 +0000 (15:20 +0100)
Created Functionality to Upload JSON file to Control Loop Instantiation.

Issue-ID: POLICY-3436
Change-Id: Iefd538c91154b7e61615ab63b440378e2feea502
Signed-off-by: brunomilitzer <bruno.militzer@est.tech>
21 files changed:
gui-clamp/ui-react-lib/libIndex.js
gui-clamp/ui-react/package.json
gui-clamp/ui-react/src/LoopUI.js
gui-clamp/ui-react/src/__snapshots__/LoopUI.test.js.snap
gui-clamp/ui-react/src/__snapshots__/OnapClamp.test.js.snap
gui-clamp/ui-react/src/api/ControlLoopService.js
gui-clamp/ui-react/src/components/dialogs/ControlLoop/InstantiationElementItem.js [moved from gui-clamp/ui-react/src/components/dialogs/ControlLoop/ControlLoopElementItem.js with 97% similarity]
gui-clamp/ui-react/src/components/dialogs/ControlLoop/InstantiationElements.js [moved from gui-clamp/ui-react/src/components/dialogs/ControlLoop/ControlLoopElements.js with 81% similarity]
gui-clamp/ui-react/src/components/dialogs/ControlLoop/InstantiationItem.js [moved from gui-clamp/ui-react/src/components/dialogs/ControlLoop/ControlLoopItem.js with 97% similarity]
gui-clamp/ui-react/src/components/dialogs/ControlLoop/MonitorInstantiation.js [moved from gui-clamp/ui-react/src/components/dialogs/ControlLoop/MonitoringControlLoopModal.js with 75% similarity]
gui-clamp/ui-react/src/components/dialogs/ControlLoop/MonitorInstantiation.test.js [new file with mode: 0644]
gui-clamp/ui-react/src/components/dialogs/ControlLoop/UploadToscaInstantiation.js [new file with mode: 0644]
gui-clamp/ui-react/src/components/dialogs/ControlLoop/UploadToscaInstantiation.test.js [new file with mode: 0644]
gui-clamp/ui-react/src/components/dialogs/ControlLoop/UploadToscaInstantiationFile.js [new file with mode: 0644]
gui-clamp/ui-react/src/components/dialogs/ControlLoop/UploadToscaInstantiationFile.test.js [new file with mode: 0644]
gui-clamp/ui-react/src/components/dialogs/ControlLoop/__snapshots__/MonitorInstantiation.test.js.snap [new file with mode: 0644]
gui-clamp/ui-react/src/components/dialogs/ControlLoop/__snapshots__/UploadToscaInstantiation.test.js.snap [new file with mode: 0644]
gui-clamp/ui-react/src/components/dialogs/ControlLoop/__snapshots__/UploadToscaInstantiationFile.test.js.snap [new file with mode: 0644]
gui-clamp/ui-react/src/components/dialogs/Policy/toscaInstantiationData.test.json [new file with mode: 0644]
gui-clamp/ui-react/src/components/menu/MenuBar.js
gui-clamp/ui-react/src/components/menu/__snapshots__/MenuBar.test.js.snap

index 179b882..b0981b0 100644 (file)
@@ -55,7 +55,11 @@ export { default as ViewAllPolicies } from '../ui-react/src/components/dialogs/P
 export { default as PolicyDeploymentEditor } from '../ui-react/src/components/dialogs/Policy/PolicyDeploymentEditor';
 export { default as PoliciesTreeViewer } from '../ui-react/src/components/dialogs/Policy/PoliciesTreeViewer';
 export { default as PolicyToscaFileSelector } from '../ui-react/src/components/dialogs/Policy/PolicyToscaFileSelector';
-export { default as MonitoringControlLoopModal } from '../ui-react/src/components/dialogs/ControlLoop/MonitoringControlLoopModal';
+export { default as MonitorInstantiation } from '../ui-react/src/components/dialogs/ControlLoop/MonitorInstantiation';
+export { default as InstantiationItem } from '../ui-react/src/components/dialogs/ControlLoop/InstantiationItem';
+export { default as InstantiationElements } from '../ui-react/src/components/dialogs/ControlLoop/InstantiationElements';
+export { default as InstantiationElementItem } from '../ui-react/src/components/dialogs/ControlLoop/InstantiationElementItem';
+export { default as UploadToscaInstantiation } from '../ui-react/src/components/dialogs/ControlLoop/UploadToscaInstantiation';
 export { default as ControlLoopService } from '../ui-react/src/api/ControlLoopService';
 export { default as GetLocalToscaFileForUpload } from '../ui-react/src/components/dialogs/ControlLoop/GetLocalToscaFileForUpload';
 export { default as ReadAndConvertYaml } from '../ui-react/src/components/dialogs/ControlLoop/ReadAndConvertYaml';
index 5754d44..6b67e3f 100644 (file)
@@ -47,7 +47,7 @@
         "@babel/preset-env": "7.14.7",
         "@babel/preset-react": "7.14.5",
         "@wojtekmaj/enzyme-adapter-react-17": "0.6.2",
-        "babel-jest": "27.0.6",
+        "babel-jest": "26.6.0",
         "enzyme": "3.11.0",
         "enzyme-adapter-react-17-updated": "1.0.2",
         "enzyme-to-json": "3.6.2",
index 752c89d..109a0b3 100644 (file)
@@ -52,8 +52,9 @@ import Spinner from 'react-bootstrap/Spinner';
 
 import { Link } from 'react-router-dom';
 import ReadAndConvertYaml from "./components/dialogs/ControlLoop/ReadAndConvertYaml";
-import MonitoringControlLoopModal from "./components/dialogs/ControlLoop/MonitoringControlLoopModal";
+import MonitorInstantiation from "./components/dialogs/ControlLoop/MonitorInstantiation";
 import GetLocalToscaFileForUpload from "./components/dialogs/ControlLoop/GetLocalToscaFileForUpload";
+import UploadToscaInstantiation from "./components/dialogs/ControlLoop/UploadToscaInstantiation";
 
 const StyledMainDiv = styled.div`
   background-color: ${ props => props.theme.backgroundColor };
@@ -382,7 +383,8 @@ export default class LoopUI extends React.Component {
                                                                               showSucAlert={ this.showSucAlert }
                                                                               showFailAlert={ this.showFailAlert }/>) }
         />
-        <Route path="/monitoring" render={ (routeProps) => (<MonitoringControlLoopModal { ...routeProps } />) }/>
+        <Route path="/monitorInstantiation" render={ (routeProps) => (<MonitorInstantiation { ...routeProps } />) }/>
+        <Route path="/uploadToscaInstantiation" render={ (routeProps) => (<UploadToscaInstantiation { ...routeProps } />) }/>
       </React.Fragment>
     );
   }
index 6d25823..040fdaa 100644 (file)
@@ -82,7 +82,11 @@ exports[`Verify LoopUI Test the render method 1`] = `
     render={[Function]}
   />
   <Route
-    path="/monitoring"
+    path="/monitorInstantiation"
+    render={[Function]}
+  />
+  <Route
+    path="/uploadToscaInstantiation"
     render={[Function]}
   />
   <div />
index f5680b1..5d8a207 100644 (file)
@@ -111,7 +111,11 @@ exports[`Verify OnapClamp Test the render method 1`] = `
       render={[Function]}
     />
     <Route
-      path="/monitoring"
+      path="/monitorInstantiation"
+      render={[Function]}
+    />
+    <Route
+      path="/uploadToscaInstantiation"
       render={[Function]}
     />
     <div />
index 1882f78..30b0522 100644 (file)
@@ -19,7 +19,7 @@
 
 export default class ControlLoopService {
 
-  static async getControlLoopList(windowLocationPathname) {
+  static async fetchControlLoopInstantiation(windowLocationPathname) {
 
     return await fetch(windowLocationPathname + '/restservices/clds/v2/toscaControlLoop/getToscaInstantiation', {
       method: 'GET',
@@ -28,10 +28,10 @@ export default class ControlLoopService {
       },
       credentials: 'same-origin',
     }).then(response => {
-      console.log("getControlLoopList received " + response.status);
+      console.log("fetchControlLoopInstantiation received " + response.status);
 
       if (response.ok) {
-        console.info("getControlLoopList query successful");
+        console.info("fetchControlLoopInstantiation query successful");
         return response.json();
       } else {
         return response.text().then(responseBody => {
@@ -39,12 +39,26 @@ export default class ControlLoopService {
         });
       }
     }).catch(error => {
-      console.error("getControlLoopList error occurred ", error);
-      alert("getControlLoopList error occurred " + error);
+      console.error("fetchControlLoopInstantiation error occurred ", error);
+      alert("fetchControlLoopInstantiation error occurred " + error);
       return undefined;
     });
   }
 
+  static async uploadToscaInstantiation(toscaObject, windowLocationPathname) {
+
+    const response =  await fetch(windowLocationPathname + '/restservices/clds/v2/toscaControlLoop/postToscaInstantiation', {
+      method: 'POST',
+      headers: {
+        'Content-Type': 'application/json',
+      },
+      credentials: 'same-origin',
+      body: JSON.stringify(toscaObject)
+    });
+
+    return response;
+  }
+
   static async getToscaTemplate(name, version, windowLocationPathname) {
     const params = {
       name: name,
@@ -65,7 +65,7 @@ const ButtonStyle = styled(Button)`
   }
 `
 
-const ControlLoopElementItem = (props) => {
+const InstantiationElementItem = (props) => {
   const [title, setTitle] = useState("");
 
   useEffect(() => {
@@ -121,4 +121,4 @@ const ControlLoopElementItem = (props) => {
   );
 }
 
-export default ControlLoopElementItem;
+export default InstantiationElementItem;
@@ -18,9 +18,9 @@
  */
 
 import React, { useEffect, useState } from "react";
-import ControlLoopElementItem from "./ControlLoopElementItem";
+import InstantiationElementItem from "./InstantiationElementItem";
 
-const ControlLoopElements = (props) => {
+const InstantiationElements = (props) => {
   const [clElements, setClElements] = useState([]);
 
   useEffect(() => {
@@ -31,11 +31,11 @@ const ControlLoopElements = (props) => {
     <React.Fragment>
       {
         clElements.map((clEl, index) => (
-          <ControlLoopElementItem title={ clEl["definition"]["name"] } orderedState={ clEl["orderedState"] } key={ index } />
+          <InstantiationElementItem title={ clEl["definition"]["name"] } orderedState={ clEl["orderedState"] } key={ index } />
         ))
       }
     </React.Fragment>
   );
 }
 
-export default ControlLoopElements;
+export default InstantiationElements;
@@ -71,7 +71,7 @@ const ToggleButton = styled(Button)`
   }
 `
 
-const ControlLoopItem = (props) => {
+const InstantiationItem = (props) => {
   const toggleState = () => {
     switch (props.orderedState) {
       case 'UNINITIALISED':
@@ -125,4 +125,4 @@ const ControlLoopItem = (props) => {
   );
 }
 
-export default ControlLoopItem;
+export default InstantiationItem;
@@ -21,15 +21,15 @@ import React, { useEffect, useState } from "react";
 import styled from "styled-components";
 import Modal from "react-bootstrap/Modal";
 import Button from "react-bootstrap/Button";
-import ControlLoopItem from "./ControlLoopItem";
+import InstantiationItem from "./InstantiationItem";
 import ControlLoopService from "../../../api/ControlLoopService";
-import ControlLoopElements from "./ControlLoopElements";
+import InstantiationElements from "./InstantiationElements";
 
 const ModalStyled = styled(Modal)`
   background-color: transparent;
 `
 
-const MonitoringControlLoopModal = (props) => {
+const MonitorInstantiation = (props) => {
   const [show, setShow] = useState(true);
   const [controlLoopList, setControlLoopList] = useState([]);
   const [windowLocationPathname, setWindowLocationPathname] = useState('');
@@ -37,12 +37,13 @@ const MonitoringControlLoopModal = (props) => {
   useEffect(() => {
     setWindowLocationPathname(window.location.pathname);
 
-    ControlLoopService.getControlLoopList(windowLocationPathname).then(controlLoopList => {
+    ControlLoopService.fetchControlLoopInstantiation(windowLocationPathname).then(controlLoopList => {
       setControlLoopList(controlLoopList['controlLoopList']);
     });
   }, [])
 
   const handleClose = () => {
+    console.log('handleClose called');
     setShow(false);
     props.history.push('/');
   }
@@ -50,22 +51,22 @@ const MonitoringControlLoopModal = (props) => {
   return (
     <ModalStyled size="xl" show={ show } onHide={ handleClose } backdrop="static" keyboard={ false }>
       <Modal.Header closeButton>
-        <Modal.Title>Tosca Control Loop - Monitoring</Modal.Title>
+        <Modal.Title>Tosca Instantiation - Monitoring</Modal.Title>
       </Modal.Header>
       <Modal.Body>
         {
           controlLoopList.map((clList, index) => (
-            <ControlLoopItem title={ clList["name"] } orderedState={ clList["orderedState"] } index={ index } key={ index }>
-              <ControlLoopElements elements={ clList["elements"] } />
-            </ControlLoopItem>
+            <InstantiationItem title={ clList["name"] } orderedState={ clList["orderedState"] } index={ index } key={ index }>
+              <InstantiationElements elements={ clList["elements"] } />
+            </InstantiationItem>
           ))
         }
       </Modal.Body>
       <Modal.Footer>
-        <Button variant="secondary" type="null" onClick={ handleClose }>Cancel</Button>
+        <Button variant="secondary" type="null" onClick={ handleClose }>Close</Button>
       </Modal.Footer>
     </ModalStyled>
   )
 }
 
-export default MonitoringControlLoopModal;
+export default MonitorInstantiation;
diff --git a/gui-clamp/ui-react/src/components/dialogs/ControlLoop/MonitorInstantiation.test.js b/gui-clamp/ui-react/src/components/dialogs/ControlLoop/MonitorInstantiation.test.js
new file mode 100644 (file)
index 0000000..5e30924
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation.
+ *  ================================================================================
+ *  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.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+import { mount, shallow } from "enzyme";
+import toJson from "enzyme-to-json";
+import { createMemoryHistory } from "history";
+import React from "react";
+import MonitorInstantiation from "./MonitorInstantiation";
+import { act } from "react-dom/test-utils";
+
+describe('Verify MonitoringInstantiation', () => {
+
+  it("renders without crashing", () => {
+    shallow(<MonitorInstantiation/>);
+  });
+
+  it("renders correctly", () => {
+    const tree = shallow(<MonitorInstantiation/>);
+    expect(toJson(tree)).toMatchSnapshot();
+  });
+
+  it('should have a Button element', () => {
+    const container = shallow(<MonitorInstantiation/>)
+    expect(container.find('Button').length).toEqual(1);
+  });
+
+  it('handleClose called when bottom button clicked', () => {
+    const history = createMemoryHistory();
+    const component = mount(<MonitorInstantiation history={ history }/>)
+    const logSpy = jest.spyOn(console, 'log');
+
+    act(() => {
+      component.find('[variant="secondary"]').simulate('click');
+      expect(logSpy).toHaveBeenCalledWith('handleClose called');
+    });
+  });
+
+  it('handleClose called when top-right button clicked', () => {
+    const history = createMemoryHistory();
+    const component = mount(<MonitorInstantiation history={ history }/>)
+    const logSpy = jest.spyOn(console, 'log');
+
+    act(() => {
+      component.find('[size="xl"]').get(0).props.onHide();
+      expect(logSpy).toHaveBeenCalledWith('handleClose called');
+    });
+  });
+});
diff --git a/gui-clamp/ui-react/src/components/dialogs/ControlLoop/UploadToscaInstantiation.js b/gui-clamp/ui-react/src/components/dialogs/ControlLoop/UploadToscaInstantiation.js
new file mode 100644 (file)
index 0000000..d9bbe8b
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation.
+ *  ================================================================================
+ *  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.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+import React, { useState } from "react";
+import Modal from "react-bootstrap/Modal";
+import Button from "react-bootstrap/Button";
+import styled from "styled-components";
+import Row from "react-bootstrap/Row";
+import Form from "react-bootstrap/Form";
+import UploadToscaInstantiationFile from "./UploadToscaInstantiationFile";
+import jsYaml from "js-yaml";
+import Alert from "react-bootstrap/Alert";
+
+const ModalStyled = styled(Modal)`
+  background-color: transparent;
+`
+const StyledMessagesDiv = styled.div`
+  overflow: auto;
+  min-width: 100%;
+  max-height: 300px;
+  padding: 5px 5px 0px 5px;
+  text-align: center;
+`
+
+const UploadToscaInstantiation = (props) => {
+  const [show, setShow] = useState(true);
+  const [fileIsSelected, setFileIsSelected] = useState(false);
+  const [selectedFile, setSelectedFile] = useState();
+  const [jsonObject, setJsonObject] = useState([]);
+  const [alertMessages, setAlertMessages] = useState();
+
+  const fileUploadHandler = (event) => {
+    event.preventDefault();
+    console.log('fileUploadHandler called');
+
+    const file = event.currentTarget.files[0];
+
+    if (file !== undefined) {
+      console.log('fileDefined called');
+      setSelectedFile(file);
+      setFileIsSelected(true);
+
+      const fileReader = new FileReader();
+      fileReader.onload = () => {
+        const jsonFile = jsYaml.load(fileReader.result, 'utf8')
+        setJsonObject(jsonFile)
+      }
+
+      fileReader.readAsText(file);
+    } else {
+      console.log('fileUndefined called');
+    }
+  }
+
+  const onResponseReceivedHandler = async (response) => {
+    console.log('onResponseReceivedHandler called');
+
+    if (await response.ok) {
+      setAlertMessages(<Alert variant="success">
+        <Alert.Heading>Upload Success</Alert.Heading>
+        <p>Tosca Instantiation from { selectedFile.name } was Successfully Uploaded</p>
+        <hr/>
+        <p>Type: { selectedFile.type }</p><p>Size: { +selectedFile.size / 1000 }Kb</p>
+      </Alert>);
+    } else {
+      setAlertMessages(<Alert variant="danger">
+        <Alert.Heading>Upload Failure</Alert.Heading>
+        <p>Tosca Instantiation from { selectedFile.name } failed to upload</p>
+        <p>Status code: { await response.status }: { response.statusText }</p>
+        <p>Response Text: { await response.text() }</p>
+        <hr/>
+        <p>Type: { selectedFile.type }</p><p>Size: { +selectedFile.size / 1000 }Kb</p>
+      </Alert>);
+    }
+  }
+
+  const handleClose = () => {
+    console.log('handleClose called');
+    setShow(false);
+    props.history.push('/');
+  }
+
+  return (
+    <ModalStyled size="lg" show={ show } onHide={ handleClose } backdrop="static" keyboard={ false }>
+      <Modal.Header closeButton>
+        <Modal.Title>Tosca Control Loop - Create Instantiation</Modal.Title>
+      </Modal.Header>
+      <div style={ { padding: '5px 5px 0px 5px' } }>
+        <Modal.Body>
+          <Form style={ { paddingTop: '20px' } }>
+            <Form.Group as={ Row }>
+              <Form.File
+                type="file"
+                className="custom-file-label"
+                id="inputGroupFile01"
+                onChange={ fileUploadHandler }
+                custom
+                accept=".yaml,.yml,.json"
+                label={ fileIsSelected ? selectedFile.name : 'Please select a file' }></Form.File>
+              <UploadToscaInstantiationFile
+                jsonObject={jsonObject}
+                onResponseReceived={onResponseReceivedHandler}/>
+              <Form.Text>Only .yaml, .yml and .json files are supported</Form.Text>
+            </Form.Group>
+            <Form.Group as={ Row }>
+              <StyledMessagesDiv>
+                { alertMessages }
+              </StyledMessagesDiv>
+            </Form.Group>
+          </Form>
+        </Modal.Body>
+      </div>
+      <Modal.Footer>
+        <Button variant="secondary" type="null" onClick={ handleClose }>Close</Button>
+      </Modal.Footer>
+    </ModalStyled>
+  )
+}
+
+export default UploadToscaInstantiation;
diff --git a/gui-clamp/ui-react/src/components/dialogs/ControlLoop/UploadToscaInstantiation.test.js b/gui-clamp/ui-react/src/components/dialogs/ControlLoop/UploadToscaInstantiation.test.js
new file mode 100644 (file)
index 0000000..79daafb
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation.
+ *  ================================================================================
+ *  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.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+import { mount, shallow } from "enzyme";
+import React from "react";
+import toJson from "enzyme-to-json";
+import UploadToscaInstantiation from "./UploadToscaInstantiation";
+import { createMemoryHistory } from "history";
+import { act } from "react-dom/test-utils";
+
+describe("Verify UploadToscaInstantiation", () => {
+  const fs = require("fs");
+  const jsonFile = fs.readFileSync("src/components/dialogs/Policy/toscaInstantiationData.test.json");
+  const file = new Blob([jsonFile], { type: 'file' });
+
+  it("renders without crashing", () => {
+    shallow(<UploadToscaInstantiation/>);
+  });
+
+  it("renders correctly", () => {
+    const tree = shallow(<UploadToscaInstantiation/>);
+    expect(toJson(tree)).toMatchSnapshot();
+  });
+
+  it('should have a Button element', () => {
+    const container = shallow(<UploadToscaInstantiation/>)
+    expect(container.find('Button').length).toEqual(1);
+  });
+
+  it('handleClose called when bottom button clicked', () => {
+    const history = createMemoryHistory();
+    const component = mount(<UploadToscaInstantiation history={ history }/>)
+    const logSpy = jest.spyOn(console, 'log');
+
+
+    act(() => {
+      component.find('[variant="secondary"]').simulate('click');
+      expect(logSpy).toHaveBeenCalledWith('handleClose called');
+    });
+  });
+
+  it('handleClose called when top-right button clicked', () => {
+    const history = createMemoryHistory();
+    const component = mount(<UploadToscaInstantiation history={ history }/>)
+    const logSpy = jest.spyOn(console, 'log');
+
+
+    act(() => {
+      component.find('[size="lg"]').get(0).props.onHide();
+      expect(logSpy).toHaveBeenCalledWith('handleClose called');
+    });
+  });
+
+  it('fileUploadHandler called when uploading a defined file', () => {
+    const component = mount(<UploadToscaInstantiation/>)
+    const logSpy = jest.spyOn(console, 'log');
+
+    const event = {
+      preventDefault() {
+      },
+      currentTarget: { files: [file] }
+    };
+
+    act(() => {
+      component.find('[type="file"]').get(0).props.onChange(event);
+      expect(logSpy).toHaveBeenCalledWith('fileUploadHandler called');
+      expect(logSpy).toHaveBeenCalledWith('fileDefined called');
+    });
+  });
+
+  it('fileUploadHandler called when uploading a undefined file', () => {
+    const component = mount(<UploadToscaInstantiation/>)
+    const logSpy = jest.spyOn(console, 'log');
+
+    const event = {
+      preventDefault() {
+      },
+      currentTarget: { files: [] }
+    };
+
+    act(() => {
+      component.find('[type="file"]').get(0).props.onChange(event);
+      expect(logSpy).toHaveBeenCalledWith('fileUndefined called');
+    });
+  });
+});
diff --git a/gui-clamp/ui-react/src/components/dialogs/ControlLoop/UploadToscaInstantiationFile.js b/gui-clamp/ui-react/src/components/dialogs/ControlLoop/UploadToscaInstantiationFile.js
new file mode 100644 (file)
index 0000000..5329bc0
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation.
+ *  ================================================================================
+ *  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.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+import React, { useEffect, useState } from "react";
+import Button from "react-bootstrap/Button";
+import ControlLoopService from "../../../api/ControlLoopService";
+
+const UploadToscaInstantiationFile = (props) => {
+  const [windowLocationPathName, setWindowLocationPathname] = useState('');
+
+  const postToscaInstantiationHandler = async (event) => {
+    event.preventDefault();
+    console.log('postToscaInstantiationHandler called');
+
+    setWindowLocationPathname(window.location.pathname);
+
+    const response = await ControlLoopService.uploadToscaInstantiation(props.jsonObject, windowLocationPathName)
+      .catch(error => error.message);
+
+    props.onResponseReceived(response);
+  }
+
+  return (
+    <React.Fragment>
+      <Button variant="primary"
+              block={ true }
+              type="submit"
+              onClick={ postToscaInstantiationHandler }>
+        Upload Tosca Instantiation
+      </Button>
+    </React.Fragment>
+  );
+}
+
+export default UploadToscaInstantiationFile;
+
diff --git a/gui-clamp/ui-react/src/components/dialogs/ControlLoop/UploadToscaInstantiationFile.test.js b/gui-clamp/ui-react/src/components/dialogs/ControlLoop/UploadToscaInstantiationFile.test.js
new file mode 100644 (file)
index 0000000..83ab8d5
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation.
+ *  ================================================================================
+ *  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.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+import { mount, shallow } from "enzyme";
+import React from "react";
+import UploadToscaInstantiationFile from "./UploadToscaInstantiationFile";
+import toJson from "enzyme-to-json";
+import { act } from "react-dom/test-utils";
+
+describe('Verify UploadToscaInstantiationFile', () => {
+
+  it("renders without crashing", () => {
+    shallow(<UploadToscaInstantiationFile />);
+  });
+
+  it("renders correctly", () => {
+    const tree = shallow(<UploadToscaInstantiationFile />);
+    expect(toJson(tree)).toMatchSnapshot();
+  });
+
+  it('should have a Button element', () => {
+    const container = shallow(<UploadToscaInstantiationFile />)
+    expect(container.find('Button').length).toEqual(1);
+  });
+
+  it('Button should have a specific text', () => {
+    const container = shallow(<UploadToscaInstantiationFile />)
+    expect(container.find('Button').text()).toBe('Upload Tosca Instantiation');
+  });
+
+  it('button should call postToscaInstantiationHandler when clicked', async () => {
+    const mockFunction = jest.fn(() => 'default');
+    const component = mount(<UploadToscaInstantiationFile onResponseReceived={mockFunction}/>)
+    const logSpy = jest.spyOn(console, 'log');
+    const event = {
+      preventDefault() {
+      }
+    };
+
+    await act(async () => {
+      component.find('[variant="primary"]').get(0).props.onClick(event);
+      expect(logSpy).toHaveBeenCalledWith('postToscaInstantiationHandler called');
+    });
+  });
+});
diff --git a/gui-clamp/ui-react/src/components/dialogs/ControlLoop/__snapshots__/MonitorInstantiation.test.js.snap b/gui-clamp/ui-react/src/components/dialogs/ControlLoop/__snapshots__/MonitorInstantiation.test.js.snap
new file mode 100644 (file)
index 0000000..6504b54
--- /dev/null
@@ -0,0 +1,32 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Verify MonitoringInstantiation renders correctly 1`] = `
+<Styled(Modal)
+  backdrop="static"
+  keyboard={false}
+  onHide={[Function]}
+  show={true}
+  size="xl"
+>
+  <ModalHeader
+    closeButton={true}
+    closeLabel="Close"
+  >
+    <ModalTitle>
+      Tosca Instantiation - Monitoring
+    </ModalTitle>
+  </ModalHeader>
+  <ModalBody />
+  <ModalFooter>
+    <Button
+      active={false}
+      disabled={false}
+      onClick={[Function]}
+      type="null"
+      variant="secondary"
+    >
+      Close
+    </Button>
+  </ModalFooter>
+</Styled(Modal)>
+`;
diff --git a/gui-clamp/ui-react/src/components/dialogs/ControlLoop/__snapshots__/UploadToscaInstantiation.test.js.snap b/gui-clamp/ui-react/src/components/dialogs/ControlLoop/__snapshots__/UploadToscaInstantiation.test.js.snap
new file mode 100644 (file)
index 0000000..77e4b99
--- /dev/null
@@ -0,0 +1,91 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Verify UploadToscaInstantiation renders correctly 1`] = `
+<Styled(Modal)
+  backdrop="static"
+  keyboard={false}
+  onHide={[Function]}
+  show={true}
+  size="lg"
+>
+  <ModalHeader
+    closeButton={true}
+    closeLabel="Close"
+  >
+    <ModalTitle>
+      Tosca Control Loop - Create Instantiation
+    </ModalTitle>
+  </ModalHeader>
+  <div
+    style={
+      Object {
+        "padding": "5px 5px 0px 5px",
+      }
+    }
+  >
+    <ModalBody>
+      <Form
+        inline={false}
+        style={
+          Object {
+            "paddingTop": "20px",
+          }
+        }
+      >
+        <FormGroup
+          as={
+            Object {
+              "$$typeof": Symbol(react.forward_ref),
+              "defaultProps": Object {
+                "noGutters": false,
+              },
+              "render": [Function],
+            }
+          }
+        >
+          <FormFile
+            accept=".yaml,.yml,.json"
+            className="custom-file-label"
+            custom={true}
+            id="inputGroupFile01"
+            label="Please select a file"
+            onChange={[Function]}
+            type="file"
+          />
+          <UploadToscaInstantiationFile
+            jsonObject={Array []}
+            onResponseReceived={[Function]}
+          />
+          <FormText>
+            Only .yaml, .yml and .json files are supported
+          </FormText>
+        </FormGroup>
+        <FormGroup
+          as={
+            Object {
+              "$$typeof": Symbol(react.forward_ref),
+              "defaultProps": Object {
+                "noGutters": false,
+              },
+              "render": [Function],
+            }
+          }
+        >
+          <styled.div />
+        </FormGroup>
+      </Form>
+    </ModalBody>
+  </div>
+  <ModalFooter>
+    <Button
+      active={false}
+      disabled={false}
+      onClick={[Function]}
+      type="null"
+      variant="secondary"
+    >
+      Close
+    </Button>
+  </ModalFooter>
+</Styled(Modal)>
+`;
diff --git a/gui-clamp/ui-react/src/components/dialogs/ControlLoop/__snapshots__/UploadToscaInstantiationFile.test.js.snap b/gui-clamp/ui-react/src/components/dialogs/ControlLoop/__snapshots__/UploadToscaInstantiationFile.test.js.snap
new file mode 100644 (file)
index 0000000..3ac5087
--- /dev/null
@@ -0,0 +1,16 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Verify UploadToscaInstantiationFile renders correctly 1`] = `
+<Fragment>
+  <Button
+    active={false}
+    block={true}
+    disabled={false}
+    onClick={[Function]}
+    type="submit"
+    variant="primary"
+  >
+    Upload Tosca Instantiation
+  </Button>
+</Fragment>
+`;
diff --git a/gui-clamp/ui-react/src/components/dialogs/Policy/toscaInstantiationData.test.json b/gui-clamp/ui-react/src/components/dialogs/Policy/toscaInstantiationData.test.json
new file mode 100644 (file)
index 0000000..d740203
--- /dev/null
@@ -0,0 +1,59 @@
+{
+    "controlLoopList": [
+        {
+            "name": "PMSHInstance0",
+            "version": "1.0.1",
+            "definition": {
+                "name": "org.onap.domain.pmsh.PMSHControlLoopDefinition",
+                "version": "1.2.3"
+            },
+            "state": "UNINITIALISED",
+            "orderedState": "UNINITIALISED",
+            "description": "PMSH control loop instance 0",
+            "elements": {
+                "709c62b3-8918-41b9-a747-d21eb79c6c20": {
+                    "id": "709c62b3-8918-41b9-a747-d21eb79c6c20",
+                    "definition": {
+                        "name": "org.onap.domain.pmsh.PMSH_DCAEMicroservice",
+                        "version": "1.2.3"
+                    },
+                    "participantType": {
+                        "name": "org.onap.dcae.controlloop.DCAEMicroserviceControlLoopParticipant",
+                        "version": "2.3.4"
+                    },
+                    "state": "UNINITIALISED",
+                    "orderedState": "UNINITIALISED",
+                    "description": "DCAE Control Loop Element for the PMSH instance 0 control loop"
+                },
+                "709c62b3-8918-41b9-a747-d21eb79c6c21": {
+                    "id": "709c62b3-8918-41b9-a747-d21eb79c6c21",
+                    "definition": {
+                        "name": "org.onap.domain.pmsh.PMSH_MonitoringPolicyControlLoopElement",
+                        "version": "1.2.3"
+                    },
+                    "participantType": {
+                        "name": "org.onap.policy.controlloop.PolicyControlLoopParticipant",
+                        "version": "2.3.1"
+                    },
+                    "state": "UNINITIALISED",
+                    "orderedState": "UNINITIALISED",
+                    "description": "Monitoring Policy Control Loop Element for the PMSH instance 0 control loop"
+                },
+                "709c62b3-8918-41b9-a747-d21eb79c6c22": {
+                    "id": "709c62b3-8918-41b9-a747-d21eb79c6c22",
+                    "definition": {
+                        "name": "org.onap.domain.pmsh.PMSH_OperationalPolicyControlLoopElement",
+                        "version": "1.2.3"
+                    },
+                    "participantType": {
+                        "name": "org.onap.policy.controlloop.PolicyControlLoopParticipant",
+                        "version": "2.3.1"
+                    },
+                    "state": "UNINITIALISED",
+                    "orderedState": "UNINITIALISED",
+                    "description": "Operational Policy Control Loop Element for the PMSH instance 0 control loop"
+                }
+            }
+        }
+    ]
+}
index 4574a2a..f32d6c8 100644 (file)
@@ -114,7 +114,9 @@ export default class MenuBar extends React.Component {
           <NavDropdown.Item as={ StyledLink } to="/undeploy" disabled={ this.state.disabled }>UnDeploy to DCAE (UNDEPLOY)</NavDropdown.Item>
         </StyledNavDropdown>
         <StyledNavDropdown title="TOSCA Control Loop">
-          <NavDropdown.Item as={ StyledLink } to="/monitoring">Monitoring Control Loop</NavDropdown.Item>
+          <NavDropdown.Header>Instantiation</NavDropdown.Header>
+          <NavDropdown.Item as={ StyledLink } to="/monitorInstantiation">Monitor Instantiation</NavDropdown.Item>
+          <NavDropdown.Item as={ StyledLink } to="/uploadToscaInstantiation">Upload Tosca Instantiation</NavDropdown.Item>
           <NavDropdown.Divider />
           <NavDropdown.Header>Commissioning</NavDropdown.Header>
           <NavDropdown.Item as={ StyledLink } to="/readToscaTemplate">View Tosca Template</NavDropdown.Item>
index 2712218..9543c12 100644 (file)
@@ -1005,6 +1005,76 @@ exports[`Verify MenuBar Test the render method 1`] = `
   <Styled(NavDropdown)
     title="TOSCA Control Loop"
   >
+    <DropdownHeader
+      role="heading"
+    >
+      Instantiation
+    </DropdownHeader>
+    <DropdownItem
+      as={
+        Object {
+          "$$typeof": Symbol(react.forward_ref),
+          "attrs": Array [],
+          "componentStyle": e {
+            "baseHash": -715527839,
+            "baseStyle": undefined,
+            "componentId": "sc-bdnxRM",
+            "isStatic": false,
+            "rules": Array [
+              "
+  color: ",
+              [Function],
+              ";
+  background-color: ",
+              [Function],
+              ";
+  font-weight: normal;
+  display: block;
+  width: 100%;
+  padding: .25rem 1.5rem;
+  clear: both;
+  text-align: inherit;
+  white-space: nowrap;
+  border: 0;
+
+  :hover {
+    text-decoration: none;
+    background-color: ",
+              [Function],
+              ";
+    color: ",
+              [Function],
+              ";
+  }
+",
+            ],
+            "staticRulesId": "",
+          },
+          "foldedComponentIds": Array [],
+          "render": [Function],
+          "shouldForwardProp": undefined,
+          "styledComponentId": "sc-bdnxRM",
+          "target": Object {
+            "$$typeof": Symbol(react.forward_ref),
+            "propTypes": Object {
+              "innerRef": [Function],
+              "onClick": [Function],
+              "replace": [Function],
+              "target": [Function],
+              "to": [Function],
+            },
+            "render": [Function],
+          },
+          "toString": [Function],
+          "warnTooManyClasses": [Function],
+          "withComponent": [Function],
+        }
+      }
+      disabled={false}
+      to="/monitorInstantiation"
+    >
+      Monitor Instantiation
+    </DropdownItem>
     <DropdownItem
       as={
         Object {
@@ -1066,9 +1136,9 @@ exports[`Verify MenuBar Test the render method 1`] = `
         }
       }
       disabled={false}
-      to="/monitoring"
+      to="/uploadToscaInstantiation"
     >
-      Monitoring Control Loop
+      Upload Tosca Instantiation
     </DropdownItem>
     <DropdownDivider
       role="separator"