Skip to content

Commit

Permalink
Fix: enhance the application graph (#618)
Browse files Browse the repository at this point in the history
* Fix: enhance the application graph

Signed-off-by: barnettZQG <[email protected]>

* Fix: remove the never read property

Signed-off-by: barnettZQG <[email protected]>

* Fix: show the graph not as the tree

Signed-off-by: barnettZQG <[email protected]>

Signed-off-by: barnettZQG <[email protected]>
  • Loading branch information
barnettZQG authored Oct 20, 2022
1 parent ed60e2a commit aa7390c
Show file tree
Hide file tree
Showing 12 changed files with 746 additions and 530 deletions.
30 changes: 30 additions & 0 deletions src/components/TreeGraph/component-node.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
.label-traits {
z-index: 1000;
display: flex;
flex-direction: row;
align-items: center;
white-space: nowrap;
.trait-num {
display: block;
padding: 2px 4px;
border: rgb(27, 88, 244) solid 1px;
border-radius: 4px;
&.active {
color: #fff;
background-color: rgb(27, 88, 244);
}
}
}

.trait-graph {
position: absolute;
top: -16px;
left: 260px;
.trait {
height: 24px;
line-height: 24px;
}
.trait-node {
padding-left: 1em;
}
}
235 changes: 235 additions & 0 deletions src/components/TreeGraph/component-node.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
import React, { useState } from 'react';
import * as dagre from 'dagre';
import classNames from 'classnames';
import Translation from '../Translation';
import type { GraphNode, GraphEdge, TraitGraphNode, Line } from './interface';
import { describeComponents, getGraphSize, ResourceIcon } from './utils';
import { If } from 'tsx-control-statements/components';
import { Balloon, Tag } from '@b-design/ui';

import './component-node.less';
import type { TraitStatus } from '../../interface/application';

export interface ComponentNodeProps {
node: GraphNode;
showTrait: boolean;
}

function renderTraitTree(traits: TraitStatus[]) {
const graph = new dagre.graphlib.Graph<TraitGraphNode, GraphEdge>();
graph.setGraph({
nodesep: 20,
rankdir: 'TB',
align: 'UL',
ranksep: 26,
compound: true,
});

// set node and make layout
graph.setNode('graph-trait-start', {
width: 5,
height: 40,
x: 0,
y: 0,
});
traits.map((trait) => {
graph.setEdge('graph-trait-start', trait.type, {});
graph.setNode(trait.type, {
trait: trait,
width: 130,
height: 30,
x: 0,
y: 0,
});
});
dagre.layout(graph);
const edges: { from: string; to: string; lines: Line[] }[] = [];
graph.edges().forEach((edgeInfo) => {
const edge = graph.edge(edgeInfo);
const lines: Line[] = [];
if (edge.points && edge.points.length > 1) {
for (let i = 1; i < edge.points.length; i++) {
lines.push({
x1: edge.points[i - 1].x,
y1: edge.points[i - 1].y - 14,
x2: edge.points[i].x,
y2: edge.points[i].y - 14,
});
}
}
edges.push({
from: edgeInfo.v,
to: edgeInfo.w,
lines: lines,
});
});
const size = getGraphSize(graph.nodes().map((key) => graph.node(key)));
return (
<div
className="trait-graph"
style={{
width: size.width,
height: size.height,
left: 190,
transformOrigin: '0% 0%',
transform: `scale(${1})`,
}}
>
{graph.nodes().map((key) => {
if (key === 'graph-trait-start') {
return;
}
const { trait, x, y, width, height } = graph.node(key);
if (!trait) {
return;
}
const label = trait.type;
const traitNode = (
<div
key={key}
id={label}
className={classNames('graph-node', 'trait-node')}
style={{
left: x + 22,
top: y - 10,
width: width,
height: height,
transform: `translate(-60px, 0px)`,
}}
>
<div className={classNames('trait')}>
<div>
<span
className={classNames('circle', {
'circle-success': trait.healthy,
'circle-failure': !trait.healthy,
})}
/>
{label}
</div>
</div>
</div>
);

if (trait.message) {
return (
<Balloon trigger={traitNode}>
<div>
<p className="line">{`Message: ${trait.message}`}</p>
</div>
</Balloon>
);
}
return traitNode;
})}
{edges.map((edge) => (
<div key={`${edge.from}-${edge.to}`} className="graph-edge">
{edge.lines.map((line) => {
const distance = Math.sqrt(
Math.pow(line.x1 - line.x2, 2) + Math.pow(line.y1 - line.y2, 2),
);
const xMid = (line.x1 + line.x2) / 2;
const yMid = (line.y1 + line.y2) / 2;
const angle = (Math.atan2(line.y1 - line.y2, line.x1 - line.x2) * 180) / Math.PI;
return (
<div
className="graph-edge-line"
key={'line' + line.x2 + line.y2}
style={{
width: distance,
left: xMid - distance / 2,
top: yMid,
transform: `translate(40px, 30px) rotate(${angle}deg)`,
}}
/>
);
})}
</div>
))}
</div>
);
}

export const ComponentNode = (props: ComponentNodeProps) => {
const { node } = props;
const traits = node.resource.service?.traits || [];
const [showTrait, setShowTrait] = useState(props.showTrait);
const WithBalloon = (graphNode: React.ReactNode) => {
return (
<Balloon trigger={graphNode}>
<div>
{describeComponents(node).map((line: any) => {
return <p className="line">{line}</p>;
})}
</div>
</Balloon>
);
};

const graphNode = (
<div
className={classNames('graph-node', 'graph-node-resource', {
'warning-status': !node.resource.service?.healthy,
})}
style={{
// 50 = (nodeWidth - 220)/2
left: node.x - 50,
top: node.y,
width: node.width,
height: node.height,
transform: `translate(-80px, 0px)`,
}}
>
{WithBalloon(
<div className={classNames('icon')}>
<ResourceIcon
kind={node.resource.component?.componentType.substring(0, 1).toUpperCase() || ''}
/>
</div>,
)}
{WithBalloon(
<div className={classNames('name')}>
<div>{node.resource.name}</div>
<div className={classNames('healthy', { success: node.resource.service?.healthy })}>
<If condition={node.resource.service?.healthy}>
<span className="circle circle-success" />
<Translation>Healthy</Translation>
</If>
<If condition={!node.resource.service?.healthy}>
<span className="circle circle-warning" />
<Translation>UnHealthy</Translation>
</If>
</div>
</div>,
)}

<If condition={traits.length > 0}>
<div className={classNames('label-traits')}>
{traits && (
<Tag animation={true}>
<span
className={classNames('circle', {
'circle-success': traits[0].healthy,
'circle-failure': !traits[0].healthy,
})}
/>
{traits[0].type}
</Tag>
)}
<If condition={traits?.length > 1}>
<div
className={classNames('trait-num', { active: showTrait })}
color="blue"
style={{ marginLeft: '8px' }}
onClick={() => setShowTrait(!showTrait)}
>
{traits?.length > 1 && '+' + (traits?.length - 1)}
</div>
</If>
</div>
</If>
{traits.length > 1 && showTrait && renderTraitTree(traits)}
</div>
);
return graphNode;
};
19 changes: 5 additions & 14 deletions src/components/TreeGraph/index.less
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

.graph-tree {
position: relative;

margin-top: 32px;
overflow: auto;

.graph-node {
Expand Down Expand Up @@ -31,6 +33,9 @@
color: #a6a6a6;
font-size: 12px;
}
.healthy.success {
color: #28a745;
}
}

.icon {
Expand Down Expand Up @@ -59,20 +64,6 @@
color: #666;
}
}
.trait {
height: 24px;
line-height: 24px;
}
.label-dot {
width: 5px;
height: 5px;
border-radius: 5px;
background: #2DC86D;
display: inline-block;
position: absolute;
bottom: 7px;
left: -9px;
}
.additional {
position: absolute;
right: 8px;
Expand Down
Loading

0 comments on commit aa7390c

Please sign in to comment.