diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/package.json b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/package.json index 469943d5b72..bb4a3574055 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/package.json +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/package.json @@ -13,9 +13,11 @@ "acorn": "^6.1.1", "antd": "^3.15.0", "axios": "^0.18.0", + "d3": "^5.9.2", "dagre": "^0.8.4", "keymirror": "^0.1.1", "react": "^16.8.4", + "react-d3-graph": "^2.0.2", "react-dom": "^16.8.4", "react-highlight-words": "^0.16.0", "react-router": "latest", diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/LifeCycle.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/LifeCycle.js index 1d76067d7f1..485ff30e29b 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/LifeCycle.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/LifeCycle.js @@ -1,32 +1,51 @@ import React from "react"; import LifeCycleGraph from "./LifeCycleGraph"; import {connect} from "react-redux"; -import {getLifecycle, openReleasesModal} from "../../../js/actions"; +import {getLifecycle, openLifecycleModal} from "../../../js/actions"; +import {Button} from "antd"; +import LifecycleModal from "./LifecycleModal"; const mapDispatchToProps = dispatch => ({ - getLifecycle: () => dispatch(getLifecycle()) + getLifecycle: () => dispatch(getLifecycle()), + openLifecycleModal: (nextState) => dispatch(openLifecycleModal(nextState)) }); const mapStateToProps = state => { return { - lifecycle: state.lifecycle + lifecycle: state.lifecycle, + currentStatus : state.release.currentStatus.toUpperCase(), + uuid : state.release.uuid }; }; class ConnectedLifeCycle extends React.Component { + + constructor(props){ + super(props); + + this.openModal = this.openModal.bind(this); + } + componentDidMount() { this.props.getLifecycle(); } + openModal() { + this.props.openLifecycleModal("IN_REVIEW"); + } + render() { - console.log(); const lifecycle = this.props.lifecycle; - if(lifecycle != null){ + if (lifecycle != null) { return ( - +
+ + + +
); - }else { + } else { return null; } } diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/LifeCycleGraph.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/LifeCycleGraph.js index f5051decd19..dd22c00eb48 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/LifeCycleGraph.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/LifeCycleGraph.js @@ -1,90 +1,117 @@ import React from "react"; -import * as SRD from "storm-react-diagrams"; -import "storm-react-diagrams/dist/style.min.css"; -import "./LifeCycle.css"; -import {distributeElements} from "../../../js/utils/dagre-utils.ts"; +import {Graph} from 'react-d3-graph'; +import {connect} from "react-redux"; +import {getLifecycle, openLifecycleModal} from "../../../js/actions"; + +const mapDispatchToProps = dispatch => ({ + openLifecycleModal: (nextState) => dispatch(openLifecycleModal(nextState)) +}); + +// the graph configuration, you only need to pass down properties +// that you want to override, otherwise default ones will be used +const myConfig = { + nodeHighlightBehavior: true, + directed: true, + height: 400, + d3: { + alphaTarget: 0.05, + gravity: -200, + linkLength: 200, + linkStrength: 1 + }, + node: { + color: "#d3d3d3", + fontColor: "black", + fontSize: 12, + fontWeight: "normal", + highlightFontSize: 12, + highlightFontWeight: "bold", + highlightStrokeColor: "SAME", + highlightStrokeWidth: 1.5, + labelProperty: "id", + mouseCursor: "pointer", + opacity: 1, + strokeColor: "none", + strokeWidth: 1.5, + svg: "", + symbolType: "circle", + }, + link: { + highlightColor: 'lightblue' + } +}; + + + +class ConnectedLifeCycleGraph extends React.Component { + + + constructor(props){ + super(props); + this.nextStates = null; + this.onClickNode = this.onClickNode.bind(this); + } -const inPortName = "IN"; -const outPortName = "OUT"; + onClickNode = function(nodeId) { + const nextStates = this.nextStates; + if(nextStates.includes(nodeId)){ + this.props.openLifecycleModal(nodeId); + } + }; -class LifeCycleGraph extends React.Component { render() { +// graph payload (with minimalist structure) const lifecycle = this.props.lifecycle; const nodes = []; const links = []; - - const engine = new SRD.DiagramEngine(); - engine.installDefaultFactories(); - - const model = new SRD.DiagramModel(); - const nextStates = lifecycle[this.props.currentStatus].proceedingStates; + this.nextStates = lifecycle[this.props.currentStatus].proceedingStates; Object.keys(lifecycle).forEach((stateName) => { + const state = lifecycle[stateName]; let color = "rgb(83, 92, 104)"; if (stateName === this.props.currentStatus) { - color = "rgb(192,255,0)"; - }else if(nextStates.includes(stateName)){ + color = "rgb(39, 174, 96)"; + } else if (this.nextStates.includes(stateName)) { color = "rgb(0,192,255)"; } - const node = createNode(stateName, color); - // node.addPort() + let node = { + id: stateName, + color: color + }; nodes.push(node); - lifecycle[stateName].node = node; - }); - Object.keys(lifecycle).forEach((stateName) => { - const state = lifecycle[stateName]; //todo: remove checking property if (state.hasOwnProperty("proceedingStates")) { - state.proceedingStates.forEach((proceedingState) => { - links.push(connectNodes(state.node, lifecycle[proceedingState].node)); + let link = { + source: stateName, + target: proceedingState + }; + links.push(link); }); } }); - nodes.forEach((node) => { - model.addNode(node); - }); - links.forEach((link) => { - model.addLink(link); - }); - + const data = { + nodes: nodes, + links: links + }; - let distributedModel = getDistributedModel(engine, model); - engine.setDiagramModel(distributedModel); return ( -
- +
+
); } } -function getDistributedModel(engine, model) { - const serialized = model.serializeDiagram(); - const distributedSerializedDiagram = distributeElements(serialized); - - //deserialize the model - let deSerializedModel = new SRD.DiagramModel(); - deSerializedModel.deSerializeDiagram(distributedSerializedDiagram, engine); - return deSerializedModel; -} - -function createNode(name, color) { - const node = new SRD.DefaultNodeModel(name, color); - node.addPort(new SRD.DefaultPortModel(true, inPortName, " ")); - node.addPort(new SRD.DefaultPortModel(false, outPortName, " ")); - return node; -} - -let count = 0; - -function connectNodes(nodeFrom, nodeTo) { - return nodeFrom.getPort(outPortName).link(nodeTo.getPort(inPortName)); -} - +const LifeCycleGraph = connect(null,mapDispatchToProps)(ConnectedLifeCycleGraph); export default LifeCycleGraph; \ No newline at end of file diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/LifecycleModal.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/LifecycleModal.js new file mode 100644 index 00000000000..26db61287c6 --- /dev/null +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/LifecycleModal.js @@ -0,0 +1,112 @@ +import React from "react"; +import {Modal, Typography, Icon, Input, Form, Checkbox, Button} from 'antd'; +import {connect} from 'react-redux'; +import {closeLifecycleModal, updateLifecycleState} from "../../../js/actions"; + +const { TextArea } = Input; +const { Title } = Typography; + +// connecting state.releaseView with the component +const mapStateToProps = state => { + return { + nextState: state.lifecycleModal.nextState, + visible: state.lifecycleModal.visible + } +}; + +const mapDispatchToProps = dispatch => ({ + closeLifecycleModal : () => dispatch(closeLifecycleModal()), + updateLifecycleState : (uuid, nextState, reason) => dispatch(updateLifecycleState(uuid, nextState, reason)) +}); + +const Text = Typography; + +class ConnectedLifecycleModal extends React.Component { + constructor(props) { + super(props); + this.state = { + loading: false, + visible: false + }; + } + + componentWillReceiveProps(nextProps) { + if (nextProps !== this.props) { + this.setState({ + visible: nextProps.visible + }) + } + } + + showModal = () => { + this.setState({ + visible: true, + }); + }; + + handleOk = (e) => { + this.setState({ + visible: false, + }); + this.props.closeLifecycleModal(); + }; + + handleCancel = (e) => { + this.setState({ + visible: false, + loading: false + }); + this.props.closeLifecycleModal(); + }; + handleSubmit = event => { + this.setState({ loading: true }); + event.preventDefault(); + console.log(this.reason); + console.log("uuid", this.props.uuid); + this.props.updateLifecycleState(this.props.uuid, this.props.nextState, this.reason.state.value) + }; + + render() { + if (this.props.nextState != null) { + const nextState = this.props.nextState; + return ( +
+ + {this.props.currentStatus} <Icon type="arrow-right" /> {nextState} +
+ + + + this.reason = input}/> + + {/**/} + {/* this.input = input}*/} + {/*autosize*/} + {/*/>*/} + {/**/} + , + +
+
+
+ ); + } else { + return null; + } + } +} + +const LifecycleModal = connect(mapStateToProps, mapDispatchToProps)(ConnectedLifecycleModal); + +export default LifecycleModal; \ No newline at end of file diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/nodes/CustomNode.css b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/nodes/CustomNode.css new file mode 100644 index 00000000000..2b59de070cc --- /dev/null +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/nodes/CustomNode.css @@ -0,0 +1,21 @@ + +/* --- Shape for the nodes --- */ + +.node { + width: 75px; + height: 30px; + border-radius: 20% 20% 20% 20%; + overflow: hidden; + box-sizing: border-box; + display: flex; +} + + +.node .name { + padding: 5%; + font-size: 0.5rem; + font-weight: bold; + text-align: center; + text-transform: uppercase; + color: white; +} diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/nodes/CustomNode.jsx b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/nodes/CustomNode.jsx new file mode 100644 index 00000000000..a77025bdd15 --- /dev/null +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/nodes/CustomNode.jsx @@ -0,0 +1,36 @@ +import React from "react"; + +import "./CustomNode.css"; + + +/** + * Component that renders a person's name and gender, along with icons + * representing if they have a driver license for bike and / or car. + * @param {Object} props component props to render. + */ +function CustomNode({ node }) { + + return ( +
+
{node.id}
+ + {/*
*/} + {/*
*/} + {/**/} + {/*
*/} + + {/*
*/} + {/*{person.hasBike && (*/} + {/*
*/} + {/*)}*/} + {/*{person.hasCar &&
}*/} + {/*
*/} + {/*
*/} +
+ ); +} + +export default CustomNode; \ No newline at end of file diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/temp/LifeCycleGraph.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/temp/LifeCycleGraph.js new file mode 100644 index 00000000000..fc25a9b306b --- /dev/null +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/components/apps/release/temp/LifeCycleGraph.js @@ -0,0 +1,98 @@ +import React from "react"; +import * as SRD from "storm-react-diagrams"; +import "storm-react-diagrams/dist/style.min.css"; +import "./LifeCycle.css"; +import {distributeElements} from "../../../js/utils/dagre-utils.ts"; + +const inPortName = "IN"; +const outPortName = "OUT"; + +class LifeCycleGraph extends React.Component { + render() { + + const lifecycle = this.props.lifecycle; + const nodes = []; + const links = []; + + const engine = new SRD.DiagramEngine(); + engine.installDefaultFactories(); + + const model = new SRD.DiagramModel(); + const nextStates = lifecycle[this.props.currentStatus].proceedingStates; + + + Object.keys(lifecycle).forEach((stateName) => { + let color = "rgb(83, 92, 104)"; + if (stateName === this.props.currentStatus) { + color = "rgb(192,255,0)"; + } else if (nextStates.includes(stateName)) { + color = "rgb(0,192,255)"; + } + const node = createNode(stateName, color); + nodes.push(node); + lifecycle[stateName].node = node; + }); + + Object.keys(lifecycle).forEach((stateName) => { + const state = lifecycle[stateName]; + //todo: remove checking property + if (state.hasOwnProperty("proceedingStates")) { + + state.proceedingStates.forEach((proceedingState) => { + links.push(connectNodes(state.node, lifecycle[proceedingState].node)); + }); + } + }); + + nodes.forEach((node) => { + model.addNode(node); + // node.addListener({ + // selectionChanged: (node, isSelected) => { + // console.log(isSelected); + // } + // }); + }); + links.forEach((link) => { + model.addLink(link); + }); + + + let distributedModel = getDistributedModel(engine, model); + engine.setDiagramModel(distributedModel); + + return ( +
+ +
+ ); + } +} + +function getDistributedModel(engine, model) { + const serialized = model.serializeDiagram(); + const distributedSerializedDiagram = distributeElements(serialized); + + //deserialize the model + let deSerializedModel = new SRD.DiagramModel(); + deSerializedModel.deSerializeDiagram(distributedSerializedDiagram, engine); + return deSerializedModel; +} + +function createNode(name, color) { + const node = new SRD.DefaultNodeModel(name, color); + node.addPort(new SRD.DefaultPortModel(true, inPortName, " ")); + node.addPort(new SRD.DefaultPortModel(false, outPortName, " ")); + return node; +} + +let count = 0; + +function connectNodes(nodeFrom, nodeTo) { + return nodeFrom.getPort(outPortName).link(nodeTo.getPort(inPortName)); +} + +function f() { + console.log(1); +} + +export default LifeCycleGraph; \ No newline at end of file diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/js/actions/index.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/js/actions/index.js index f89df5fbf09..d3274757216 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/js/actions/index.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/js/actions/index.js @@ -23,12 +23,11 @@ export const getApps = () => dispatch => { } }); - }; export const getRelease = (uuid) => dispatch => { - const request = "method=get&content-type=application/json&payload={}&api-endpoint=/application-mgt-publisher/v1.0/applications/release/"+uuid; + const request = "method=get&content-type=application/json&payload={}&api-endpoint=/application-mgt-publisher/v1.0/applications/release/" + uuid; return axios.post('https://' + config.serverConfig.hostname + ':' + config.serverConfig.httpsPort + config.serverConfig.invokerUri, request ).then(res => { @@ -50,12 +49,28 @@ export const openReleasesModal = (app) => dispatch => { dispatch({ type: ActionTypes.OPEN_RELEASES_MODAL, payload: { - app:app + app: app + } + }); +}; + + +export const openLifecycleModal = (nextState) => dispatch => { + dispatch({ + type: ActionTypes.OPEN_LIFECYCLE_MODAL, + payload: { + nextState: nextState } }); }; -export const getLifecycle = ()=> dispatch =>{ +export const closeLifecycleModal = () => dispatch => { + dispatch({ + type: ActionTypes.CLOSE_LIFECYCLE_MODAL + }); +}; + +export const getLifecycle = () => dispatch => { const request = "method=get&content-type=application/json&payload={}&api-endpoint=/application-mgt-publisher/v1.0/applications/lifecycle-config"; return axios.post('https://' + config.serverConfig.hostname + ':' + config.serverConfig.httpsPort + config.serverConfig.invokerUri, request @@ -71,3 +86,42 @@ export const getLifecycle = ()=> dispatch =>{ } }); }; + +export const updateLifecycleState = (uuid, nextState, reason) => dispatch => { + + const payload = { + action: nextState, + reason: reason + }; + + const request = "method=post&content-type=application/json&payload=" + JSON.stringify(payload) + "&api-endpoint=/application-mgt-publisher/v1.0/applications/life-cycle/" + uuid; + + console.log(request); + + return axios.post('https://' + config.serverConfig.hostname + ':' + config.serverConfig.httpsPort + config.serverConfig.invokerUri, request + ).then(res => { + if (res.status === 200) { + if(res.data.data.hasOwnProperty("release")) { + let release = res.data.data; + dispatch({type: ActionTypes.UPDATE_LIFECYCLE_STATE, payload: release}); + }else{ + alert("error"); + dispatch({ + type: ActionTypes.CLOSE_LIFECYCLE_MODAL + }); + } + } + + }).catch(function (error) { + if (error.response.status === 401) { + window.location.href = 'https://localhost:9443/publisher/login'; + } else if (error.response.status === 500) { + alert("error"); + dispatch({ + type: ActionTypes.CLOSE_LIFECYCLE_MODAL + }); + } + }); + + +}; diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/js/constants/ActionTypes.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/js/constants/ActionTypes.js index dd66861ec7a..76ec0da9f7f 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/js/constants/ActionTypes.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/js/constants/ActionTypes.js @@ -6,8 +6,10 @@ const ActionTypes = keyMirror({ OPEN_RELEASES_MODAL: null, CLOSE_RELEASES_MODAL: null, GET_RELEASE: null, - GET_LIFECYCLE: null - + GET_LIFECYCLE: null, + OPEN_LIFECYCLE_MODAL: null, + CLOSE_LIFECYCLE_MODAL: null, + UPDATE_LIFECYCLE_STATE: null, }); export default ActionTypes; \ No newline at end of file diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/js/reducers/index.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/js/reducers/index.js index 0d720a35786..a03213ef461 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/js/reducers/index.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/js/reducers/index.js @@ -7,7 +7,11 @@ const initialState = { app: null }, release: null, - lifecycle: null + lifecycle: null, + lifecycleModal:{ + visible: false, + nextState: null + } }; function rootReducer(state = initialState, action) { @@ -30,6 +34,28 @@ function rootReducer(state = initialState, action) { return Object.assign({}, state, { lifecycle: action.payload }); + }else if (action.type === ActionTypes.OPEN_LIFECYCLE_MODAL) { + return Object.assign({}, state, { + lifecycleModal: { + visible: true, + nextState: action.payload.nextState + } + }); + }else if (action.type === ActionTypes.CLOSE_LIFECYCLE_MODAL) { + return Object.assign({}, state, { + lifecycleModal: { + visible: false, + nextState: null + } + }); + }else if (action.type === ActionTypes.UPDATE_LIFECYCLE_STATE) { + return Object.assign({}, state, { + lifecycleModal: { + visible: false, + nextState: null, + }, + release: action.payload + }); } return state; } diff --git a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/pages/dashboard/apps/release/Release.js b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/pages/dashboard/apps/release/Release.js index c01c0b9283f..ebf96e42f1c 100644 --- a/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/pages/dashboard/apps/release/Release.js +++ b/components/application-mgt/org.wso2.carbon.device.application.mgt.publisher.ui/react-app/src/pages/dashboard/apps/release/Release.js @@ -69,14 +69,15 @@ class ConnectedRelease extends React.Component { - + + + - +
-
);