Skip to content
This repository has been archived by the owner on Jul 3, 2023. It is now read-only.

Commit

Permalink
Topology view component PoC (#376)
Browse files Browse the repository at this point in the history
* Topology view component PoC

* Remove success status

* Fix TS error

* update version of package

* Fix naming convention, cleanup

* Update interface name
  • Loading branch information
ppadti authored Apr 27, 2023
1 parent 0ea3fed commit 3ebf752
Show file tree
Hide file tree
Showing 8 changed files with 2,564 additions and 8 deletions.
1,581 changes: 1,575 additions & 6 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"@patternfly/react-icons": "4.93.6",
"@patternfly/react-styles": "4.92.6",
"@patternfly/react-table": "4.113.0",
"@patternfly/react-topology": "4.91.40",
"@rhoas/app-services-ui-components": "2.30.0",
"@rhoas/app-services-ui-shared": "0.16.6",
"@rhoas/smart-events-management-sdk": "1.0.2",
Expand Down
2 changes: 1 addition & 1 deletion public/mockServiceWorker.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
/* tslint:disable */

/**
* Mock Service Worker (1.0.0).
* Mock Service Worker (1.2.1).
* @see https://github.com/mswjs/msw
* - Please do NOT modify this file.
* - Please do NOT serve this file on production.
Expand Down
56 changes: 56 additions & 0 deletions src/app/components/POCs/TopologyView/NodeSideBarDetails.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import {
DescriptionList,
DescriptionListDescription,
DescriptionListGroup,
DescriptionListTerm,
} from "@patternfly/react-core";
import React from "react";
import { NodeModel } from "@patternfly/react-topology";

interface NodeInformationProps {
node: NodeModel | undefined;
}

const NodeInformation = (props: NodeInformationProps): JSX.Element => {
const { node } = props;

return (
<DescriptionList
isHorizontal
isCompact
style={{ marginLeft: "2rem", marginTop: "2rem" }}
>
<DescriptionListGroup>
<DescriptionListTerm>{"Name"}</DescriptionListTerm>
<DescriptionListDescription>{node?.label}</DescriptionListDescription>
</DescriptionListGroup>
<DescriptionListGroup>
<DescriptionListTerm>{"Type"}</DescriptionListTerm>
<DescriptionListDescription>
{node?.data.type}
</DescriptionListDescription>
</DescriptionListGroup>
<DescriptionListGroup>
<DescriptionListTerm>{"Owner"}</DescriptionListTerm>
<DescriptionListDescription>
{node?.data.owner}
</DescriptionListDescription>
</DescriptionListGroup>
<DescriptionListGroup>
<DescriptionListTerm>{"Time created"}</DescriptionListTerm>
<DescriptionListDescription>
{node?.data.timeCreated}
</DescriptionListDescription>
</DescriptionListGroup>{" "}
<DescriptionListGroup>
<DescriptionListTerm>{"Time Updated"}</DescriptionListTerm>
<DescriptionListDescription>
{node?.data.timeUpdated}
</DescriptionListDescription>
</DescriptionListGroup>
</DescriptionList>
);
};

export default NodeInformation;
62 changes: 62 additions & 0 deletions src/app/components/POCs/TopologyView/TopologyView.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React from "react";
import { ComponentMeta, ComponentStory } from "@storybook/react";
import TopologyView from "./TopologyView";
import {
EDGES_12,
EDGES_6,
NODES_12,
NODES_12_Without_Edges,
NODES_6,
} from "./topologyStoriesHelper";

export default {
title: "PoCs/Topology View",
component: TopologyView,
} as ComponentMeta<typeof TopologyView>;

const Template: ComponentStory<typeof TopologyView> = (args) => (
<TopologyView {...args} />
);

export const DagreLayout_6_nodes = Template.bind({});
DagreLayout_6_nodes.args = {
layout: "Dagre_network-simplex",
nodes: NODES_6,
edges: EDGES_6,
};

export const DagreLayout_12_nodes_with_tight_tree_algorithm = Template.bind({});
DagreLayout_12_nodes_with_tight_tree_algorithm.args = {
layout: "Dagre_tight-tree",
nodes: NODES_12,
edges: EDGES_12,
};

export const DagreLayout_12_nodes_with_Network_simplex_algorithm =
Template.bind({});
DagreLayout_12_nodes_with_Network_simplex_algorithm.args = {
layout: "Dagre_network-simplex",
nodes: NODES_12,
edges: EDGES_12,
};

export const BreadthFirstLayout_6_nodes = Template.bind({});
BreadthFirstLayout_6_nodes.args = {
layout: "BreadthFirst",
nodes: NODES_6,
edges: EDGES_6,
};

export const BreadthFirstLayout_12_nodes = Template.bind({});
BreadthFirstLayout_12_nodes.args = {
layout: "BreadthFirst",
nodes: NODES_12,
edges: EDGES_12,
};

export const ColaLayout_Without_Edges = Template.bind({});
ColaLayout_Without_Edges.args = {
layout: "Cola",
nodes: NODES_12_Without_Edges,
edges: [],
};
257 changes: 257 additions & 0 deletions src/app/components/POCs/TopologyView/TopologyView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
import React from "react";
import { RegionsIcon as Icon4 } from "@patternfly/react-icons";
import { DataSourceIcon as Icon1 } from "@patternfly/react-icons";
import { DataSinkIcon as Icon2 } from "@patternfly/react-icons";
import { DataProcessorIcon as Icon3 } from "@patternfly/react-icons";
import {
action,
BreadthFirstLayout,
ColaLayout,
ConcentricLayout,
createTopologyControlButtons,
DagreLayout,
defaultControlButtonsOptions,
DefaultEdge,
DefaultGroup,
DefaultNode,
Edge,
EdgeTerminalType,
ForceLayout,
GraphComponent,
GridLayout,
ModelKind,
SELECTION_EVENT,
TopologyControlBar,
TopologySideBar,
TopologyView as PFTopologyView,
Visualization,
VisualizationProvider,
VisualizationSurface,
withPanZoom,
withSelection,
WithSelectionProps,
ComponentFactory,
Graph,
Layout,
LayoutFactory,
Model,
Node,
EdgeModel,
NodeModel,
} from "@patternfly/react-topology";

import NodeInformation from "./NodeSideBarDetails";

export interface TopologyViewProps {
layout: string;
nodes: NodeModel[];
edges: EdgeModel[];
truncateLength?: number;
}

interface CustomNodeProps {
element: Node;
}
interface DataEdgeProps {
element: Edge;
}

const DataEdge: React.FC<DataEdgeProps> = ({ element, ...rest }) => (
<DefaultEdge
element={element}
startTerminalType={EdgeTerminalType.cross}
endTerminalType={EdgeTerminalType.directionalAlt}
{...rest}
/>
);

const CustomNode: React.FC<CustomNodeProps & WithSelectionProps> = ({
element,
onSelect,
selected,
...rest
}) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const data = element.getData();
let Icon;
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if (data.type == "Source") {
Icon = Icon1;
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
} else if (data.type == "Sink") {
Icon = Icon2;
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
} else if (data.type == "Processor") {
Icon = Icon3;
} else {
Icon = Icon4;
}

return (
<DefaultNode
element={element}
showStatusDecorator
{...rest}
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
truncateLength={data.length}
onSelect={onSelect}
selected={selected}
>
<g transform={`translate(25, 25)`}>
<Icon style={{ color: "#393F44" }} width={25} height={25} />
</g>
</DefaultNode>
);
};

const customLayoutFactory: LayoutFactory = (
type: string,
graph: Graph
): Layout | undefined => {
switch (type) {
case "Cola":
return new ColaLayout(graph, {
layoutOnDrag: false,
linkDistance: 10,
collideDistance: 30,
maxTicks: 1,
groupDistance: 150,
});
case "Dagre_network-simplex":
return new DagreLayout(graph, {
rankdir: "LR",
nodesep: 10,
linkDistance: 5,
edgesep: 2,
groupDistance: 150,
ranker: "network-simplex",
});
case "Dagre_tight-tree":
return new DagreLayout(graph, {
rankdir: "LR",
nodesep: 10,
linkDistance: 5,
edgesep: 2,
groupDistance: 150,
ranker: "tight-tree",
});
case "BreadthFirst":
return new BreadthFirstLayout(graph, {
nodeDistance: 80,
});
case "Concentric":
return new ConcentricLayout(graph, {
groupDistance: 150,
});
case "Grid":
return new GridLayout(graph, {
groupDistance: 150,
nodeDistance: 75,
});
default:
return new ForceLayout(graph, {
groupDistance: 250,
});
}
};

const customComponentFactory: ComponentFactory = (
kind: ModelKind,
type: string
): any => {
switch (type) {
case "group":
return DefaultGroup;
case "data-edge":
return DataEdge;
default:
switch (kind) {
case ModelKind.graph:
return withPanZoom()(GraphComponent);
case ModelKind.node:
return withSelection()(CustomNode as React.FC);
case ModelKind.edge:
return DefaultEdge;
default:
return undefined;
}
}
};

const TopologyView = (props: TopologyViewProps): JSX.Element => {
const { layout, nodes, edges, truncateLength } = props;

const [selectedIds, setSelectedIds] = React.useState<string[]>([]);

const controller = React.useMemo(() => {
nodes.map((node) => {
node.data && Object.assign(node?.data, { length: truncateLength });
});

const model: Model = {
nodes: nodes,
edges: edges,
graph: {
id: "g1",
type: "graph",
layout,
},
};

const newController = new Visualization();
newController.setFitToScreenOnLayout(true);
newController.registerLayoutFactory(customLayoutFactory);
newController.registerComponentFactory(customComponentFactory);

newController.addEventListener(SELECTION_EVENT, setSelectedIds);

newController.fromModel(model, false);

return newController;
}, [edges, layout, nodes, truncateLength]);

const node = nodes.find((node) => node.id === selectedIds[0]);

const topologySideBar = (
<TopologySideBar
className="topology-example-sidebar"
show={selectedIds.length > 0}
onClose={(): void => setSelectedIds([])}
>
<NodeInformation node={node} />
</TopologySideBar>
);

return (
<PFTopologyView
sideBar={topologySideBar}
controlBar={
<TopologyControlBar
controlButtons={createTopologyControlButtons({
...defaultControlButtonsOptions,
zoomInCallback: action(() => {
controller.getGraph().scaleBy(4 / 3);
}),
zoomOutCallback: action(() => {
controller.getGraph().scaleBy(0.75);
}),
fitToScreenCallback: action(() => {
controller.getGraph().fit(80);
}),
resetViewCallback: action(() => {
controller.getGraph().reset();
controller.getGraph().layout();
}),
legend: false,
})}
/>
}
>
<VisualizationProvider controller={controller}>
<VisualizationSurface state={{ selectedIds }} />
</VisualizationProvider>
</PFTopologyView>
);
};

export default TopologyView;
Loading

0 comments on commit 3ebf752

Please sign in to comment.