From a6d04dbf27354a308fba29b3ecef6a7131bb22d0 Mon Sep 17 00:00:00 2001 From: Linda Song Date: Mon, 31 Oct 2022 18:31:07 -0700 Subject: [PATCH 1/2] got conditional rendering working for deployments and namespaces --- .../Containers/NetworkGraph/NetworkGraph.tsx | 108 ++++++++++++------ .../NetworkGraph/NetworkGraphPage.tsx | 26 +---- .../src/Containers/NetworkGraph/utils.tsx | 3 +- 3 files changed, 74 insertions(+), 63 deletions(-) diff --git a/ui/apps/platform/src/Containers/NetworkGraph/NetworkGraph.tsx b/ui/apps/platform/src/Containers/NetworkGraph/NetworkGraph.tsx index 54a078ff0fadf..f720dcea06330 100644 --- a/ui/apps/platform/src/Containers/NetworkGraph/NetworkGraph.tsx +++ b/ui/apps/platform/src/Containers/NetworkGraph/NetworkGraph.tsx @@ -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, @@ -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'; @@ -49,25 +51,58 @@ 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); + }); +} +const TopologyComponent = ({ model }: TopologyComponentProps) => { + const history = useHistory(); + const { detailId } = useParams(); + const selectedEntity = detailId && findEntityById(model, detailId); const controller = useVisualizationController(); + console.log('TopologyComponent'); + + if (detailId) { + const selectedNode = controller.getNodeById(detailId); + if (selectedNode?.isGroup()) { + const children = selectedNode.getAllNodeChildren(); + children.forEach((child) => { + // set visible edges + const relatedEdges = getNodeEdges(child); + setVisibleEdges(relatedEdges); + }); + } else if (selectedNode) { + // set visible edges + const relatedEdges = getNodeEdges(selectedNode); + setVisibleEdges(relatedEdges); + } + } else if (controller.hasGraph()) { + const edges = controller.getGraph().getEdges(); + edges.forEach((edge) => { + edge.setVisible(false); + }); + } + + function closeSidebar() { + history.push(`${networkBasePathPF}`); + } + React.useEffect(() => { function onSelect(ids: string[]) { const newSelectedId = ids?.[0] || ''; @@ -76,7 +111,13 @@ const TopologyComponent = ({ // @ts-ignore if (newSelectedEntity) { const [newDetailType, newDetailId] = getUrlParamsForEntity(newSelectedEntity); - onSelectNode(newDetailType, newDetailId); + // 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}`); + } } } @@ -86,7 +127,7 @@ const TopologyComponent = ({ return () => { controller.removeEventListener(SELECTION_EVENT, onSelect); }; - }, [controller, model, onSelectNode]); + }, [controller, model]); const selectedIds = selectedEntity ? [selectedEntity.id] : []; @@ -139,28 +180,21 @@ const TopologyComponent = ({ ); }; -const NetworkGraph = React.memo( - ({ detailType, detailId, closeSidebar, onSelectNode, model }) => { - const controller = new Visualization(); - controller.registerLayoutFactory(defaultLayoutFactory); - controller.registerComponentFactory(defaultComponentFactory); - controller.registerComponentFactory(stylesComponentFactory); - - return ( -
- - - -
- ); - } -); +const NetworkGraph = React.memo(({ model }) => { + const controller = new Visualization(); + controller.registerLayoutFactory(defaultLayoutFactory); + controller.registerComponentFactory(defaultComponentFactory); + controller.registerComponentFactory(stylesComponentFactory); + console.log('NetworkGraph'); + + return ( +
+ + + +
+ ); +}); NetworkGraph.displayName = 'NetworkGraph'; diff --git a/ui/apps/platform/src/Containers/NetworkGraph/NetworkGraphPage.tsx b/ui/apps/platform/src/Containers/NetworkGraph/NetworkGraphPage.tsx index 9d5c8d1ae170d..9f0808c2d2860 100644 --- a/ui/apps/platform/src/Containers/NetworkGraph/NetworkGraphPage.tsx +++ b/ui/apps/platform/src/Containers/NetworkGraph/NetworkGraphPage.tsx @@ -1,11 +1,9 @@ import React, { useEffect, useState } from 'react'; -import { useParams, useHistory } from 'react-router-dom'; import { PageSection, Title, Flex, FlexItem } 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'; @@ -14,8 +12,6 @@ import { transformData, graphModel } from './utils'; import './NetworkGraphPage.css'; function NetworkGraphPage() { - const history = useHistory(); - const { detailType, detailId } = useParams(); const [model, setModel] = useState({ graph: graphModel, }); @@ -44,19 +40,7 @@ function NetworkGraphPage() { } }, [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}`); - } + console.log('NetworkGraphPage'); return ( <> @@ -69,13 +53,7 @@ function NetworkGraphPage() { - + ); diff --git a/ui/apps/platform/src/Containers/NetworkGraph/utils.tsx b/ui/apps/platform/src/Containers/NetworkGraph/utils.tsx index cf5ccb6a8f4c6..fa26d34cf7483 100644 --- a/ui/apps/platform/src/Containers/NetworkGraph/utils.tsx +++ b/ui/apps/platform/src/Containers/NetworkGraph/utils.tsx @@ -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); }); From 4f405faf4fb200cae825ffe0d5e213c6ba000378 Mon Sep 17 00:00:00 2001 From: Linda Song Date: Thu, 3 Nov 2022 16:56:20 -0700 Subject: [PATCH 2/2] clearing edges on subsequent selected nodes; adding spinner --- .../Containers/NetworkGraph/NetworkGraph.tsx | 72 ++++++++++--------- .../NetworkGraph/NetworkGraphPage.tsx | 24 ++++--- 2 files changed, 54 insertions(+), 42 deletions(-) diff --git a/ui/apps/platform/src/Containers/NetworkGraph/NetworkGraph.tsx b/ui/apps/platform/src/Containers/NetworkGraph/NetworkGraph.tsx index f720dcea06330..2fb3406b217b2 100644 --- a/ui/apps/platform/src/Containers/NetworkGraph/NetworkGraph.tsx +++ b/ui/apps/platform/src/Containers/NetworkGraph/NetworkGraph.tsx @@ -70,60 +70,65 @@ function setVisibleEdges(edges) { }); } -const TopologyComponent = ({ model }: TopologyComponentProps) => { - const history = useHistory(); - const { detailId } = useParams(); - const selectedEntity = detailId && findEntityById(model, detailId); - const controller = useVisualizationController(); - - console.log('TopologyComponent'); +function setEdges(controller, detailId) { + controller + .getGraph() + .getEdges() + .forEach((edge) => { + edge.setVisible(false); + }); if (detailId) { const selectedNode = controller.getNodeById(detailId); if (selectedNode?.isGroup()) { - const children = selectedNode.getAllNodeChildren(); - children.forEach((child) => { + selectedNode.getAllNodeChildren().forEach((child) => { // set visible edges - const relatedEdges = getNodeEdges(child); - setVisibleEdges(relatedEdges); + setVisibleEdges(getNodeEdges(child)); }); } else if (selectedNode) { // set visible edges - const relatedEdges = getNodeEdges(selectedNode); - setVisibleEdges(relatedEdges); + setVisibleEdges(getNodeEdges(selectedNode)); } - } else if (controller.hasGraph()) { - const edges = controller.getGraph().getEdges(); - edges.forEach((edge) => { - edge.setVisible(false); - }); + } +} + +const TopologyComponent = ({ model }: TopologyComponentProps) => { + const history = useHistory(); + const { detailId } = useParams(); + const selectedEntity = detailId && findEntityById(model, detailId); + const controller = useVisualizationController(); + + // to prevent error where graph hasn't initialized yet + if (controller.hasGraph()) { + setEdges(controller, detailId); } function closeSidebar() { history.push(`${networkBasePathPF}`); } - 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); - // 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}`); - } + 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); }; @@ -185,7 +190,6 @@ const NetworkGraph = React.memo(({ model }) => { controller.registerLayoutFactory(defaultLayoutFactory); controller.registerComponentFactory(defaultComponentFactory); controller.registerComponentFactory(stylesComponentFactory); - console.log('NetworkGraph'); return (
diff --git a/ui/apps/platform/src/Containers/NetworkGraph/NetworkGraphPage.tsx b/ui/apps/platform/src/Containers/NetworkGraph/NetworkGraphPage.tsx index 9f0808c2d2860..6a81bdb3e490d 100644 --- a/ui/apps/platform/src/Containers/NetworkGraph/NetworkGraphPage.tsx +++ b/ui/apps/platform/src/Containers/NetworkGraph/NetworkGraphPage.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react'; -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'; @@ -11,10 +11,13 @@ import { transformData, graphModel } from './utils'; import './NetworkGraphPage.css'; +const emptyModel = { + graph: graphModel, +}; + function NetworkGraphPage() { - const [model, setModel] = useState({ - graph: graphModel, - }); + const [model, setModel] = useState(emptyModel); + const [isLoading, setIsLoading] = useState(false); const [clusters, setClusters] = useState([]); useEffect(() => { @@ -29,6 +32,7 @@ function NetworkGraphPage() { useEffect(() => { if (clusters.length > 0) { + setIsLoading(true); fetchNetworkFlowGraph(clusters[0].id, []) .then(({ response }) => { const dataModel = transformData(response.nodes); @@ -36,12 +40,11 @@ function NetworkGraphPage() { }) .catch(() => { // TODO - }); + }) + .finally(() => setIsLoading(false)); } }, [clusters]); - console.log('NetworkGraphPage'); - return ( <> @@ -53,7 +56,12 @@ function NetworkGraphPage() { - + {model.nodes && } + {isLoading && ( + + + + )} );