Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 83 additions & 45 deletions ui/apps/platform/src/Containers/NetworkGraph/NetworkGraph.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/no-unsafe-return */
import React from 'react';
import { useHistory, useParams } from 'react-router-dom';
import {
Model,
SELECTION_EVENT,
Expand All @@ -10,11 +11,12 @@ import {
TopologyControlBar,
useVisualizationController,
Visualization,
VisualizationProvider,
VisualizationSurface,
VisualizationProvider,
NodeModel,
} from '@patternfly/react-topology';

import { networkBasePathPF } from 'routePaths';
import stylesComponentFactory from './components/stylesComponentFactory';
import defaultLayoutFactory from './layouts/defaultLayoutFactory';
import defaultComponentFactory from './components/defaultComponentFactory';
Expand Down Expand Up @@ -49,44 +51,88 @@ function getUrlParamsForEntity(selectedEntity: NodeModel): [UrlDetailTypeValue,
}

export type NetworkGraphProps = {
detailType?: UrlDetailTypeValue;
detailId?: string;
model: Model;
closeSidebar: () => void;
onSelectNode: (type, id) => void;
};

export type TopologyComponentProps = NetworkGraphProps;
export type TopologyComponentProps = {
model: Model;
};

function getNodeEdges(selectedNode) {
const egressEdges = selectedNode.getSourceEdges();
const ingressEdges = selectedNode.getTargetEdges();
return [...egressEdges, ...ingressEdges];
}

const TopologyComponent = ({
detailId,
model,
closeSidebar,
onSelectNode,
}: TopologyComponentProps) => {
const selectedEntity = findEntityById(model, detailId);
function setVisibleEdges(edges) {
edges.forEach((edge) => {
edge.setVisible(true);
});
}

function setEdges(controller, detailId) {
controller
.getGraph()
.getEdges()
.forEach((edge) => {
edge.setVisible(false);
});

if (detailId) {
const selectedNode = controller.getNodeById(detailId);
if (selectedNode?.isGroup()) {
selectedNode.getAllNodeChildren().forEach((child) => {
// set visible edges
setVisibleEdges(getNodeEdges(child));
});
} else if (selectedNode) {
// set visible edges
setVisibleEdges(getNodeEdges(selectedNode));
}
}
}

const TopologyComponent = ({ model }: TopologyComponentProps) => {
const history = useHistory();
const { detailId } = useParams();
const selectedEntity = detailId && findEntityById(model, detailId);
const controller = useVisualizationController();

React.useEffect(() => {
function onSelect(ids: string[]) {
const newSelectedId = ids?.[0] || '';
const newSelectedEntity = findEntityById(model, newSelectedId);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
if (newSelectedEntity) {
const [newDetailType, newDetailId] = getUrlParamsForEntity(newSelectedEntity);
onSelectNode(newDetailType, newDetailId);
// to prevent error where graph hasn't initialized yet
if (controller.hasGraph()) {
setEdges(controller, detailId);
}

function closeSidebar() {
history.push(`${networkBasePathPF}`);
}

function onSelect(ids: string[]) {
const newSelectedId = ids?.[0] || '';
const newSelectedEntity = findEntityById(model, newSelectedId);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
if (newSelectedEntity) {
const [newDetailType, newDetailId] = getUrlParamsForEntity(newSelectedEntity);
// if found, and it's not the logical grouping of all external sources, then trigger URL update
if (newDetailId !== 'EXTERNAL') {
history.push(`${networkBasePathPF}/${newDetailType}/${newDetailId}`);
} else {
// otherwise, return to the graph-only state
history.push(`${networkBasePathPF}`);
}
}
}

React.useEffect(() => {
controller.fromModel(model, false);
controller.addEventListener(SELECTION_EVENT, onSelect);

setEdges(controller, detailId);
return () => {
controller.removeEventListener(SELECTION_EVENT, onSelect);
};
}, [controller, model, onSelectNode]);
}, [controller, model]);

const selectedIds = selectedEntity ? [selectedEntity.id] : [];

Expand Down Expand Up @@ -139,28 +185,20 @@ const TopologyComponent = ({
);
};

const NetworkGraph = React.memo<NetworkGraphProps>(
({ detailType, detailId, closeSidebar, onSelectNode, model }) => {
const controller = new Visualization();
controller.registerLayoutFactory(defaultLayoutFactory);
controller.registerComponentFactory(defaultComponentFactory);
controller.registerComponentFactory(stylesComponentFactory);

return (
<div className="pf-ri__topology-demo">
<VisualizationProvider controller={controller}>
<TopologyComponent
detailType={detailType}
detailId={detailId}
model={model}
closeSidebar={closeSidebar}
onSelectNode={onSelectNode}
/>
</VisualizationProvider>
</div>
);
}
);
const NetworkGraph = React.memo<NetworkGraphProps>(({ model }) => {
const controller = new Visualization();
controller.registerLayoutFactory(defaultLayoutFactory);
controller.registerComponentFactory(defaultComponentFactory);
controller.registerComponentFactory(stylesComponentFactory);

return (
<div className="pf-ri__topology-demo">
<VisualizationProvider controller={controller}>
<TopologyComponent model={model} />
</VisualizationProvider>
</div>
);
});

NetworkGraph.displayName = 'NetworkGraph';

Expand Down
46 changes: 16 additions & 30 deletions ui/apps/platform/src/Containers/NetworkGraph/NetworkGraphPage.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
import React, { useEffect, useState } from 'react';
import { useParams, useHistory } from 'react-router-dom';
import { PageSection, Title, Flex, FlexItem } from '@patternfly/react-core';
import { PageSection, Title, Flex, FlexItem, Bullseye, Spinner } from '@patternfly/react-core';
import { Model } from '@patternfly/react-topology';

import { fetchNetworkFlowGraph } from 'services/NetworkService';
import { fetchClustersAsArray, Cluster } from 'services/ClustersService';
import { networkBasePathPF } from 'routePaths';

import PageTitle from 'Components/PageTitle';
import NetworkGraph from './NetworkGraph';
import { transformData, graphModel } from './utils';

import './NetworkGraphPage.css';

const emptyModel = {
graph: graphModel,
};

function NetworkGraphPage() {
const history = useHistory();
const { detailType, detailId } = useParams();
const [model, setModel] = useState<Model>({
graph: graphModel,
});
const [model, setModel] = useState<Model>(emptyModel);
const [isLoading, setIsLoading] = useState(false);
const [clusters, setClusters] = useState<Cluster[]>([]);

useEffect(() => {
Expand All @@ -33,31 +32,19 @@ function NetworkGraphPage() {

useEffect(() => {
if (clusters.length > 0) {
setIsLoading(true);
fetchNetworkFlowGraph(clusters[0].id, [])
.then(({ response }) => {
const dataModel = transformData(response.nodes);
setModel(dataModel);
})
.catch(() => {
// TODO
});
})
.finally(() => setIsLoading(false));
}
}, [clusters]);

function onSelectNode(type: string, id: string) {
// if found, and it's not the logical grouping of all external sources, then trigger URL update
if (id !== 'EXTERNAL') {
history.push(`${networkBasePathPF}/${type}/${id}`);
} else {
// otherwise, return to the graph-only state
history.push(`${networkBasePathPF}`);
}
}

function closeSidebar() {
history.push(`${networkBasePathPF}`);
}

return (
<>
<PageTitle title="Network Graph" />
Expand All @@ -69,13 +56,12 @@ function NetworkGraphPage() {
</Flex>
</PageSection>
<PageSection className="network-graph no-padding">
<NetworkGraph
detailType={detailType}
detailId={detailId}
model={model}
closeSidebar={closeSidebar}
onSelectNode={onSelectNode}
/>
{model.nodes && <NetworkGraph model={model} />}
{isLoading && (
<Bullseye>
<Spinner isSVG />
</Bullseye>
)}
</PageSection>
</>
);
Expand Down
3 changes: 1 addition & 2 deletions ui/apps/platform/src/Containers/NetworkGraph/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,7 @@ export function transformData(nodes: Node[]): Model {
type: 'edge',
source: entity.id,
target: nodes[nodeIdx].entity.id,
// TODO: figure out how to conditionally render performantly
// visible: false,
visible: false,
};
dataModel.edges.push(edge);
});
Expand Down