diff --git a/src/pages/Topology/__tests__/TopologyProcesses.spec.tsx b/src/pages/Topology/__tests__/TopologyProcesses.spec.tsx index 1d24b8b09..0bf4e6f30 100644 --- a/src/pages/Topology/__tests__/TopologyProcesses.spec.tsx +++ b/src/pages/Topology/__tests__/TopologyProcesses.spec.tsx @@ -3,7 +3,6 @@ import { FC, forwardRef, memo, Suspense, useImperativeHandle } from 'react'; import { Button } from '@patternfly/react-core'; import { fireEvent, render, screen, waitForElementToBeRemoved } from '@testing-library/react'; import { Server } from 'miragejs'; -import * as router from 'react-router'; import { waitForElementToBeRemovedTimeout } from '@config/config'; import { getTestsIds } from '@config/testIds'; @@ -18,8 +17,6 @@ import TopologyProcesses from '../components/TopologyProcesses'; import { TopologyLabels } from '../Topology.enum'; import { NodeOrEdgeListProps } from '../Topology.interfaces'; -const navigate = jest.fn(); - const processesResults = processesData.results; const servicesResults = servicesData.results; const serviceIdSelected = servicesResults[2].identity; @@ -105,17 +102,6 @@ describe('Topology Process', () => { fireEvent.click(screen.getByText('onClickEdge')); }); - it('should clicking on a combo', async () => { - jest.spyOn(router, 'useNavigate').mockImplementation(() => navigate); - - await waitForElementToBeRemoved(() => screen.queryByTestId(getTestsIds.loadingView()), { - timeout: waitForElementToBeRemovedTimeout - }); - - fireEvent.click(screen.getByText('onClickCombo')); - expect(navigate).toHaveBeenCalledTimes(1); - }); - it('should save node positions and display info alert when handleSaveTopology is called', async () => { await waitForElementToBeRemoved(() => screen.queryByTestId(getTestsIds.loadingView()), { timeout: waitForElementToBeRemovedTimeout diff --git a/src/pages/Topology/__tests__/TopologyToasts.spec.tsx b/src/pages/Topology/__tests__/TopologyToasts.spec.tsx new file mode 100644 index 000000000..342763212 --- /dev/null +++ b/src/pages/Topology/__tests__/TopologyToasts.spec.tsx @@ -0,0 +1,33 @@ +import { createRef } from 'react'; + +import { render, waitFor } from '@testing-library/react'; +import eventUser from '@testing-library/user-event'; + +import AlertToast, { ToastExposeMethods } from '../components/TopologyToasts'; + +describe('AlertToast', () => { + it('should add a new alert when addMessage is called', async () => { + const toastRef = createRef(); + const { findByText } = render(); + + const addMessage = toastRef.current?.addMessage; + expect(addMessage).toBeDefined(); + + addMessage!('This is a toast message!'); + expect(await findByText('This is a toast message!')).toBeInTheDocument(); + }); + + it('should remove an alert when the close button is clicked', async () => { + const toastRef = createRef(); + const { getByTestId } = render(); + + const addMessage = toastRef.current?.addMessage; + expect(addMessage).toBeDefined(); + + addMessage!('This is a toast message!'); + const toast = await waitFor(() => getByTestId(`sk-toast-0`)); + + await eventUser.click(toast.querySelector('button') as HTMLButtonElement); + expect(toast).not.toBeInTheDocument(); + }); +}); diff --git a/src/pages/Topology/components/TopologyProcessGroups.tsx b/src/pages/Topology/components/TopologyProcessGroups.tsx index e896440a5..f8ffc7e6e 100644 --- a/src/pages/Topology/components/TopologyProcessGroups.tsx +++ b/src/pages/Topology/components/TopologyProcessGroups.tsx @@ -1,27 +1,11 @@ -import { FC, Key, useCallback, useRef, useState, ComponentType } from 'react'; +import { FC, useCallback, useRef, useState, ComponentType } from 'react'; -import { - Alert, - AlertActionCloseButton, - AlertGroup, - AlertProps, - AlertVariant, - Button, - Checkbox, - Divider, - Stack, - StackItem, - Toolbar, - ToolbarContent, - ToolbarGroup, - ToolbarItem, - getUniqueId -} from '@patternfly/react-core'; +import { Divider, Stack, StackItem } from '@patternfly/react-core'; import { useSuspenseQueries } from '@tanstack/react-query'; import { useNavigate } from 'react-router-dom'; import { RESTApi } from '@API/REST.api'; -import { TOAST_VISIBILITY_TIMEOUT, UPDATE_INTERVAL } from '@config/config'; +import { UPDATE_INTERVAL } from '@config/config'; import { LAYOUT_TOPOLOGY_DEFAULT, LAYOUT_TOPOLOGY_SINGLE_NODE } from '@core/components/Graph/Graph.constants'; import { GraphNode, @@ -29,10 +13,10 @@ import { GraphReactAdaptorProps } from '@core/components/Graph/Graph.interfaces'; import GraphReactAdaptor from '@core/components/Graph/ReactAdaptor'; -import NavigationViewLink from '@core/components/NavigationViewLink'; import { ProcessGroupsRoutesPaths, QueriesProcessGroups } from '@pages/ProcessGroups/ProcessGroups.enum'; -import DisplayResources from './DisplayResources'; +import AlertToasts, { ToastExposeMethods } from './TopologyToasts'; +import TopologyToolbar from './TopologyToolbar'; import { TopologyController } from '../services'; import { TopologyLabels, QueriesTopology } from '../Topology.enum'; @@ -48,12 +32,12 @@ const TopologyProcessGroups: FC<{ id?: string; GraphComponent?: ComponentType(componentId); - const [alerts, setAlerts] = useState[]>([]); const [showOnlyNeighbours, setShowOnlyNeighbours] = useState(false); const [moveToNodeSelected, setMoveToNodeSelected] = useState(false); const graphRef = useRef(); + const toastRef = useRef(null); const [{ data: processGroups }, { data: processGroupsPairs }] = useSuspenseQueries({ queries: [ @@ -71,22 +55,10 @@ const TopologyProcessGroups: FC<{ id?: string; GraphComponent?: ComponentType { - setAlerts((prevAlerts) => [...prevAlerts, { title, variant, key }]); - }; - - const removeAlert = (key: Key) => { - setAlerts((prevAlerts) => [...prevAlerts.filter((alert) => alert.key !== key)]); - }; - - const addInfoAlert = useCallback((message: string) => { - addAlert(message, 'info', getUniqueId()); - }, []); - const handleSaveTopology = useCallback(() => { graphRef?.current?.saveNodePositions(); - addInfoAlert(TopologyLabels.ToastSave); - }, [addInfoAlert]); + toastRef.current?.addMessage(TopologyLabels.ToastSave); + }, []); const handleComponentSelected = useCallback((id?: string) => { setComponentIdSelected(id); @@ -131,66 +103,17 @@ const TopologyProcessGroups: FC<{ id?: string; GraphComponent?: ComponentType - - - - - ({ name: node.label, identity: node.id }))} - /> - - - - { - handleShowOnlyNeighboursChecked(checked); - }} - id="showOnlyNeighboursCheckbox" - /> - - - - { - handleMoveToNodeSelectedChecked(checked); - }} - id="moveToNodeSelectedCheckbox" - /> - - - - - - - - - - - - - - - - + @@ -206,17 +129,7 @@ const TopologyProcessGroups: FC<{ id?: string; GraphComponent?: ComponentType - - {alerts.map(({ key, title }) => ( - removeAlert(key as Key)} />} - /> - ))} - + ); }; diff --git a/src/pages/Topology/components/TopologyProcesses.tsx b/src/pages/Topology/components/TopologyProcesses.tsx index f98b07ca3..7a0be0f86 100644 --- a/src/pages/Topology/components/TopologyProcesses.tsx +++ b/src/pages/Topology/components/TopologyProcesses.tsx @@ -1,13 +1,6 @@ -import { ComponentType, FC, Key, startTransition, useCallback, useEffect, useRef, useState } from 'react'; +import { ComponentType, FC, startTransition, useCallback, useEffect, useRef, useState } from 'react'; import { - Alert, - AlertActionCloseButton, - AlertGroup, - AlertProps, - AlertVariant, - Button, - Checkbox, Divider, Drawer, DrawerActions, @@ -19,20 +12,13 @@ import { DrawerPanelContent, Stack, StackItem, - Title, - Toolbar, - ToolbarContent, - ToolbarGroup, - ToolbarItem, - Tooltip, - getUniqueId + Title } from '@patternfly/react-core'; -import { QuestionCircleIcon } from '@patternfly/react-icons'; import { useSuspenseQueries } from '@tanstack/react-query'; import { useNavigate } from 'react-router-dom'; import { RESTApi } from '@API/REST.api'; -import { MAX_NODE_COUNT_WITHOUT_AGGREGATION, TOAST_VISIBILITY_TIMEOUT, UPDATE_INTERVAL } from '@config/config'; +import { MAX_NODE_COUNT_WITHOUT_AGGREGATION, UPDATE_INTERVAL } from '@config/config'; import { LAYOUT_TOPOLOGY_DEFAULT, LAYOUT_TOPOLOGY_SINGLE_NODE } from '@core/components/Graph/Graph.constants'; import { GraphEdge, @@ -43,15 +29,12 @@ import { GraphEdgeMetrics } from '@core/components/Graph/Graph.interfaces'; import GraphReactAdaptor from '@core/components/Graph/ReactAdaptor'; -import NavigationViewLink from '@core/components/NavigationViewLink'; import { ProcessesLabels, ProcessesRoutesPaths, QueriesProcesses } from '@pages/Processes/Processes.enum'; import LoadingPage from '@pages/shared/Loading'; -import { SitesRoutesPaths } from '@pages/Sites/Sites.enum'; -import DisplayOptions from './DisplayOptions'; -import DisplayResources from './DisplayResources'; -import DisplayServices from './DisplayServices'; import NodeOrEdgeList from './NodeOrEdgeList'; +import AlertToasts, { ToastExposeMethods } from './TopologyToasts'; +import TopologyToolbar from './TopologyToolbar'; import { TopologyController, groupNodes, groupEdges as groupEdges } from '../services'; import { ROTATE_LINK_LABEL, @@ -102,11 +85,11 @@ const TopologyProcesses: FC<{ const [displayOptionsSelected, setDisplayOptionsSelected] = useState(configuration); - const [alerts, setAlerts] = useState[]>([]); const [modalType, setModalType] = useState<{ type: 'process' | 'processPair'; id: string } | undefined>(); const drawerRef = useRef(null); const graphRef = useRef(); + const toastRef = useRef(null); const isDisplayOptionActive = useCallback( (option: string) => displayOptionsSelected.includes(option), @@ -145,26 +128,14 @@ const TopologyProcesses: FC<{ ] }); - const addAlert = (title: string, variant: AlertProps['variant'], key: Key) => { - setAlerts((prevAlerts) => [...prevAlerts, { title, variant, key }]); - }; - - const removeAlert = (key: Key) => { - setAlerts((prevAlerts) => [...prevAlerts.filter((alert) => alert.key !== key)]); - }; - - const addInfoAlert = useCallback((message: string) => { - addAlert(message, 'info', getUniqueId()); - }, []); - - const handleResourceSelected = useCallback((id?: string) => { + const handleProcessSelected = useCallback((id?: string) => { setItemIdSelected(id); setModalType(undefined); }, []); const handleShowOnlyNeighboursChecked = useCallback((checked: boolean) => { if (checked) { - graphRef?.current?.saveNodePositions(); + graphRef.current?.saveNodePositions(); } setShowOnlyNeighbours(checked); @@ -174,14 +145,7 @@ const TopologyProcesses: FC<{ setMoveToNodeSelected(checked); }, []); - const handleGetSelectedSite = useCallback( - ({ id, label }: GraphCombo) => { - navigate(`${SitesRoutesPaths.Sites}/${label}@${id}`); - }, - [navigate] - ); - - const handleGetSelectedEdge = useCallback( + const handleSelectedLink = useCallback( ({ id, sourceName, source: sourceId, metrics: edgeMetrics }: GraphEdge) => { if (id.split('~').length > 1) { setModalType({ type: 'processPair', id }); @@ -198,7 +162,7 @@ const TopologyProcesses: FC<{ [navigate] ); - const handleGetSelectedNode = useCallback( + const handleSelectedNode = useCallback( ({ id, label }: { id: string; label: string }) => { if (id.split('~').length > 1) { setItemIdSelected(id); @@ -228,10 +192,10 @@ const TopologyProcesses: FC<{ const handleSaveTopology = useCallback(() => { localStorage.setItem(SERVICE_OPTIONS, JSON.stringify(serviceIdsSelected)); - graphRef?.current?.saveNodePositions(); + graphRef.current?.saveNodePositions(); - addInfoAlert(TopologyLabels.ToastSave); - }, [addInfoAlert, serviceIdsSelected]); + toastRef.current?.addMessage(TopologyLabels.ToastSave); + }, [serviceIdsSelected]); const handleLoadTopology = useCallback(() => { const ids = localStorage.getItem(SERVICE_OPTIONS); @@ -245,11 +209,10 @@ const TopologyProcesses: FC<{ setDisplayOptionsSelected(options !== 'undefined' ? JSON.parse(options) : undefined); } - addInfoAlert(TopologyLabels.ToastLoad); - }, [addInfoAlert]); + toastRef.current?.addMessage(TopologyLabels.ToastLoad); + }, []); - const handleCloseClick = () => { - setItemIdSelected(undefined); + const handleCloseModal = () => { setModalType(undefined); }; @@ -341,31 +304,6 @@ const TopologyProcesses: FC<{ return ; } - const displayOptions = displayOptionsForProcesses.map((option) => { - if (option.key === SHOW_LINK_REVERSE_LABEL) { - return { - ...option, - isDisabled: () => - !isDisplayOptionActive(SHOW_LINK_BYTES) && - !isDisplayOptionActive(SHOW_LINK_BYTERATE) && - !isDisplayOptionActive(SHOW_LINK_LATENCY) - }; - } - - if (option.key === ROTATE_LINK_LABEL) { - return { - ...option, - isDisabled: () => - !isDisplayOptionActive(SHOW_LINK_BYTES) && - !isDisplayOptionActive(SHOW_LINK_PROTOCOL) && - !isDisplayOptionActive(SHOW_LINK_BYTERATE) && - !isDisplayOptionActive(SHOW_LINK_LATENCY) - }; - } - - return option; - }); - const nodeIdSelected = nodes.find( ({ id }) => id.split('~').includes(itemIdSelected || '') || itemIdSelected === id )?.id; @@ -383,105 +321,19 @@ const TopologyProcesses: FC<{ filteredCombos = groups.filter(({ id }) => comboIdsFromNodes.includes(id)); } - const TopologyToolbar = ( - - - - - - - - - - - ({ name: node.label, identity: node.id }))} - /> - - - - { - handleShowOnlyNeighboursChecked(checked); - }} - id="showOnlyNeighboursCheckbox" - /> - - - - { - handleMoveToNodeSelectedChecked(checked); - }} - id="moveToNodeSelectedCheckbox" - /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); - const onExpand = () => { drawerRef.current && drawerRef.current.focus(); }; const panelContent = ( - + {TopologyLabels.TopologyModalTitle} - + - + {modalType?.type && ( - {TopologyToolbar} + @@ -512,26 +380,15 @@ const TopologyProcesses: FC<{ itemSelected={nodeIdSelected} layout={showOnlyNeighbours && nodeIdSelected ? LAYOUT_TOPOLOGY_SINGLE_NODE : LAYOUT_TOPOLOGY_DEFAULT} moveToSelectedNode={moveToNodeSelected && !!nodeIdSelected && !showOnlyNeighbours} - onClickCombo={handleGetSelectedSite} - onClickNode={handleGetSelectedNode} - onClickEdge={handleGetSelectedEdge} + onClickNode={handleSelectedNode} + onClickEdge={handleSelectedLink} /> - - {alerts.map(({ key, title }) => ( - removeAlert(key as Key)} />} - /> - ))} - + ); }; diff --git a/src/pages/Topology/components/TopologySite.tsx b/src/pages/Topology/components/TopologySite.tsx index f9554c62b..400cff2a1 100644 --- a/src/pages/Topology/components/TopologySite.tsx +++ b/src/pages/Topology/components/TopologySite.tsx @@ -1,28 +1,12 @@ -import { ComponentType, FC, Key, startTransition, useCallback, useEffect, useRef, useState } from 'react'; +import { ComponentType, FC, startTransition, useCallback, useEffect, useRef, useState } from 'react'; -import { - Alert, - AlertActionCloseButton, - AlertGroup, - AlertProps, - AlertVariant, - Button, - Checkbox, - Divider, - getUniqueId, - Stack, - StackItem, - Toolbar, - ToolbarContent, - ToolbarGroup, - ToolbarItem -} from '@patternfly/react-core'; +import { Divider, Stack, StackItem } from '@patternfly/react-core'; import { useSuspenseQueries } from '@tanstack/react-query'; import { useNavigate } from 'react-router-dom'; import { RESTApi } from '@API/REST.api'; import { FlowDirection } from '@API/REST.enum'; -import { TOAST_VISIBILITY_TIMEOUT, UPDATE_INTERVAL } from '@config/config'; +import { UPDATE_INTERVAL } from '@config/config'; import { prometheusSiteNameAndIdSeparator } from '@config/prometheus'; import { LAYOUT_TOPOLOGY_DEFAULT, LAYOUT_TOPOLOGY_SINGLE_NODE } from '@core/components/Graph/Graph.constants'; import { @@ -32,12 +16,11 @@ import { GraphReactAdaptorProps } from '@core/components/Graph/Graph.interfaces'; import GraphReactAdaptor from '@core/components/Graph/ReactAdaptor'; -import NavigationViewLink from '@core/components/NavigationViewLink'; import LoadingPage from '@pages/shared/Loading'; import { QueriesSites, SitesRoutesPaths } from '@pages/Sites/Sites.enum'; -import DisplayOptions from './DisplayOptions'; -import DisplayResources from './DisplayResources'; +import AlertToasts, { ToastExposeMethods } from './TopologyToasts'; +import TopologyToolbar from './TopologyToolbar'; import { TopologyController } from '../services'; import { displayOptionsForSites, @@ -64,7 +47,6 @@ const TopologySite: FC<{ id?: string | null; GraphComponent?: ComponentType(); const [edges, setEdges] = useState([]); const [siteIdSelected, setSiteIdSelected] = useState(); - const [alerts, setAlerts] = useState[]>([]); const [showOnlyNeighbours, setShowOnlyNeighbours] = useState(false); const [moveToNodeSelected, setMoveToNodeSelected] = useState(false); @@ -74,6 +56,7 @@ const TopologySite: FC<{ id?: string | null; GraphComponent?: ComponentType(configuration); const graphRef = useRef(); + const toastRef = useRef(null); const isDisplayOptionActive = useCallback( (option: string) => displayOptionsSelected.includes(option), @@ -123,22 +106,10 @@ const TopologySite: FC<{ id?: string | null; GraphComponent?: ComponentType { - setAlerts((prevAlerts) => [...prevAlerts, { title, variant, key }]); - }; - - const removeAlert = (key: Key) => { - setAlerts((prevAlerts) => [...prevAlerts.filter((alert) => alert.key !== key)]); - }; - - const addInfoAlert = useCallback((message: string) => { - addAlert(message, 'info', getUniqueId()); - }, []); - const handleSaveTopology = useCallback(() => { graphRef?.current?.saveNodePositions(); - addInfoAlert(TopologyLabels.ToastSave); - }, [addInfoAlert]); + toastRef.current?.addMessage(TopologyLabels.ToastSave); + }, []); const handleGetSelectedNode = useCallback( ({ id: idSelected }: GraphNode) => { @@ -236,39 +207,6 @@ const TopologySite: FC<{ id?: string | null; GraphComponent?: ComponentType; } - const displayOptions = displayOptionsForSites.map((option) => { - if (option.key === SHOW_LINK_BYTES || option.key === SHOW_LINK_BYTERATE || option.key === SHOW_LINK_LATENCY) { - return { - ...option, - isDisabled: () => !isDisplayOptionActive(SHOW_DATA_LINKS) - }; - } - - if (option.key === SHOW_LINK_REVERSE_LABEL) { - return { - ...option, - isDisabled: () => - !isDisplayOptionActive(SHOW_DATA_LINKS) || - (!isDisplayOptionActive(SHOW_LINK_BYTES) && - !isDisplayOptionActive(SHOW_LINK_BYTERATE) && - !isDisplayOptionActive(SHOW_LINK_LATENCY)) - }; - } - - if (option.key === ROTATE_LINK_LABEL) { - return { - ...option, - isDisabled: () => - !isDisplayOptionActive(SHOW_DATA_LINKS) || - (!isDisplayOptionActive(SHOW_LINK_BYTES) && - !isDisplayOptionActive(SHOW_LINK_BYTERATE) && - !isDisplayOptionActive(SHOW_LINK_LATENCY)) - }; - } - - return option; - }); - let filteredLinks = edges; let filteredNodes = nodes; @@ -278,80 +216,25 @@ const TopologySite: FC<{ id?: string | null; GraphComponent?: ComponentType idsFromService.includes(id)); } - const TopologyToolbar = ( - - - - - - - - - - - ({ name: node.label, identity: node.id }))} - /> - - - - { - handleShowOnlyNeighboursChecked(checked); - }} - id="showOnlyNeighboursCheckbox" - /> - - - - { - handleMoveToNodeSelectedChecked(checked); - }} - id="moveToNodeSelectedCheckbox" - /> - - - - - - - - - - - - - - - - - ); - return ( <> - {TopologyToolbar} + + - - {alerts.map(({ key, title }) => ( - removeAlert(key as Key)} />} - /> - ))} - + ); }; diff --git a/src/pages/Topology/components/TopologyToasts.tsx b/src/pages/Topology/components/TopologyToasts.tsx new file mode 100644 index 000000000..3fa36b9ce --- /dev/null +++ b/src/pages/Topology/components/TopologyToasts.tsx @@ -0,0 +1,58 @@ +import { ForwardRefRenderFunction, Key, MutableRefObject, forwardRef, useImperativeHandle, useState } from 'react'; + +import { + Alert, + AlertActionCloseButton, + AlertGroup, + AlertProps, + AlertVariant, + getUniqueId +} from '@patternfly/react-core'; + +import { TOAST_VISIBILITY_TIMEOUT } from '@config/config'; + +export interface ToastExposeMethods { + addMessage: (message: string) => void; +} + +interface AlertToastProps { + variant?: AlertVariant; + timeout?: number | boolean; + ref: MutableRefObject; +} + +const AlertToast: ForwardRefRenderFunction = function ( + { variant = AlertVariant.info, timeout = TOAST_VISIBILITY_TIMEOUT }, + ref +) { + const [alerts, setAlerts] = useState[]>([]); + + const addMessage = (title: string) => { + setAlerts((prevAlerts) => [...prevAlerts, { title, key: getUniqueId() }]); + }; + + const handleRemoveAlert = (key: Key) => { + setAlerts((prevAlerts) => [...prevAlerts.filter((alert) => alert.key !== key)]); + }; + + useImperativeHandle(ref, () => ({ + addMessage + })); + + return ( + + {alerts.map(({ key, title }, index) => ( + handleRemoveAlert(key as Key)} />} + /> + ))} + + ); +}; + +export default forwardRef(AlertToast); diff --git a/src/pages/Topology/components/TopologyToolbar.tsx b/src/pages/Topology/components/TopologyToolbar.tsx new file mode 100644 index 000000000..b92f3ba83 --- /dev/null +++ b/src/pages/Topology/components/TopologyToolbar.tsx @@ -0,0 +1,198 @@ +import React, { useCallback } from 'react'; + +import { Toolbar, ToolbarContent, ToolbarItem, ToolbarGroup, Checkbox, Button, Tooltip } from '@patternfly/react-core'; +import { QuestionCircleIcon } from '@patternfly/react-icons'; + +import { GraphNode } from '@core/components/Graph/Graph.interfaces'; +import NavigationViewLink from '@core/components/NavigationViewLink'; + +import DisplayOptions from './DisplayOptions'; +import DisplayResources from './DisplayResources'; +import DisplayServices from './DisplayServices'; +import { + ROTATE_LINK_LABEL, + SHOW_LINK_BYTERATE, + SHOW_LINK_BYTES, + SHOW_LINK_LATENCY, + SHOW_LINK_PROTOCOL, + SHOW_LINK_REVERSE_LABEL, + SHOW_ROUTER_LINKS +} from '../Topology.constants'; +import { TopologyLabels } from '../Topology.enum'; +import { DisplaySelectProps } from '../Topology.interfaces'; + +interface ToolbarProps { + nodes: GraphNode[]; + onProcessSelected?: (id?: string) => void; + displayOptions?: DisplaySelectProps[]; + onDisplayOptionSelected?: (options: string[]) => void; + defaultDisplayOptionsSelected?: string[]; + nodeIdSelected?: string; + showOnlyNeighbours?: boolean; + onShowOnlyNeighboursChecked?: (checked: boolean) => void; + moveToNodeSelected?: boolean; + onMoveToNodeSelectedChecked?: (checked: boolean) => void; + serviceIdsSelected?: string[]; + onServiceSelected?: (ids: string[] | undefined) => void; + onLoadTopology?: () => void; + onSaveTopology?: () => void; + linkToPage: string; +} + +const TopologyToolbar: React.FC = function ({ + nodes, + onProcessSelected, + displayOptions, + onDisplayOptionSelected, + defaultDisplayOptionsSelected, + nodeIdSelected, + showOnlyNeighbours, + onShowOnlyNeighboursChecked, + moveToNodeSelected, + onMoveToNodeSelectedChecked, + serviceIdsSelected, + onServiceSelected, + onLoadTopology, + onSaveTopology, + linkToPage +}) { + const isLinkOptionActive = useCallback( + () => + defaultDisplayOptionsSelected?.includes(SHOW_LINK_BYTES) || + defaultDisplayOptionsSelected?.includes(SHOW_LINK_BYTERATE) || + defaultDisplayOptionsSelected?.includes(SHOW_LINK_LATENCY), + [defaultDisplayOptionsSelected] + ); + + const isRotateOptionActive = useCallback( + () => isLinkOptionActive() || defaultDisplayOptionsSelected?.includes(SHOW_LINK_PROTOCOL) || [isLinkOptionActive], + [defaultDisplayOptionsSelected, isLinkOptionActive] + ); + + const getDisplayOptions = useCallback( + () => + (displayOptions || []).map((option) => { + if (option.key === SHOW_LINK_BYTES || option.key === SHOW_LINK_BYTERATE || option.key === SHOW_LINK_LATENCY) { + return { + ...option, + isDisabled: () => defaultDisplayOptionsSelected?.includes(SHOW_ROUTER_LINKS) + }; + } + + if (option.key === SHOW_LINK_REVERSE_LABEL) { + return { + ...option, + isDisabled: () => defaultDisplayOptionsSelected?.includes(SHOW_ROUTER_LINKS) || !isLinkOptionActive() + }; + } + + if (option.key === ROTATE_LINK_LABEL) { + return { + ...option, + isDisabled: () => defaultDisplayOptionsSelected?.includes(SHOW_ROUTER_LINKS) || !isRotateOptionActive() + }; + } + + return option; + }), + [defaultDisplayOptionsSelected, displayOptions, isLinkOptionActive, isRotateOptionActive] + ); + + return ( + + + {onDisplayOptionSelected && ( + <> + + + + + + + )} + + {onProcessSelected && ( + <> + + + ({ name: node.label, identity: node.id }))} + /> + + + + { + onShowOnlyNeighboursChecked?.(checked); + }} + id="showOnlyNeighboursCheckbox" + /> + + + + { + onMoveToNodeSelectedChecked?.(checked); + }} + id="moveToNodeSelectedCheckbox" + /> + + + + + + )} + + {onServiceSelected && ( + <> + + + + + + + + + + + + + + + + )} + + {onSaveTopology && ( + + + + )} + + + + + + + ); +}; + +export default TopologyToolbar;