Merge branch 'application-mgt-new' into 'application-mgt-new'

Add Life-cycle Graph for APPM Publisher

See merge request entgra/carbon-device-mgt!96
feature/appm-store/pbac
Dharmakeerthi Lasantha 6 years ago
commit 1c5bb8c088

@ -13,9 +13,11 @@
"acorn": "^6.1.1", "acorn": "^6.1.1",
"antd": "^3.15.0", "antd": "^3.15.0",
"axios": "^0.18.0", "axios": "^0.18.0",
"d3": "^5.9.2",
"dagre": "^0.8.4", "dagre": "^0.8.4",
"keymirror": "^0.1.1", "keymirror": "^0.1.1",
"react": "^16.8.4", "react": "^16.8.4",
"react-d3-graph": "^2.0.2",
"react-dom": "^16.8.4", "react-dom": "^16.8.4",
"react-highlight-words": "^0.16.0", "react-highlight-words": "^0.16.0",
"react-router": "latest", "react-router": "latest",

@ -1,32 +1,51 @@
import React from "react"; import React from "react";
import LifeCycleGraph from "./LifeCycleGraph"; import LifeCycleGraph from "./LifeCycleGraph";
import {connect} from "react-redux"; 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 => ({ const mapDispatchToProps = dispatch => ({
getLifecycle: () => dispatch(getLifecycle()) getLifecycle: () => dispatch(getLifecycle()),
openLifecycleModal: (nextState) => dispatch(openLifecycleModal(nextState))
}); });
const mapStateToProps = state => { const mapStateToProps = state => {
return { return {
lifecycle: state.lifecycle lifecycle: state.lifecycle,
currentStatus : state.release.currentStatus.toUpperCase(),
uuid : state.release.uuid
}; };
}; };
class ConnectedLifeCycle extends React.Component { class ConnectedLifeCycle extends React.Component {
constructor(props){
super(props);
this.openModal = this.openModal.bind(this);
}
componentDidMount() { componentDidMount() {
this.props.getLifecycle(); this.props.getLifecycle();
} }
openModal() {
this.props.openLifecycleModal("IN_REVIEW");
}
render() { render() {
console.log();
const lifecycle = this.props.lifecycle; const lifecycle = this.props.lifecycle;
if(lifecycle != null){ if (lifecycle != null) {
return ( return (
<LifeCycleGraph currentStatus={this.props.currentStatus} lifecycle={this.props.lifecycle}/> <div>
<LifecycleModal uuid={this.props.uuid} currentStatus={this.props.currentStatus}/>
<Button onClick={this.openModal}>aaaa</Button>
<LifeCycleGraph openModel={this.openModal} currentStatus={this.props.currentStatus} lifecycle={this.props.lifecycle}/>
</div>
); );
}else { } else {
return null; return null;
} }
} }

@ -1,90 +1,117 @@
import React from "react"; import React from "react";
import * as SRD from "storm-react-diagrams"; import {Graph} from 'react-d3-graph';
import "storm-react-diagrams/dist/style.min.css"; import {connect} from "react-redux";
import "./LifeCycle.css"; import {getLifecycle, openLifecycleModal} from "../../../js/actions";
import {distributeElements} from "../../../js/utils/dagre-utils.ts";
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 {
const inPortName = "IN";
const outPortName = "OUT";
class LifeCycleGraph extends React.Component { constructor(props){
super(props);
this.nextStates = null;
this.onClickNode = this.onClickNode.bind(this);
}
onClickNode = function(nodeId) {
const nextStates = this.nextStates;
if(nextStates.includes(nodeId)){
this.props.openLifecycleModal(nodeId);
}
};
render() { render() {
// graph payload (with minimalist structure)
const lifecycle = this.props.lifecycle; const lifecycle = this.props.lifecycle;
const nodes = []; const nodes = [];
const links = []; const links = [];
this.nextStates = lifecycle[this.props.currentStatus].proceedingStates;
const engine = new SRD.DiagramEngine();
engine.installDefaultFactories();
const model = new SRD.DiagramModel();
const nextStates = lifecycle[this.props.currentStatus].proceedingStates;
Object.keys(lifecycle).forEach((stateName) => { Object.keys(lifecycle).forEach((stateName) => {
const state = lifecycle[stateName];
let color = "rgb(83, 92, 104)"; let color = "rgb(83, 92, 104)";
if (stateName === this.props.currentStatus) { if (stateName === this.props.currentStatus) {
color = "rgb(192,255,0)"; color = "rgb(39, 174, 96)";
}else if(nextStates.includes(stateName)){ } else if (this.nextStates.includes(stateName)) {
color = "rgb(0,192,255)"; color = "rgb(0,192,255)";
} }
const node = createNode(stateName, color); let node = {
// node.addPort() id: stateName,
color: color
};
nodes.push(node); nodes.push(node);
lifecycle[stateName].node = node;
});
Object.keys(lifecycle).forEach((stateName) => {
const state = lifecycle[stateName];
//todo: remove checking property //todo: remove checking property
if (state.hasOwnProperty("proceedingStates")) { if (state.hasOwnProperty("proceedingStates")) {
state.proceedingStates.forEach((proceedingState) => { state.proceedingStates.forEach((proceedingState) => {
links.push(connectNodes(state.node, lifecycle[proceedingState].node)); let link = {
source: stateName,
target: proceedingState
};
links.push(link);
}); });
} }
}); });
nodes.forEach((node) => { const data = {
model.addNode(node); nodes: nodes,
}); links: links
links.forEach((link) => { };
model.addLink(link);
});
let distributedModel = getDistributedModel(engine, model);
engine.setDiagramModel(distributedModel);
return ( return (
<div style={{height: 900}}> <div>
<SRD.DiagramWidget diagramEngine={engine} maxNumberPointsPerLink={10} smartRouting={true}/> <Graph
id="graph-id" // id is mandatory, if no id is defined rd3g will throw an error
data={data}
config={myConfig}
onClickNode={this.onClickNode}
/>
</div> </div>
); );
} }
} }
function getDistributedModel(engine, model) { const LifeCycleGraph = connect(null,mapDispatchToProps)(ConnectedLifeCycleGraph);
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));
}
export default LifeCycleGraph; export default LifeCycleGraph;

