Skip to content

Commit

Permalink
FR-310 lsp count (#1605)
Browse files Browse the repository at this point in the history
* lsp count implementation

* add mpls count labels

* add title to lsp count
  • Loading branch information
soson authored Aug 19, 2024
1 parent ef9f9da commit b09e940
Show file tree
Hide file tree
Showing 9 changed files with 264 additions and 81 deletions.
25 changes: 25 additions & 0 deletions packages/frinx-device-topology/src/__generated__/graphql.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { assert, describe, expect, test } from 'vitest';
import { getDistanceBetweenPoints, getPointOnCircle, getPointOnSlope } from './graph.helpers';
import { getDistanceBetweenPoints, getPointAtLength, getPointOnCircle, getPointOnSlope } from './graph.helpers';

describe('graph helpers', () => {
test('test edge curve position 0 degrees', () => {
Expand Down Expand Up @@ -56,4 +56,19 @@ describe('graph helpers', () => {
const distance = getDistanceBetweenPoints(p1, p2);
expect(distance).toBeCloseTo(1.414);
});
test('test get point at length', () => {
const source = { x: 5, y: 5 };
const target = { x: 3, y: 3 };
const { x: x1, y: y1 } = getPointAtLength({ start: source, end: target }, 0.5);
expect(x1).toBeCloseTo(4);
expect(y1).toBeCloseTo(4);

const { x: x2, y: y2 } = getPointAtLength({ start: source, end: target }, 0.1);
expect(x2).toBeCloseTo(4.8);
expect(y2).toBeCloseTo(4.8);

const { x: x3, y: y3 } = getPointAtLength({ start: source, end: target }, 0.9);
expect(x3).toBeCloseTo(3.2);
expect(y3).toBeCloseTo(3.2);
});
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import unwrap from '@frinx/shared/src/helpers/unwrap';
import { GraphEdgeWithDiff } from '../../helpers/topology-helpers';
import { DeviceSize } from '../../__generated__/graphql';
import { DeviceSize, MplsLspCountItem } from '../../__generated__/graphql';

export const width = 1248;
export const height = 600;
Expand Down Expand Up @@ -197,6 +197,12 @@ export type GraphMplsNodeInterface = {
status: string;
};

export type LspCount = {
deviceName: string;
incomingLsps: number;
outcomingLsps: number;
};

export const NODE_CIRCLE_RADIUS = 30;

export type PositionsMap = {
Expand Down Expand Up @@ -490,3 +496,23 @@ export function normalizeNodeInterfaceData<
...details,
};
}

export function getLspCounts(input: MplsLspCountItem): LspCount {
return {
deviceName: input.target ?? '',
incomingLsps: input.incomingLsps ?? 0,
outcomingLsps: input.outcomingLsps ?? 0,
};
}

// distance is number between 0-1 - thats ratio distance/length
export function getPointAtLength(line: Line, distance: number): Position {
const { start, end } = line;
const length = Math.sqrt((end.x - start.x) ** 2 + (end.y - start.y) ** 2);
const x = start.x + ((distance * length) / length) * (end.x - start.x);
const y = start.y + ((distance * length) / length) * (end.y - start.y);
return {
x,
y,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { chakra } from '@chakra-ui/react';
import React, { useRef, VoidFunctionComponent } from 'react';
import { getCurvePath, getPointAtLength, Line, LspCount } from '../graph.helpers';

const G = chakra('g');
const Circle = chakra('circle');
const Text = chakra('text');

type Props = {
lspCount: LspCount;
linePoints: Line;
};

const LspCountItem: VoidFunctionComponent<Props> = ({ linePoints, lspCount }) => {
const edgeRef = useRef<SVGPathElement>(null);

const { start, end } = linePoints;

const incomingPosition = getPointAtLength(linePoints, 0.3);
const outcomingPosition = getPointAtLength(linePoints, 0.7);

return (
<g>
<path
ref={edgeRef}
strokeWidth={3}
stroke="red"
strokeLinejoin="round"
fill="red"
d={getCurvePath(start, end, [])}
cursor="pointer"
/>
<G transform={`translate3d(${incomingPosition.x}px, ${incomingPosition.y}px, 0)`} transformOrigin="center center">
<G transform="translate3d(-18px, -18px, 0) scale(1.5)">
<path
d="M11 9L8 12M8 12L11 15M8 12H16M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z"
stroke="#000000"
strokeWidth="1"
strokeLinecap="round"
strokeLinejoin="round"
fill="#fff"
/>
</G>
<Circle r="12" fillOpacity={0}>
<title>incoming Laps: {lspCount.incomingLsps}</title>
</Circle>
<Text
height="sm"
textAnchor="middle"
y="-20"
paintOrder="stroke"
strokeWidth={3}
stroke="white"
strokeLinecap="butt"
strokeLinejoin="round"
>
{lspCount.incomingLsps}
</Text>
</G>
<G
transform={`translate3d(${outcomingPosition.x}px, ${outcomingPosition.y}px, 0)`}
transformOrigin="center center"
>
<G transform="translate3d(-18px, -18px, 0) scale(1.5)">
<path
d="M17 12H7M17 12L13 16M17 12L13 8M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z"
stroke="#000000"
strokeWidth="1"
strokeLinecap="round"
strokeLinejoin="round"
fill="#fff"
/>
</G>
<Circle r="12" fillOpacity={0}>
<title>outcoming Laps: {lspCount.outcomingLsps}</title>
</Circle>
<Text
height="sm"
textAnchor="middle"
y="-20"
paintOrder="stroke"
strokeWidth={2}
stroke="white"
strokeLinecap="butt"
strokeLinejoin="round"
>
{lspCount.outcomingLsps}
</Text>
</G>
</g>
);
};

export default LspCountItem;
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React, { VoidFunctionComponent } from 'react';
import { GraphEdgeWithDiff } from '../../../helpers/topology-helpers';
import { useStateContext } from '../../../state.provider';
import { getLinePoints, getNameFromNode, isTargetingActiveNode, LspCount } from '../graph.helpers';
import LspCountItem from './lsp-count-item';

type Props = {
edges: GraphEdgeWithDiff[];
lspCounts: LspCount[];
};

const LspCounts: VoidFunctionComponent<Props> = ({ edges, lspCounts }) => {
const { state } = useStateContext();
const {
connectedNodeIds,
selectedNode,
mplsNodePositions: nodePositions,
mplsInterfaceGroupPositions: interfaceGroupPositions,
} = state;

const activeEdges = edges.filter((e) =>
isTargetingActiveNode(e, getNameFromNode(selectedNode), interfaceGroupPositions),
);

const lspCountsMap = new Map(lspCounts.map((c) => [c.deviceName, c]));

return (
<g>
{activeEdges.map((edge) => {
const linePoints = getLinePoints({
edge,
connectedNodeIds,
nodePositions,
interfaceGroupPositions,
});

const lspCount = lspCountsMap.get(edge.source.nodeId) ?? null;
if (!linePoints || !lspCount) {
return null;
}

return <LspCountItem key={`lsp-count-item-${edge.id}`} linePoints={linePoints} lspCount={lspCount} />;
})}
</g>
);
};

export default LspCounts;
Original file line number Diff line number Diff line change
@@ -1,38 +1,25 @@
import { Box, Button, Text } from '@chakra-ui/react';
import { Box } from '@chakra-ui/react';
import { unwrap } from '@frinx/shared';
import React, { useRef, VoidFunctionComponent } from 'react';
import { clearGmPathSearch, setSelectedNode, updateMplsNodePosition } from '../../../state.actions';
import { setSelectedNode, updateMplsNodePosition } from '../../../state.actions';
import { useStateContext } from '../../../state.provider';
import Edges from './mpls-edges';
import { height, Position, width, MplsGraphNode } from '../graph.helpers';
import BackgroundSvg from '../img/background.svg';
import MplsNodes from './mpls-nodes';
import MplsInfoPanel from './mpls-info-panel';
import { getGmPathHopsCount } from '../../../helpers/topology-helpers';
import LspCounts from './lsp-counts';

type Props = {
isGrandMasterPathFetching: boolean;
onNodePositionUpdate: (positions: { deviceName: string; position: Position }[]) => Promise<void>;
onGrandMasterPathSearch: (nodeIds: string[]) => void;
};

const MplsTopologyGraph: VoidFunctionComponent<Props> = ({
isGrandMasterPathFetching,
onNodePositionUpdate,
onGrandMasterPathSearch,
}) => {
const MplsTopologyGraph: VoidFunctionComponent<Props> = ({ onNodePositionUpdate }) => {
const { state, dispatch } = useStateContext();
const lastPositionRef = useRef<{ deviceName: string; position: Position } | null>(null);
const positionListRef = useRef<{ deviceName: string; position: Position }[]>([]);
const timeoutRef = useRef<number>();
const {
mplsEdges: edges,
mplsNodes: nodes,
gmPathIds,
selectedNode,
unconfirmedSelectedNodeIds,
unconfirmedSelectedGmPathNodeId,
} = state;
const { mplsEdges: edges, mplsNodes: nodes, selectedNode, lspCounts } = state;

const handleNodePositionUpdate = (deviceName: string, position: Position) => {
if (timeoutRef.current != null) {
Expand Down Expand Up @@ -62,14 +49,6 @@ const MplsTopologyGraph: VoidFunctionComponent<Props> = ({
dispatch(setSelectedNode(null));
};

const handleClearGmPath = () => {
dispatch(clearGmPathSearch());
};

const handleSearchClick = () => {
onGrandMasterPathSearch(unconfirmedSelectedNodeIds);
};

return (
<Box background="white" borderRadius="md" position="relative" backgroundImage={`url(${BackgroundSvg})`}>
<svg width={width} height={height} viewBox={`0 0 ${width} ${height}`}>
Expand All @@ -79,23 +58,9 @@ const MplsTopologyGraph: VoidFunctionComponent<Props> = ({
onNodePositionUpdate={handleNodePositionUpdate}
onNodePositionUpdateFinish={handleNodePositionUpdateFinish}
/>
{selectedNode && <LspCounts edges={edges} lspCounts={lspCounts} />}
</svg>
{selectedNode != null && <MplsInfoPanel node={selectedNode as MplsGraphNode} onClose={handleInfoPanelClose} />}
{unconfirmedSelectedGmPathNodeId && (
<Box position="absolute" top={2} left="2" background="transparent">
<Box display="flex" alignItems="center">
<Button onClick={handleClearGmPath} marginRight={2}>
Clear GM path
</Button>
<Button onClick={handleSearchClick} isDisabled={isGrandMasterPathFetching} marginRight={2}>
Find GM path
</Button>
{gmPathIds.length > 0 && (
<Text fontWeight="600">Number of hops: {getGmPathHopsCount(gmPathIds, 'SynceDevice')}</Text>
)}
</Box>
</Box>
)}
</Box>
);
};
Expand Down
Loading

0 comments on commit b09e940

Please sign in to comment.