feat(ui-toolkit, cp-frontend): Allow topology nodes are displayed at top when not connected

This commit is contained in:
JUDIT GRESKOVITS 2017-07-12 12:54:55 +01:00 committed by Sérgio Ramos
parent be7bb5f871
commit abdd9f9f6a
8 changed files with 108 additions and 69 deletions

View File

@ -51,24 +51,7 @@ const DeleteServicesGql = graphql(ServicesDeleteMutation, {
props: ({ mutate }) => ({ props: ({ mutate }) => ({
deleteServices: serviceId => deleteServices: serviceId =>
mutate({ mutate({
variables: { ids: [serviceId] }, variables: { ids: [serviceId] }
updateQueries: {
Services: (prev, { mutationResult }) => {
const deletedService = mutationResult.data.deleteServices[0];
const prevServices = prev.deploymentGroup.services;
const services = prevServices.filter(
service =>
service.id !== deletedService.id &&
service.parent !== deletedService.id
);
return {
deploymentGroup: {
...prev.deploymentGroup,
services
}
};
}
}
}) })
}) })
}); });

View File

@ -126,6 +126,7 @@ const UiConnect = connect(mapStateToProps, mapDispatchToProps);
const ServicesGql = graphql(ServicesQuery, { const ServicesGql = graphql(ServicesQuery, {
options(props) { options(props) {
return { return {
pollInterval: 1000,
variables: { variables: {
deploymentGroupSlug: props.match.params.deploymentGroup deploymentGroupSlug: props.match.params.deploymentGroup
} }

View File

@ -158,12 +158,23 @@ const processServicesForTopology = services => {
const processedServices = processServices(services); const processedServices = processServices(services);
const connectedServices = processedServices.reduce( const connectedServices = processedServices.reduce(
(connections, service) => (connections, service) => {
service.connections && service.connections.length if(!service.connections || !service.connections.length) {
? connections.concat(service.connections).concat(service.id) return connections;
: connections, }
[]
); const existingConnections = service.connections.reduce((connections, connection) => {
const connectionExists = processedServices.filter(ps => ps.id === connection).length;
if(connectionExists) {
connections.push(connection);
}
return connections;
}, []);
return existingConnections.length
? connections.concat(existingConnections).concat(service.id)
: connections
}, []);
return processedServices.map(service => ({ return processedServices.map(service => ({
...service, ...service,

View File

@ -99,15 +99,21 @@ const calculateLineLayout = ({ source, target }) => {
}; };
}; };
const getStatusesLength = (data) =>
data.transitionalStatus
? 1
: data.instanceStatuses.length;
const getNodeRect = (data) => { const getNodeRect = (data) => {
const nodeSize = data.children const nodeSize = data.children
? Constants.nodeSizeWithChildren ? Constants.nodeSizeWithChildren
: Constants.nodeSize; : Constants.nodeSize;
const statuses = data.children const statuses = data.children
? data.children.reduce((statuses, child) => ? data.children.reduce((statuses, child) => {
statuses + child.instanceStatuses.length, 0) return statuses + getStatusesLength(child), 0
: data.instanceStatuses.length; })
: getStatusesLength(data);
const { width, height } = nodeSize; const { width, height } = nodeSize;

View File

@ -79,6 +79,7 @@ class Topology extends React.Component {
getNextNodes(nextServices) { getNextNodes(nextServices) {
const nodes = this.state.nodes; const nodes = this.state.nodes;
let notConnectedX = 0;
return nodes.reduce((nextNodes, node) => { return nodes.reduce((nextNodes, node) => {
const keep = nextServices.filter(nextService => nextService.id === node.id).length; const keep = nextServices.filter(nextService => nextService.id === node.id).length;
if(keep) { if(keep) {
@ -94,6 +95,9 @@ class Topology extends React.Component {
// on other updates, we should update the services on the state and that's it // on other updates, we should update the services on the state and that's it
// we should forceUpdate once the state has been updated // we should forceUpdate once the state has been updated
const nextServices = nextProps.services.sort(); const nextServices = nextProps.services.sort();
const connectedNextServices = nextServices.filter(service => service.connected);
const notConnectedNextServices = nextServices.filter(service => !service.connected);
const { services, nodes } = this.state; const { services, nodes } = this.state;
if(nextServices.length > services.length) { if(nextServices.length > services.length) {
// new service added, we need to redraw // new service added, we need to redraw
@ -117,14 +121,16 @@ class Topology extends React.Component {
this.create(nextProps); this.create(nextProps);
} }
else if(servicesRemoved.length || changedConnections.removed) { else if(servicesRemoved.length || changedConnections.removed) {
const nextNodes = servicesRemoved.length
? this.getNextNodes(nextServices) const nextNodes = this.getNextNodes(connectedNextServices);
: nodes; const notConnectedNodes = this.getNotConnectedNodes(notConnectedNextServices);
const nextLinks = this.getNextLinks(nextServices); const nextLinks = this.getNextLinks(nextServices);
this.setState({ this.setState({
services: nextServices, services: nextServices,
links: nextLinks, links: nextLinks,
nodes: nextNodes, nodes: nextNodes,
notConnectedNodes
}, () => this.forceUpdate()); }, () => this.forceUpdate());
} }
else { else {
@ -135,6 +141,21 @@ class Topology extends React.Component {
} }
} }
getNotConnectedNodes(notConnectedServices) {
return notConnectedServices.map((notConnectedService, index) => {
const svgSize = this.getSvgSize();
const x = notConnectedService.isConsul
? svgSize.width - Constants.nodeSize.width
: (Constants.nodeSize.width + 10)*index;
return ({
id: notConnectedService.id,
x,
y: 0
})
});
}
handleResize(evt) { handleResize(evt) {
this.create(this.props); this.create(this.props);
// resize should just rejig the positions // resize should just rejig the positions
@ -144,12 +165,14 @@ class Topology extends React.Component {
// other updates should also just update the services rather than recreate the simulation // other updates should also just update the services rather than recreate the simulation
const services = props.services.sort(); const services = props.services.sort();
const connectedServices = services.filter(service => service.connected); const connectedServices = services.filter(service => service.connected);
const notConnectedServices = services.filter(service => !service.connected && !service.isConsul); const notConnectedServices = services.filter(service => !service.connected);
const svgSize = this.getSvgSize(); const svgSize = this.getSvgSize();
const { nodes, links, simulation } = createSimulation(services, svgSize); const { nodes, links, simulation } = createSimulation(connectedServices, svgSize);
const notConnectedNodes = this.getNotConnectedNodes(notConnectedServices);
this.setState({ this.setState({
notConnectedNodes,
nodes, nodes,
links, links,
simulation, simulation,
@ -207,21 +230,16 @@ class Topology extends React.Component {
); );
} }
getConsulNodePosition() {
const svgSize = this.getSvgSize();
const x = svgSize.width - Constants.nodeSize.width;
return {
x,
y: 0
};
}
getConstrainedNodePosition(nodeId, children = false) { getConstrainedNodePosition(nodeId, children = false) {
const node = this.findNode(nodeId); const node = this.findNode(nodeId);
return this.constrainNodePosition(node.x, node.y, children); return this.constrainNodePosition(node.x, node.y, children);
} }
getNotConnectedNodePosition(nodeId) {
return this.state.notConnectedNodes.filter(ncn =>
ncn.id === nodeId).shift();
}
findNodeData(nodesData, nodeId) { findNodeData(nodesData, nodeId) {
return nodesData.filter(nodeData => nodeData.id === nodeId).shift(); return nodesData.filter(nodeData => nodeData.id === nodeId).shift();
} }
@ -240,9 +258,9 @@ class Topology extends React.Component {
const { nodes, links, services } = this.state; const { nodes, links, services } = this.state;
const nodesData = services.map((service, index) => { const nodesData = services.map((service, index) => {
const nodePosition = service.isConsul const nodePosition = service.connected
? this.getConsulNodePosition() ? this.getConstrainedNodePosition(service.id, service.children)
: this.getConstrainedNodePosition(service.id, service.children); : this.getNotConnectedNodePosition(service.id);
const nodeRect = getNodeRect(service); const nodeRect = getNodeRect(service);
@ -255,12 +273,11 @@ class Topology extends React.Component {
// TODO links will need to know whether a service has children // TODO links will need to know whether a service has children
// if it does, the height of it will be different // if it does, the height of it will be different
const t = links const linksData = links
.map((link, index) => ({ .map((link, index) => ({
source: this.findNodeData(nodesData, link.source.id), source: this.findNodeData(nodesData, link.source.id),
target: this.findNodeData(nodesData, link.target.id) target: this.findNodeData(nodesData, link.target.id)
})); }))
const linksData = t
.map((linkData, index) => { .map((linkData, index) => {
return calculateLineLayout(linkData, index) return calculateLineLayout(linkData, index)
}); });

View File

@ -50,13 +50,8 @@ const GraphNodeContent = ({ child = false, data, index = 0 }) => {
const nodeInfo = const nodeInfo =
<GraphNodeInfo <GraphNodeInfo
datacenter={data.datacenter} data={data}
instances={data.instances}
instanceStatuses={data.instanceStatuses}
healthy
pos={nodeInfoPos} pos={nodeInfoPos}
isConsul={data.isConsul}
instancesActive={data.instancesActive}
/>; />;
return ( return (

View File

@ -33,15 +33,31 @@ const StyledDataCentresIcon = styled(DataCentresIcon)`
`}; `};
`; `;
const GraphNodeInfo = ({ datacenter, instances, instanceStatuses, healthy, pos, isConsul, instancesActive }) => { const GraphNodeInfo = ({ data, pos }) => {
const {
datacenter,
instances,
instanceStatuses,
healthy,
isConsul,
instancesActive,
transitionalStatus,
status
} = data;
const { x, y } = pos; const { x, y } = pos;
const statuses = instanceStatuses.map((instanceStatus, index) => const statuses = transitionalStatus
<GraphText key={index} consul={isConsul} active={instancesActive}> ? <GraphText consul={isConsul} active={instancesActive}>
{`${instanceStatus.count} { status.toLowerCase() }
${instanceStatus.status.toLowerCase()}`} </GraphText>
</GraphText> : instanceStatuses.map((instanceStatus, index) =>
); <GraphText key={index} consul={isConsul} active={instancesActive}>
{`${instanceStatus.count}
${instanceStatus.status.toLowerCase()}`}
</GraphText>
);
return ( return (
<g transform={`translate(${x}, ${y})`}> <g transform={`translate(${x}, ${y})`}>
@ -69,13 +85,15 @@ const GraphNodeInfo = ({ datacenter, instances, instanceStatuses, healthy, pos,
}; };
GraphNodeInfo.propTypes = { GraphNodeInfo.propTypes = {
data: PropTypes.object.isRequired,
pos: Point.isRequired/*,
datacenter: PropTypes.string, datacenter: PropTypes.string,
healthy: PropTypes.bool, healthy: PropTypes.bool,
instances: PropTypes.array, instances: PropTypes.array,
instanceStatuses: PropTypes.array, instanceStatuses: PropTypes.array,
pos: Point.isRequired, pos: Point.isRequired,
isConsul: PropTypes.bool, isConsul: PropTypes.bool,
instancesActive: PropTypes.bool instancesActive: PropTypes.bool*/
}; };
export default Baseline(GraphNodeInfo); export default Baseline(GraphNodeInfo);

View File

@ -10,7 +10,7 @@ const forcePlayAnimation = (simulation, animationTicks) => {
const n = const n =
Math.ceil( Math.ceil(
Math.log(simulation.alphaMin()) / Math.log(1 - simulation.alphaDecay()) Math.log(simulation.alphaMin()) / Math.log(1 - simulation.alphaDecay())
) - animationTicks; ) + 100; // - animationTicks;
for (let i = 0; i < n; ++i) { for (let i = 0; i < n; ++i) {
simulation.tick(); simulation.tick();
@ -41,13 +41,20 @@ const createLinks = services =>
const createSimulation = (services, svgSize, animationTicks = 0) => { const createSimulation = (services, svgSize, animationTicks = 0) => {
// This is not going to work given that as well as the d3 layout stuff, other things might be at play too // This is not going to work given that as well as the d3 layout stuff, other things might be at play too
// We should pass two objects to the components - one for positioning and one for data // We should pass two objects to the components - one for positioning and one for data
const nodes = services.map((service, index) => ({ console.log('createSimulation services = ', services);
id: service.id, console.log('createSimulation services.length = ', services.length);
index const nodes = services.map((service, index) => {
})); console.log('createSimulation services.map service.id = ', service.id);
console.log('createSimulation services.map service.connected = ', service.connected);
return ({
id: service.id,
index
})
});
console.log('createSimulation nodes = ', nodes);
console.log('createSimulation nodes.length = ', nodes.length);
const links = createLinks(services); const links = createLinks(services);
console.log('createSimulation links = ', links);
const { width, height } = svgSize; const { width, height } = svgSize;
const nodeRadius = rectRadius(Constants.nodeSizeWithChildren); const nodeRadius = rectRadius(Constants.nodeSizeWithChildren);
@ -58,6 +65,7 @@ const createSimulation = (services, svgSize, animationTicks = 0) => {
.force('center', forceCenter(width / 2, height / 2)); .force('center', forceCenter(width / 2, height / 2));
forcePlayAnimation(simulation, animationTicks); forcePlayAnimation(simulation, animationTicks);
console.log('createSimulation nodes = ', nodes);
return { return {
nodes, nodes,