@ -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 (
<div>
<Modal
title="Change State"
visible={this.state.visible}
onCancel={this.handleCancel}
footer={null}
>
<Title level={4}>{this.props.currentStatus} <Icon type="arrow-right" /> {nextState}</Title>
<form onSubmit={this.handleSubmit}>
<Form.Item>
<label htmlFor="username">Reason</label>
<Input placeholder="Enter the reason" ref={(input) => this.reason = input}/>
</Form.Item>
{/*<Form.Item>*/}
{/*<TextArea*/}
{/*placeholder="Please enter the reason..."*/}
{/*ref={(input) => this.input = input}*/}
{/*autosize*/}
{/*/>*/}
{/*</Form.Item>*/}
<Button key="back" onClick={this.handleCancel}>
Cancel
</Button>,
<Button key="submit" type="primary" htmlType="submit" loading={this.state.loading}>
Submit
</Button>
</form>
</Modal>
</div>
);
} else {
return null;
}
}
}
const LifecycleModal = connect(mapStateToProps, mapDispatchToProps)(ConnectedLifecycleModal);
export default LifecycleModal;

@ -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;
}

@ -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 (
<div className="node" style={{backgroundColor: node.color}}>
<div className="name">{node.id}</div>
{/*<div className="flex-container fill-space flex-container-row">*/}
{/*<div className="fill-space">*/}
{/*<div*/}
{/*className="icon"*/}
{/*style={{ backgroundImage: `url('${isMale ? ICON_TYPES.MAN : ICON_TYPES.WOMAN}')` }}*/}
{/*/>*/}
{/*</div>*/}
{/*<div className="icon-bar">*/}
{/*{person.hasBike && (*/}
{/*<div className="icon" style={{ backgroundImage: `url('${ICON_TYPES.BIKE}')` }} />*/}
{/*)}*/}
{/*{person.hasCar && <div className="icon" style={{ backgroundImage: `url('${ICON_TYPES.CAR}')` }} />}*/}
{/*</div>*/}
{/*</div>*/}
</div>
);
}
export default CustomNode;

@ -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 (
<div style={{height: 500}}>
<SRD.DiagramWidget diagramEngine={engine} maxNumberPointsPerLink={10} smartRouting={true}/>
</div>
);
}
}
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;

@ -23,12 +23,11 @@ export const getApps = () => dispatch => {
} }
}); });
}; };
export const getRelease = (uuid) => 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 return axios.post('https://' + config.serverConfig.hostname + ':' + config.serverConfig.httpsPort + config.serverConfig.invokerUri, request
).then(res => { ).then(res => {
@ -50,12 +49,28 @@ export const openReleasesModal = (app) => dispatch => {
dispatch({ dispatch({
type: ActionTypes.OPEN_RELEASES_MODAL, type: ActionTypes.OPEN_RELEASES_MODAL,
payload: { payload: {
app:app app: app
} }
}); });
}; };
export const getLifecycle = ()=> dispatch =>{
export const openLifecycleModal = (nextState) => dispatch => {
dispatch({
type: ActionTypes.OPEN_LIFECYCLE_MODAL,
payload: {
nextState: nextState
}
});
};
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"; 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 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
});
}
});
};

@ -6,8 +6,10 @@ const ActionTypes = keyMirror({
OPEN_RELEASES_MODAL: null, OPEN_RELEASES_MODAL: null,
CLOSE_RELEASES_MODAL: null, CLOSE_RELEASES_MODAL: null,
GET_RELEASE: 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; export default ActionTypes;

@ -7,7 +7,11 @@ const initialState = {
app: null app: null
}, },
release: null, release: null,
lifecycle: null lifecycle: null,
lifecycleModal:{
visible: false,
nextState: null
}
}; };
function rootReducer(state = initialState, action) { function rootReducer(state = initialState, action) {
@ -30,6 +34,28 @@ function rootReducer(state = initialState, action) {
return Object.assign({}, state, { return Object.assign({}, state, {
lifecycle: action.payload 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; return state;
} }

@ -69,14 +69,15 @@ class ConnectedRelease extends React.Component {
<ReleaseView release={release}/> <ReleaseView release={release}/>
</Card> </Card>
</Col> </Col>
<Col span={8}> </Row>
<Row style={{padding: 10}}>
<Col>
<Card> <Card>
<LifeCycle currentStatus={release.currentStatus.toUpperCase()}/> <LifeCycle currentStatus={release.currentStatus.toUpperCase()}/>
</Card> </Card>
</Col> </Col>
</Row> </Row>
</div> </div>
</div> </div>
); );

Loading…
Cancel
Save