diff --git a/__tests__/pages/Metrics/Filters.spec.tsx b/__tests__/pages/Metrics/Filters.spec.tsx index 6aff892b..e105f639 100644 --- a/__tests__/pages/Metrics/Filters.spec.tsx +++ b/__tests__/pages/Metrics/Filters.spec.tsx @@ -4,7 +4,7 @@ import { Server } from 'miragejs'; import processesData from '../../../mocks/data/PROCESSES.json'; import siteData from '../../../mocks/data/SITES.json'; import { loadMockServer } from '../../../mocks/server'; -import { AvailableProtocols } from '../../../src/API/REST.enum'; +import { Protocols } from '../../../src/API/REST.enum'; import { waitForElementToBeRemovedTimeout } from '../../../src/config/config'; import MetricFilters from '../../../src/pages/shared/Metrics/components/Filters'; import { MetricsLabels } from '../../../src/pages/shared/Metrics/Metrics.enum'; @@ -36,7 +36,7 @@ describe('Metrics component', () => { { destinationName: processesData.results[2].name }, { destinationName: processesData.results[3].name } ]} - availableProtocols={[AvailableProtocols.Http, AvailableProtocols.Http2, AvailableProtocols.Tcp]} + availableProtocols={[Protocols.Http, Protocols.Http2, Protocols.Tcp]} configFilters={{ destinationProcesses: { disabled: false, placeholder: MetricsLabels.FilterAllDestinationProcesses }, sourceProcesses: { disabled: false, placeholder: MetricsLabels.FilterAllSourceProcesses }, @@ -99,7 +99,7 @@ describe('Metrics component', () => { { destinationName: processesData.results[2].name }, { destinationName: processesData.results[3].name } ]} - availableProtocols={[AvailableProtocols.Http, AvailableProtocols.Http2, AvailableProtocols.Tcp]} + availableProtocols={[Protocols.Http, Protocols.Http2, Protocols.Tcp]} configFilters={{ destinationProcesses: { disabled: false, placeholder: MetricsLabels.FilterAllDestinationProcesses }, sourceProcesses: { disabled: false, placeholder: MetricsLabels.FilterAllSourceProcesses }, @@ -123,11 +123,11 @@ describe('Metrics component', () => { }); fireEvent.click(screen.getByText(MetricsLabels.FilterProtocolsDefault)); - await waitFor(() => expect(screen.getByText(AvailableProtocols.Http2)).toBeInTheDocument(), { + await waitFor(() => expect(screen.getByText(Protocols.Http2)).toBeInTheDocument(), { timeout: waitForElementToBeRemovedTimeout }); - fireEvent.click(screen.getByText(AvailableProtocols.Http2)); + fireEvent.click(screen.getByText(Protocols.Http2)); await waitFor(() => expect(screen.queryByText(MetricsLabels.FilterProtocolsDefault)).not.toBeInTheDocument(), { timeout: waitForElementToBeRemovedTimeout }); diff --git a/__tests__/pages/Metrics/Metrics.spec.tsx b/__tests__/pages/Metrics/Metrics.spec.tsx index 1a11f84e..20b9e250 100644 --- a/__tests__/pages/Metrics/Metrics.spec.tsx +++ b/__tests__/pages/Metrics/Metrics.spec.tsx @@ -2,7 +2,7 @@ import { Suspense } from 'react'; import { act, render, renderHook } from '@testing-library/react'; -import { AvailableProtocols } from '../../../src/API/REST.enum'; +import { Protocols } from '../../../src/API/REST.enum'; import { Wrapper } from '../../../src/core/components/Wrapper'; import LoadingPage from '../../../src/pages/shared/Loading'; import Metrics, { MetricsProps, useMetrics } from '../../../src/pages/shared/Metrics'; @@ -10,7 +10,7 @@ import { configDefaultFilters } from '../../../src/pages/shared/Metrics/Metrics. describe('useMetrics', () => { const initialProps = { - defaultMetricFilterValues: { protocol: AvailableProtocols.Http }, + defaultMetricFilterValues: { protocol: Protocols.Http }, defaultOpenSections: { byterate: true }, onGetMetricFiltersConfig: jest.fn(), onGetExpandedSectionsConfig: jest.fn() @@ -18,7 +18,7 @@ describe('useMetrics', () => { it('initializes with the correct state from props', () => { const { result } = renderHook(() => useMetrics(initialProps)); - expect(result.current.queryParams).toEqual({ protocol: AvailableProtocols.Http }); + expect(result.current.queryParams).toEqual({ protocol: Protocols.Http }); expect(result.current.shouldUpdateData).toBe(0); }); @@ -32,7 +32,7 @@ describe('useMetrics', () => { it('updates queryParams and calls onGetMetricFiltersConfig when handleFilterChange is invoked', () => { const { result } = renderHook(() => useMetrics(initialProps)); - const newFilters = { protocol: AvailableProtocols.Tcp }; + const newFilters = { protocol: Protocols.Tcp }; act(() => { result.current.handleFilterChange(newFilters); }); @@ -62,7 +62,7 @@ describe('Metrics Component', () => { destSites: [], sourceProcesses: [], destProcesses: [], - availableProtocols: [AvailableProtocols.Http, AvailableProtocols.Tcp, AvailableProtocols.Http2], + availableProtocols: [Protocols.Http, Protocols.Tcp, Protocols.Http2], onGetMetricFiltersConfig: jest.fn(), onGetExpandedSectionsConfig: jest.fn(), ...overrides diff --git a/__tests__/pages/Metrics/useMetricsSessionHandler.spec.ts b/__tests__/pages/Metrics/useMetricsSessionHandler.spec.ts new file mode 100644 index 00000000..6122de2b --- /dev/null +++ b/__tests__/pages/Metrics/useMetricsSessionHandler.spec.ts @@ -0,0 +1,89 @@ +import { act } from 'react'; + +import { renderHook } from '@testing-library/react'; + +import { Direction, Protocols } from '@API/REST.enum'; +import { getDataFromSession, storeDataToSession } from '@core/utils/persistData'; +import { useMetricSessionHandlers } from '@pages/shared/Metrics/hooks/useMetricsSessionHandler'; +import { QueryMetricsParams, ExpandedMetricSections } from '@sk-types/Metrics.interfaces'; + +const mockQueryMetricsParams: QueryMetricsParams = { + sourceSite: 'SiteA', + destSite: 'SiteB', + sourceProcess: 'ProcessA', + destProcess: 'ProcessB', + sourceComponent: 'ComponentA', + destComponent: 'ComponentB', + service: 'ServiceA', + protocol: Protocols.Tcp, + start: 1620000000, + end: 1620003600, + duration: 3600, + direction: Direction.Incoming +}; + +const mockExpandedMetricSections: ExpandedMetricSections = { + byterate: true, + latency: false, + request: true, + response: true, + connection: false +}; + +// Mock the utility functions +jest.mock('@core/utils/persistData', () => ({ + getDataFromSession: jest.fn(), + storeDataToSession: jest.fn() +})); + +describe('useMetricSessionHandlers', () => { + const id = 'test-id'; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should return undefined for visibleMetrics if session data is missing', () => { + (getDataFromSession as jest.Mock).mockReturnValueOnce(undefined); + + const { result } = renderHook(() => useMetricSessionHandlers(id)); + expect(result.current.visibleMetrics).toBeUndefined(); + }); + + it('should retrieve selectedFilters and visibleMetrics from session', () => { + const mockFilters: QueryMetricsParams = mockQueryMetricsParams; + const mockVisibleMetrics = mockExpandedMetricSections; + + (getDataFromSession as jest.Mock).mockImplementation((key: string) => + key.includes('metric-filters') ? mockFilters : mockVisibleMetrics + ); + + const { result } = renderHook(() => useMetricSessionHandlers(id)); + + expect(result.current.selectedFilters).toEqual(mockFilters); + expect(result.current.visibleMetrics).toEqual(mockVisibleMetrics); + expect(getDataFromSession).toHaveBeenCalledTimes(2); + }); + + it('should store selectedFilters to session', () => { + const mockFilters: QueryMetricsParams = mockQueryMetricsParams; + const { result } = renderHook(() => useMetricSessionHandlers(id)); + + act(() => { + result.current.setSelectedFilters(mockFilters); + }); + + expect(storeDataToSession).toHaveBeenCalledWith(`metric-filters-${id}`, mockFilters); + }); + + it('should store visibleMetrics to session', () => { + const mockVisibleMetrics = mockExpandedMetricSections; + const { result } = renderHook(() => useMetricSessionHandlers(id)); + + act(() => { + result.current.setVisibleMetrics(mockVisibleMetrics); + }); + + expect(storeDataToSession).toHaveBeenCalledWith(`metric-sections-${id}`, mockVisibleMetrics); + }); +}); diff --git a/__tests__/pages/Processes/ProcessPairs.spec.tsx b/__tests__/pages/Processes/ProcessPairs.spec.tsx index 1cd4893f..154fac69 100644 --- a/__tests__/pages/Processes/ProcessPairs.spec.tsx +++ b/__tests__/pages/Processes/ProcessPairs.spec.tsx @@ -11,7 +11,7 @@ import { loadMockServer } from '../../../mocks/server'; import { waitForElementToBeRemovedTimeout } from '../../../src/config/config'; import { getTestsIds } from '../../../src/config/testIds'; import { Wrapper } from '../../../src/core/components/Wrapper'; -import ProcessPairs, { ProcessPairsContent } from '../../../src/pages/Processes/views/ProcessPairs'; +import ProcessPair, { ProcessPairContent } from '../../../src/pages/Processes/views/ProcessPair'; import LoadingPage from '../../../src/pages/shared/Loading'; import { ProcessPairsResponse, SitePairsResponse } from '../../../src/types/REST.interfaces'; @@ -37,7 +37,7 @@ describe('Begin testing the Processes component', () => { render( }> - + ); @@ -54,7 +54,7 @@ describe('Begin testing the Processes component', () => { render( }> - + ); @@ -70,7 +70,7 @@ describe('Begin testing the Processes component', () => { const { queryByTestId, getByTestId } = render( }> - + ); @@ -86,7 +86,7 @@ describe('Begin testing the Processes component', () => { const { queryByTestId, getByText, getByTestId } = render( }> - + ); diff --git a/__tests__/pages/Processes/ProcessPairsList.spec.tsx b/__tests__/pages/Processes/ProcessPairsList.spec.tsx index 45ddc78d..184a292e 100644 --- a/__tests__/pages/Processes/ProcessPairsList.spec.tsx +++ b/__tests__/pages/Processes/ProcessPairsList.spec.tsx @@ -10,7 +10,7 @@ import { loadMockServer } from '../../../mocks/server'; import { waitForElementToBeRemovedTimeout } from '../../../src/config/config'; import { getTestsIds } from '../../../src/config/testIds'; import { Wrapper } from '../../../src/core/components/Wrapper'; -import ProcessPairsList from '../../../src/pages/Processes/components/ProcessPairsList'; +import PairsList from '../../../src/pages/Processes/components/PairsList'; import { ProcessesLabels, ProcessesRoutesPaths } from '../../../src/pages/Processes/Processes.enum'; import LoadingPage from '../../../src/pages/shared/Loading'; import { ProcessPairsResponse, ProcessResponse, SitePairsResponse } from '../../../src/types/REST.interfaces'; @@ -29,7 +29,7 @@ describe('Processes Pairs component', () => { render( }> - + ); @@ -45,7 +45,7 @@ describe('Processes Pairs component', () => { timeout: waitForElementToBeRemovedTimeout }); - expect(screen.getAllByRole('link', { name: 'view pairs' })[1]).toHaveAttribute( + expect(screen.getAllByRole('link', { name: ProcessesLabels.GoToDetailsLink })[1]).toHaveAttribute( 'href', `#${ProcessesRoutesPaths.Processes}/${processPairsResult.sourceName}@${processPairsResult.sourceId}/${ProcessesLabels.ProcessPairs}@${processPairsResult.identity}?type=${ProcessesLabels.ProcessPairs}` ); diff --git a/__tests__/pages/Sites/SiteController.spec.ts b/__tests__/pages/Sites/SiteController.spec.ts deleted file mode 100644 index df822e3d..00000000 --- a/__tests__/pages/Sites/SiteController.spec.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { LinkResponse, SiteResponse } from '../../../src/types/REST.interfaces'; -import SitesController from '../../../src/pages/Sites/services'; - -describe('SitesController', () => { - const emptySites: SiteResponse[] = []; - const emptyLinks: LinkResponse[] = []; - - it('should return an empty array if sites input is empty', () => { - const result = SitesController.bindLinksWithSiteIds(emptySites, []); - expect(result).toEqual([]); - }); - - it('should return an empty array if links input is empty', () => { - const result = SitesController.bindLinksWithSiteIds([], emptyLinks); - expect(result).toEqual([]); - }); - - it('should return an empty array if routers input is empty', () => { - const result = SitesController.bindLinksWithSiteIds([], []); - expect(result).toEqual([]); - }); - - it('should return an empty array if all inputs are empty', () => { - const result = SitesController.bindLinksWithSiteIds(emptySites, emptyLinks); - expect(result).toEqual([]); - }); -}); diff --git a/__tests__/pages/Topology/DisplayOptions.spec.tsx b/__tests__/pages/Topology/DisplayOptions.spec.tsx index 1e24ae85..5fe49dd4 100644 --- a/__tests__/pages/Topology/DisplayOptions.spec.tsx +++ b/__tests__/pages/Topology/DisplayOptions.spec.tsx @@ -1,7 +1,9 @@ import { renderHook, render, act } from '@testing-library/react'; import eventUser from '@testing-library/user-event'; -import DisplayOptions, { useDisplayOptions } from '../../../src/pages/Topology/components/DisplayOptions'; +import { useDisplayOptionsState } from '@pages/Topology/hooks/useDisplayOptionsState'; + +import DisplayOptions from '../../../src/pages/Topology/components/DisplayOptions'; import { SHOW_DATA_LINKS, SHOW_ROUTER_LINKS } from '../../../src/pages/Topology/Topology.constants'; import { TopologyLabels } from '../../../src/pages/Topology/Topology.enum'; @@ -21,13 +23,13 @@ const options = { describe('useDisplayOptions', () => { it('initializes with defaultSelected values', () => { const { result } = renderHook(() => - useDisplayOptions({ defaultSelected: ['option1', 'option2'], onSelected: mockOnSelected }) + useDisplayOptionsState({ defaultSelected: ['option1', 'option2'], onSelected: mockOnSelected }) ); expect(result.current.displayOptionsSelected).toEqual(['option1', 'option2']); }); it('adds and removes options correctly', () => { - const { result } = renderHook(() => useDisplayOptions({ defaultSelected: [], onSelected: mockOnSelected })); + const { result } = renderHook(() => useDisplayOptionsState({ defaultSelected: [], onSelected: mockOnSelected })); const { selectDisplayOptions } = result.current; // Add an option @@ -41,7 +43,7 @@ describe('useDisplayOptions', () => { it('toggles between SHOW_DATA_LINKS and SHOW_ROUTER_LINKS correctly', () => { const { result } = renderHook(() => - useDisplayOptions({ defaultSelected: [SHOW_DATA_LINKS], onSelected: mockOnSelected }) + useDisplayOptionsState({ defaultSelected: [SHOW_DATA_LINKS], onSelected: mockOnSelected }) ); // Initially, SHOW_DATA_LINKS is selected @@ -56,7 +58,7 @@ describe('useDisplayOptions', () => { it('clicks on SHOW_DATA_LINKS already checked and SHOW_ROUTER_LINKS correctly', () => { const { result } = renderHook(() => - useDisplayOptions({ defaultSelected: [SHOW_DATA_LINKS], onSelected: mockOnSelected }) + useDisplayOptionsState({ defaultSelected: [SHOW_DATA_LINKS], onSelected: mockOnSelected }) ); // Initially, SHOW_DATA_LINKS is selected @@ -69,7 +71,7 @@ describe('useDisplayOptions', () => { }); it('calls onSelect callback with updated options and selected option', () => { - const { result } = renderHook(() => useDisplayOptions({ defaultSelected: [], onSelected: mockOnSelected })); + const { result } = renderHook(() => useDisplayOptionsState({ defaultSelected: [], onSelected: mockOnSelected })); const { selectDisplayOptions } = result.current; act(() => selectDisplayOptions('option1')); diff --git a/mocks/server.ts b/mocks/server.ts index 3992badb..95624dce 100644 --- a/mocks/server.ts +++ b/mocks/server.ts @@ -1,11 +1,10 @@ import { createServer, Response } from 'miragejs'; -import { Direction } from '@API/REST.enum'; import { PrometheusMetricsV2 } from '@config/prometheus'; import { ServiceResponse, ProcessPairsResponse, - LinkResponse, + RouterLinkResponse, ComponentResponse, ProcessResponse, RouterResponse, @@ -32,7 +31,7 @@ const sitePairs: ResponseWrapper = require(`${path}/SITE_PA const processPairs: ResponseWrapper = require(`${path}/PROCESS_PAIRS.json`); const services: ResponseWrapper = require(`${path}/SERVICES.json`); const flowPairs: ResponseWrapper = require(`${path}/FLOW_PAIRS.json`); -const links: ResponseWrapper = require(`${path}/LINKS.json`); +const links: ResponseWrapper = require(`${path}/LINKS.json`); interface ApiProps { params: Record; @@ -100,7 +99,7 @@ for (let i = 0; i < ITEM_COUNT; i++) { }); } -const mockLinksForPerf: LinkResponse[] = []; +const mockLinksForPerf: RouterLinkResponse[] = []; mockSitePairsForPerf.forEach((_, index) => { const idx1 = Math.floor(Math.random() * ITEM_COUNT); const idx2 = Math.floor(Math.random() * ITEM_COUNT); @@ -111,27 +110,37 @@ mockSitePairsForPerf.forEach((_, index) => { mockLinksForPerf.push( { identity: `link-out-${index}`, - parent: '', startTime: 1674048706622878, endTime: 0, - mode: 'interior', - name: '', - linkCost: 1, - direction: Direction.Outgoing, + role: 'inter-router', + name: 'out link', + cost: 1, + octets: 10, + octetsReverse: 100, + peer: '', + routerId: '', sourceSiteId: site1.identity, - destinationSiteId: site2.identity + destinationSiteId: site2.identity, + sourceSiteName: site1.sourceName, + destinationSiteName: site2.destinationName, + status: 'up' }, { identity: `link-in-${index}`, - parent: '', startTime: 1674151543561656, endTime: 0, - mode: 'interior', - name: '', - linkCost: 1, - direction: Direction.Incoming, + role: 'edge-router', + name: 'in link', + octets: 205, + octetsReverse: 1100, + cost: 1, + peer: '', + routerId: '', sourceSiteId: site2.identity, - destinationSiteId: site1.identity + destinationSiteId: site1.identity, + sourceSiteName: site2.sourceName, + destinationSiteName: site1.destinationName, + status: 'up' } ); }); @@ -395,7 +404,7 @@ export const MockApiPaths = { ProcessPairs: `${prefix}/processpairs`, ProcessPair: `${prefix}/processpairs/:id`, Routers: `${prefix}/routers`, - Links: `${prefix}/links`, + Links: `${prefix}/routerlinks`, PrometheusQuery: `${prefix}/internal/prom/query/`, PrometheusRangeQuery: `${prefix}/internal/prom/rangequery/` }; diff --git a/package.json b/package.json index 371b0d62..a9a8c6d7 100644 --- a/package.json +++ b/package.json @@ -59,10 +59,10 @@ "@testing-library/react": "^16.0.1", "@testing-library/user-event": "^14.5.2", "@types/jest": "^29.5.13", - "@types/react": "^18.3.6", + "@types/react": "^18.3.7", "@types/react-dom": "^18.3.0", - "@typescript-eslint/eslint-plugin": "^8.5.0", - "@typescript-eslint/parser": "^8.5.0", + "@typescript-eslint/eslint-plugin": "^8.6.0", + "@typescript-eslint/parser": "^8.6.0", "commitizen": "^4.3.0", "copy-webpack-plugin": "^12.0.2", "css-loader": "^7.1.2", diff --git a/src/API/Prometheus.queries.ts b/src/API/Prometheus.queries.ts index d514b4cc..481dd693 100644 --- a/src/API/Prometheus.queries.ts +++ b/src/API/Prometheus.queries.ts @@ -1,7 +1,7 @@ import { PrometheusLabelsV2, PrometheusMetricsV2 } from '@config/prometheus'; import { IntervalTimePropValue } from '@sk-types/Prometheus.interfaces'; -import { AvailableProtocols, Quantiles } from './REST.enum'; +import { Protocols, Quantiles } from './REST.enum'; export const queries = { // data transfer queries @@ -27,7 +27,7 @@ export const queries = { // count the number of active tcp flows in the services table getOpenConnectionsByService() { - return `sum by(${PrometheusLabelsV2.RoutingKey})(${PrometheusMetricsV2.TcpOpenConnections}{protocol="${AvailableProtocols.Tcp}"}-${PrometheusMetricsV2.TcpCloseCOnnections}{protocol="${AvailableProtocols.Tcp}"})`; + return `sum by(${PrometheusLabelsV2.RoutingKey})(${PrometheusMetricsV2.TcpOpenConnections}{protocol="${Protocols.Tcp}"}-${PrometheusMetricsV2.TcpCloseCOnnections}{protocol="${Protocols.Tcp}"})`; }, // http request queries diff --git a/src/API/REST.api.ts b/src/API/REST.api.ts index 4cf0e552..1a3939a0 100644 --- a/src/API/REST.api.ts +++ b/src/API/REST.api.ts @@ -4,7 +4,7 @@ import { ProcessResponse, FlowPairsResponse, SiteResponse, - LinkResponse, + RouterLinkResponse, ProcessPairsResponse, QueryFilters, ResponseWrapper, @@ -16,7 +16,6 @@ import { import { axiosFetch } from './apiMiddleware'; import { getSitePATH, - getLinksBySitePATH, getComponentPATH, geProcessPATH, getLinkPATH, @@ -63,14 +62,6 @@ export const RESTApi = { return data; }, - fetchLinksBySite: async (id: string, options?: QueryFilters): Promise> => { - const data = await axiosFetch>(getLinksBySitePATH(id), { - params: options ? mapQueryFiltersToQueryParams(options) : null - }); - - return data; - }, - // PROCESS APIs fetchProcesses: async (options?: QueryFilters): Promise> => { const data = await axiosFetch>(geProcessesPATH(), { @@ -106,16 +97,16 @@ export const RESTApi = { }, // LINKS APIs - fetchLinks: async (options?: QueryFilters): Promise> => { - const data = await axiosFetch>(getLinksPATH(), { + fetchLinks: async (options?: QueryFilters): Promise> => { + const data = await axiosFetch>(getLinksPATH(), { params: options ? mapQueryFiltersToQueryParams(options) : null }); return data; }, - fetchLink: async (id: string, options?: QueryFilters): Promise> => { - const data = await axiosFetch>(getLinkPATH(id), { + fetchLink: async (id: string, options?: QueryFilters): Promise> => { + const data = await axiosFetch>(getLinkPATH(id), { params: options ? mapQueryFiltersToQueryParams(options) : null }); diff --git a/src/API/REST.enum.ts b/src/API/REST.enum.ts index efd3f204..afdf8dcc 100644 --- a/src/API/REST.enum.ts +++ b/src/API/REST.enum.ts @@ -3,7 +3,7 @@ export enum SortDirection { DESC = 'desc' } -export enum AvailableProtocols { +export enum Protocols { Tcp = 'tcp', Http = 'http1', Http2 = 'http2', diff --git a/src/API/REST.paths.ts b/src/API/REST.paths.ts index 0ea72d49..38e119f6 100644 --- a/src/API/REST.paths.ts +++ b/src/API/REST.paths.ts @@ -1,14 +1,15 @@ import { API_URL } from '@config/config'; +import { composePath } from './REST.utils'; + export const getUser = () => `${API_URL}/user`; export const logout = () => `${API_URL}/logout?nonce=${Date.now()}`; const SITES_PATH = `${API_URL}/sites`; export const getSitesPATH = () => SITES_PATH; export const getSitePATH = (id: string) => composePath([SITES_PATH, id]); -export const getLinksBySitePATH = (id: string) => composePath([SITES_PATH, id, 'links']); -const LINKS_PATH = `${API_URL}/links`; +const LINKS_PATH = `${API_URL}/routerlinks`; export const getLinksPATH = () => LINKS_PATH; export const getLinkPATH = (id: string) => composePath([LINKS_PATH, id]); @@ -40,7 +41,3 @@ export const getComponentPairPATH = (id: string) => composePath([COMPONENT_PAIRS const PROCESS_PAIRS_PATH = `${API_URL}/processpairs`; export const getProcessPairsPATH = () => PROCESS_PAIRS_PATH; export const getProcessPairPairPATH = (id: string) => composePath([PROCESS_PAIRS_PATH, id]); - -function composePath(elements: string[]) { - return elements.join('/'); -} diff --git a/src/API/REST.utils.ts b/src/API/REST.utils.ts index 745ba460..10850943 100644 --- a/src/API/REST.utils.ts +++ b/src/API/REST.utils.ts @@ -2,9 +2,6 @@ import { QueryParams, QueryFilters } from '@sk-types/REST.interfaces'; import { SortDirection } from './REST.enum'; -/** - * Maps an options object to query parameters for an HTTP request. - */ export function mapQueryFiltersToQueryParams({ filter, offset, @@ -25,3 +22,12 @@ export function mapQueryFiltersToQueryParams({ ...queryParams }; } + +/** + * Composes a path from an array of elements. + * @param {string[]} elements - An array of elements that will be joined together to form the path. + * @returns {string} - The composed path as a string. + */ +export function composePath(elements: string[]): string { + return elements.join('/'); +} diff --git a/src/core/components/SkFlowPairsTable/FlowPair.tsx b/src/core/components/SkFlowPairsTable/FlowPair.tsx index 04ee58b1..e10db602 100644 --- a/src/core/components/SkFlowPairsTable/FlowPair.tsx +++ b/src/core/components/SkFlowPairsTable/FlowPair.tsx @@ -19,7 +19,7 @@ import { import { LaptopIcon, ServerIcon } from '@patternfly/react-icons'; import { Link } from 'react-router-dom'; -import { AvailableProtocols } from '@API/REST.enum'; +import { Protocols } from '@API/REST.enum'; import { getTestsIds } from '@config/testIds'; import ResourceIcon from '@core/components/ResourceIcon'; import { formatBytes } from '@core/utils/formatBytes'; @@ -37,7 +37,7 @@ interface FlowPairProps { const FlowPair: FC = function ({ flowPair }) { const { protocol, endTime: endTimeMicroSeconds, identity, traceSites, duration } = flowPair; - const hasHttp = protocol === AvailableProtocols.Http || protocol === AvailableProtocols.Http2; + const hasHttp = protocol === Protocols.Http || protocol === Protocols.Http2; return ( @@ -97,14 +97,14 @@ const FlowPair: FC = function ({ flowPair }) { - {flowPair.protocol === AvailableProtocols.Tcp ? ( + {flowPair.protocol === Protocols.Tcp ? ( ) : ( )} - {flowPair.protocol === AvailableProtocols.Tcp ? ( + {flowPair.protocol === Protocols.Tcp ? ( ) : ( diff --git a/src/core/components/SkGraph/services/index.ts b/src/core/components/SkGraph/services/index.ts index f2bff0af..88ca5099 100644 --- a/src/core/components/SkGraph/services/index.ts +++ b/src/core/components/SkGraph/services/index.ts @@ -144,19 +144,22 @@ export const GraphController = { edges: EdgeData[]; combos?: ComboData[]; } => { + // Match the combo of the node with the existing combos + const comboIds = combos?.map(({ id }) => id); + + // find bidirectional edges const transformedEdges = markPairs(sanitizeEdges(nodes, edges)) as (GraphEdge & { hasPair: boolean; })[]; - // Match the combo of the node with the existing combos - const comboIds = combos?.map(({ id }) => id); // calculate the visual distribution of the metrics const edgeMetrics = transformedEdges.map(({ metricValue, source, target }) => - // remove metrics within the same site source === target ? 0 : metricValue || 0 ); const maxMetricValue = Math.max(...edgeMetrics); - const minMetricValue = Math.min(...edgeMetrics.filter((metric) => metric)) || 0; + //exclude metric values that are 0 + //the fallback ensures that if the filtered result is empty, we get 0 instead of potentially Infinity or NaN + const minMetricValue = Math.min(...edgeMetrics.filter(Boolean)) || 0; return { nodes: nodes diff --git a/src/core/utils/getIdAndNameFromUrlParams.ts b/src/core/utils/getIdAndNameFromUrlParams.ts index 60e85b05..d64e1ab1 100644 --- a/src/core/utils/getIdAndNameFromUrlParams.ts +++ b/src/core/utils/getIdAndNameFromUrlParams.ts @@ -1,7 +1,7 @@ -import { AvailableProtocols } from '@API/REST.enum'; +import { Protocols } from '@API/REST.enum'; export function getIdAndNameFromUrlParams(urlParams: string) { const params = urlParams.split('@'); - return { name: params[0], id: params[1], protocol: params[2] as AvailableProtocols }; + return { name: params[0], id: params[1], protocol: params[2] as Protocols }; } diff --git a/src/pages/ProcessGroups/components/Overview..tsx b/src/pages/ProcessGroups/components/Overview..tsx index a1230927..bd1649ec 100644 --- a/src/pages/ProcessGroups/components/Overview..tsx +++ b/src/pages/ProcessGroups/components/Overview..tsx @@ -2,7 +2,7 @@ import { FC } from 'react'; import { extractUniqueValues } from '@core/utils/extractUniqueValues'; import Metrics from '@pages/shared/Metrics'; -import { useMetricSessionHandlers } from '@pages/shared/Metrics/hooks/useSessionHandler'; +import { useMetricSessionHandlers } from '@pages/shared/Metrics/hooks/useMetricsSessionHandler'; import { ComponentResponse, ProcessResponse } from '@sk-types/REST.interfaces'; import { useComponentOverviewData } from '../hooks/useOverviewData'; diff --git a/src/pages/ProcessGroups/components/ProcessList.tsx b/src/pages/ProcessGroups/components/ProcessList.tsx index 1e8517c4..34381d7c 100644 --- a/src/pages/ProcessGroups/components/ProcessList.tsx +++ b/src/pages/ProcessGroups/components/ProcessList.tsx @@ -3,7 +3,6 @@ import { FC } from 'react'; import SkTable from '@core/components/SkTable'; import { setColumnVisibility } from '@core/components/SkTable/SkTable.utils'; import { CustomProcessCells, processesTableColumns } from '@pages/Processes/Processes.constants'; -import { ProcessesLabels } from '@pages/Processes/Processes.enum'; import { ProcessResponse } from '@sk-types/REST.interfaces'; interface ProcessListProps { @@ -13,7 +12,6 @@ interface ProcessListProps { const ProcessList: FC = function ({ processes }) { return ( ) => SkLinkCell({ ...props, @@ -28,7 +28,13 @@ export const CustomProcessPairCells = { ByteRateFormatCell: (props: SkHighlightValueCellProps) => SkHighlightValueCell({ ...props, format: formatByteRate }), LatencyFormatCell: (props: SkHighlightValueCellProps) => - SkHighlightValueCell({ ...props, format: formatLatency }) + SkHighlightValueCell({ ...props, format: formatLatency }), + viewDetailsLinkCell: (props: SkLinkCellProps) => + SkLinkCell({ + ...props, + value: ProcessesLabels.GoToDetailsLink, + link: `${ProcessesRoutesPaths.Processes}/${props.data.sourceName}@${props.data.sourceId}/${ProcessesLabels.ProcessPairs}@${props.data.identity}?type=${ProcessesLabels.ProcessPairs}` + }) }; export const CustomProcessCells = { diff --git a/src/pages/Processes/Processes.enum.ts b/src/pages/Processes/Processes.enum.ts index b57402e4..3b88d94d 100644 --- a/src/pages/Processes/Processes.enum.ts +++ b/src/pages/Processes/Processes.enum.ts @@ -24,7 +24,7 @@ export enum ProcessesLabels { Overview = 'Overview', Description = 'A process represents running application code. On Kubernetes, a process is a pod. On Docker or Podman, a process is a container. On virtual machines or bare metal hosts', Details = 'Details', - ProcessPairs = 'Process pairs', + ProcessPairs = 'Pairs', Processes = 'Processes', Services = 'Routing keys', Process = 'Process', @@ -54,8 +54,8 @@ export enum ProcessesLabels { OldConnections = 'Connection history', ProcessPairsEmptyTitle = 'No connections or requests to display', ProcessPairsEmptyMessage = 'As new connections or requests are established, they will be dynamically added to the table for display', - GoToDetails = 'View details', - Title = 'Process pair details', + GoToDetailsLink = 'View details', + Title = 'Pair details', Protocol = 'Protocol', Bytes = 'Bytes', ByteRate = 'Byterate', diff --git a/src/pages/Processes/Processes.utls.ts b/src/pages/Processes/Processes.utls.ts new file mode 100644 index 00000000..811b6271 --- /dev/null +++ b/src/pages/Processes/Processes.utls.ts @@ -0,0 +1,32 @@ +import { Protocols } from '@API/REST.enum'; +import { ProcessPairsResponse } from '@sk-types/REST.interfaces'; + +export const filterPairsByProtocols = (clients: ProcessPairsResponse[], servers: ProcessPairsResponse[]) => { + const TCPClients = clients.filter(({ protocol }) => protocol === Protocols.Tcp); + const TCPServers = servers.filter(({ protocol }) => protocol === Protocols.Tcp); + + const HTTPClients = clients.filter(({ protocol }) => [Protocols.Http, Protocols.Http2].includes(protocol)); + const HTTPServers = servers.filter(({ protocol }) => [Protocols.Http, Protocols.Http2].includes(protocol)); + + const remoteClients = clients.filter(({ protocol }) => protocol === undefined); + const remoteServers = servers.filter(({ protocol }) => protocol === undefined); + + return { + TCPClients, + TCPServers, + HTTPClients, + HTTPServers, + remoteClients, + remoteServers + }; +}; + +export function invertProcessPairsList(processPairs: ProcessPairsResponse[]): ProcessPairsResponse[] { + return processPairs.map((pair) => ({ + ...pair, + sourceId: pair.destinationId, + sourceName: pair.destinationName, + destinationId: pair.sourceId, + destinationName: pair.sourceName + })); +} diff --git a/src/pages/Processes/components/FlowPairsList.tsx b/src/pages/Processes/components/FlowPairsList.tsx index 28bb1746..f7fdaa86 100644 --- a/src/pages/Processes/components/FlowPairsList.tsx +++ b/src/pages/Processes/components/FlowPairsList.tsx @@ -5,7 +5,7 @@ import { ResourcesEmptyIcon } from '@patternfly/react-icons'; import { useSuspenseQuery } from '@tanstack/react-query'; import { RESTApi } from '@API/REST.api'; -import { AvailableProtocols, SortDirection, TcpStatus } from '@API/REST.enum'; +import { Protocols, SortDirection, TcpStatus } from '@API/REST.enum'; import { DEFAULT_PAGINATION_SIZE, UPDATE_INTERVAL } from '@config/config'; import SKEmptyData from '@core/components/SkEmptyData'; import SkFlowPairsTable from '@core/components/SkFlowPairsTable'; @@ -32,30 +32,30 @@ const initPaginatedFlowPairsQueryParams: QueryFilters = { const initPaginatedHttpRequestsQueryParams: QueryFilters = { ...initPaginatedFlowPairsQueryParams, - protocol: AvailableProtocols.Http + protocol: Protocols.Http }; const initPaginatedHttp2RequestsQueryParams: QueryFilters = { ...initPaginatedFlowPairsQueryParams, - protocol: AvailableProtocols.Http2 + protocol: Protocols.Http2 }; const initPaginatedActiveConnectionsQueryParams: QueryFilters = { ...initPaginatedFlowPairsQueryParams, - protocol: AvailableProtocols.Tcp, + protocol: Protocols.Tcp, state: TcpStatus.Active }; const initPaginatedOldConnectionsQueryParams: QueryFilters = { ...initPaginatedFlowPairsQueryParams, - protocol: AvailableProtocols.Tcp, + protocol: Protocols.Tcp, state: TcpStatus.Terminated }; const initPaginatedQueryParams: QueryFiltersProtocolMap = { - [AvailableProtocols.Http]: initPaginatedHttpRequestsQueryParams, - [AvailableProtocols.Http2]: initPaginatedHttp2RequestsQueryParams, - [AvailableProtocols.Tcp]: { + [Protocols.Http]: initPaginatedHttpRequestsQueryParams, + [Protocols.Http2]: initPaginatedHttp2RequestsQueryParams, + [Protocols.Tcp]: { active: initPaginatedActiveConnectionsQueryParams, old: initPaginatedOldConnectionsQueryParams } @@ -83,7 +83,7 @@ const useFlowPairsQuery = ( return data; }; -const useProcessPairsContent = ({ protocol }: { protocol: AvailableProtocols | 'undefined' }) => { +const useProcessPairsContent = ({ protocol }: { protocol: Protocols | 'undefined' }) => { const [queryParamsPaginated, setQueryParamsPaginated] = useState(initPaginatedQueryParams); const handleGetFilters = useCallback( @@ -110,7 +110,7 @@ const useProcessPairsContent = ({ protocol }: { protocol: AvailableProtocols | ' interface FlowPairsListProps { sourceProcessId: string; destProcessId: string; - protocol: AvailableProtocols | 'undefined'; + protocol: Protocols | 'undefined'; } const FlowPairsList: FC = function ({ sourceProcessId, destProcessId, protocol }) { @@ -125,28 +125,28 @@ const FlowPairsList: FC = function ({ sourceProcessId, destP const { results: httpRequests, count: httpRequestsCount } = extractData( useFlowPairsQuery( - queryParamsPaginated[AvailableProtocols.Http], + queryParamsPaginated[Protocols.Http], sourceProcessId, destProcessId, - AvailableProtocols.Http === protocol || protocol === 'undefined' + Protocols.Http === protocol || protocol === 'undefined' ) ); const { results: http2Requests, count: http2RequestsCount } = extractData( useFlowPairsQuery( - queryParamsPaginated[AvailableProtocols.Http2], + queryParamsPaginated[Protocols.Http2], sourceProcessId, destProcessId, - AvailableProtocols.Http2 === protocol || protocol === 'undefined' + Protocols.Http2 === protocol || protocol === 'undefined' ) ); const { results: activeConnections, count: activeConnectionsCount } = extractData( - useFlowPairsQuery(queryParamsPaginated[AvailableProtocols.Tcp].active, sourceProcessId, destProcessId, true) + useFlowPairsQuery(queryParamsPaginated[Protocols.Tcp].active, sourceProcessId, destProcessId, true) ); const { results: oldConnections, count: oldConnectionsCount } = extractData( - useFlowPairsQuery(queryParamsPaginated[AvailableProtocols.Tcp].old, sourceProcessId, destProcessId, true) + useFlowPairsQuery(queryParamsPaginated[Protocols.Tcp].old, sourceProcessId, destProcessId, true) ); const activeTab = tabSelected @@ -161,100 +161,101 @@ const FlowPairsList: FC = function ({ sourceProcessId, destP ? TAB_4_KEY : ''; + if (!activeConnectionsCount && !oldConnectionsCount) { + return ( + + + + + + ); + } + return ( - <> - {!activeConnectionsCount && !oldConnectionsCount && ( - - - - - + + {!!activeConnectionsCount && ( + {ProcessesLabels.OpenConnections}}> + handleGetFilters(filters, 'active')} + /> + + )} + + {!!oldConnectionsCount && ( + {ProcessesLabels.OldConnections}} + > + handleGetFilters(filters, 'old')} + /> + + )} + + {!!http2RequestsCount && ( + {ProcessesLabels.Http2Requests}} + > + + + )} + + {!!httpRequestsCount && ( + {ProcessesLabels.HttpRequests}} + > + + )} - - {!!activeConnectionsCount && ( - {ProcessesLabels.OpenConnections}}> - handleGetFilters(filters, 'active')} - /> - - )} - - {!!oldConnectionsCount && ( - {ProcessesLabels.OldConnections}} - > - handleGetFilters(filters, 'old')} - /> - - )} - - {!!http2RequestsCount && ( - {ProcessesLabels.Http2Requests}} - > - - - )} - - {!!httpRequestsCount && ( - {ProcessesLabels.HttpRequests}} - > - - - )} - - + ); }; diff --git a/src/pages/Processes/components/Overview.tsx b/src/pages/Processes/components/Overview.tsx index 1dffdce0..d7d97384 100644 --- a/src/pages/Processes/components/Overview.tsx +++ b/src/pages/Processes/components/Overview.tsx @@ -3,7 +3,7 @@ import { FC } from 'react'; import { extractUniqueValues } from '@core/utils/extractUniqueValues'; import { mapDataToMetricFilterOptions } from '@core/utils/getResourcesFromPairs'; import Metrics from '@pages/shared/Metrics'; -import { useMetricSessionHandlers } from '@pages/shared/Metrics/hooks/useSessionHandler'; +import { useMetricSessionHandlers } from '@pages/shared/Metrics/hooks/useMetricsSessionHandler'; import { ProcessResponse } from '@sk-types/REST.interfaces'; import { useProcessOverviewData } from '../hooks/useOverviewData'; diff --git a/src/pages/Processes/components/PairsList.tsx b/src/pages/Processes/components/PairsList.tsx new file mode 100644 index 00000000..85aceed6 --- /dev/null +++ b/src/pages/Processes/components/PairsList.tsx @@ -0,0 +1,84 @@ +import { FC, useMemo } from 'react'; + +import { Card, Flex } from '@patternfly/react-core'; +import { SearchIcon } from '@patternfly/react-icons'; + +import { SMALL_PAGINATION_SIZE } from '@config/config'; +import { PrometheusLabelsV2 } from '@config/prometheus'; +import SKEmptyData from '@core/components/SkEmptyData'; +import SkTable from '@core/components/SkTable'; +import { TopologyController } from '@pages/Topology/services'; +import { ProcessPairsResponse, ProcessResponse } from '@sk-types/REST.interfaces'; +import { SKTableColumn } from '@sk-types/SkTable.interfaces'; + +import { useProcessPairsListData } from '../hooks/useProcessPairsListData'; +import { CustomPairsCells, processesConnectedColumns, processesHttpConnectedColumns } from '../Processes.constants'; +import { ProcessesLabels } from '../Processes.enum'; +import { filterPairsByProtocols, invertProcessPairsList } from '../Processes.utls'; + +interface PairsListProps { + process: ProcessResponse; +} + +const PairsList: FC = function ({ process: { identity: id, name } }) { + const { pairsTx, pairsRx, metricsTx, metricsRx } = useProcessPairsListData(id, name); + + const clients = invertProcessPairsList( + TopologyController.addMetricsToPairs({ + processesPairs: pairsRx, + metrics: metricsRx, + prometheusKey: PrometheusLabelsV2.SourceProcess, + processPairsKey: 'sourceName' + }) + ); + + const servers = TopologyController.addMetricsToPairs({ + processesPairs: pairsTx, + metrics: metricsTx, + prometheusKey: PrometheusLabelsV2.DestProcess, + processPairsKey: 'destinationName' + }); + const { TCPClients, TCPServers, HTTPClients, HTTPServers, remoteClients, remoteServers } = useMemo( + () => filterPairsByProtocols(clients, servers), + [clients, servers] + ); + + const renderTable = (title: string, rows: ProcessPairsResponse[], columns: SKTableColumn[]) => + !!rows.length && ( + + ); + + return ( + <> + {!TCPClients.length && + !TCPServers.length && + !HTTPClients.length && + !HTTPServers.length && + !remoteClients.length && + !remoteServers.length && ( + + + + )} + + + {renderTable(ProcessesLabels.TCPClients, TCPClients, processesConnectedColumns)} + {renderTable(ProcessesLabels.TCPServers, TCPServers, processesConnectedColumns)} + {renderTable(ProcessesLabels.HTTPClients, HTTPClients, processesHttpConnectedColumns)} + {renderTable(ProcessesLabels.HTTPServers, HTTPServers, processesHttpConnectedColumns)} + {renderTable(ProcessesLabels.RemoteClients, remoteClients, processesConnectedColumns)} + {renderTable(ProcessesLabels.RemoteServers, remoteServers, processesConnectedColumns)} + + + ); +}; + +export default PairsList; diff --git a/src/pages/Processes/components/ProcessPairsDetails.tsx b/src/pages/Processes/components/ProcessPairDetails.tsx similarity index 89% rename from src/pages/Processes/components/ProcessPairsDetails.tsx rename to src/pages/Processes/components/ProcessPairDetails.tsx index adfd9092..51749701 100644 --- a/src/pages/Processes/components/ProcessPairsDetails.tsx +++ b/src/pages/Processes/components/ProcessPairDetails.tsx @@ -10,12 +10,12 @@ import { ProcessResponse } from '@sk-types/REST.interfaces'; import Details from './Details'; import { ProcessesRoutesPaths } from '../Processes.enum'; -interface ProcessPairsDetailsProps { +interface ProcessPairDetailsProps { source: ProcessResponse; destination: ProcessResponse; } -const ProcessPairsDetails: FC = function ({ source, destination }) { +const ProcessPairDetails: FC = function ({ source, destination }) { return ( @@ -51,4 +51,4 @@ const ProcessPairsDetails: FC = function ({ source, de ); }; -export default ProcessPairsDetails; +export default ProcessPairDetails; diff --git a/src/pages/Processes/components/ProcessPairsList.tsx b/src/pages/Processes/components/ProcessPairsList.tsx deleted file mode 100644 index 29db8701..00000000 --- a/src/pages/Processes/components/ProcessPairsList.tsx +++ /dev/null @@ -1,166 +0,0 @@ -import { FC } from 'react'; - -import { Card, Flex } from '@patternfly/react-core'; -import { SearchIcon } from '@patternfly/react-icons'; -import { Link } from 'react-router-dom'; - -import { AvailableProtocols } from '@API/REST.enum'; -import { SMALL_PAGINATION_SIZE } from '@config/config'; -import { PrometheusLabelsV2 } from '@config/prometheus'; -import SKEmptyData from '@core/components/SkEmptyData'; -import { SkLinkCellProps } from '@core/components/SkLinkCell'; -import SkTable from '@core/components/SkTable'; -import { TopologyController } from '@pages/Topology/services'; -import { ProcessPairsResponse, ProcessResponse } from '@sk-types/REST.interfaces'; - -import { useProcessPairsListData } from '../hooks/useProcessPairsListData'; -import { - CustomProcessPairCells, - processesConnectedColumns, - processesHttpConnectedColumns -} from '../Processes.constants'; -import { ProcessesLabels, ProcessesRoutesPaths } from '../Processes.enum'; - -interface ProcessesPairsListProps { - process: ProcessResponse; -} - -const ProcessPairsList: FC = function ({ process: { identity: id, name } }) { - const { pairsTx, pairsRx, metricsTx, metricsRx } = useProcessPairsListData(id, name); - - const clients = TopologyController.addMetricsToTopologyDetails({ - processesPairs: pairsRx, - metrics: metricsRx, - prometheusKey: PrometheusLabelsV2.SourceProcess, - processPairsKey: 'sourceName' - }); - - const servers = TopologyController.addMetricsToTopologyDetails({ - processesPairs: pairsTx, - metrics: metricsTx, - prometheusKey: PrometheusLabelsV2.DestProcess, - processPairsKey: 'destinationName' - }); - - const processesPairsRxReverse = - (clients || []).map((processPairsData) => ({ - ...processPairsData, - sourceId: processPairsData.destinationId, - sourceName: processPairsData.destinationName, - destinationName: processPairsData.sourceName, - destinationId: processPairsData.sourceId - })) || []; - - const TCPServers = (servers || []).filter(({ protocol }) => protocol === AvailableProtocols.Tcp); - const TCPClients = processesPairsRxReverse.filter(({ protocol }) => protocol === AvailableProtocols.Tcp); - - const HTTPServers = (servers || []).filter( - ({ protocol }) => protocol === AvailableProtocols.Http || protocol === AvailableProtocols.Http2 - ); - const HTTPClients = processesPairsRxReverse.filter( - ({ protocol }) => protocol === AvailableProtocols.Http || protocol === AvailableProtocols.Http2 - ); - const remoteServers = (servers || []).filter(({ protocol }) => protocol === undefined); - const remoteClients = processesPairsRxReverse.filter(({ protocol }) => protocol === undefined); - - const CustomProcessPairCellsWithLinkDetail = { - ...CustomProcessPairCells, - viewDetailsLinkCell: ({ data }: SkLinkCellProps) => ( - - view pairs - - ) - }; - - return ( - <> - {!TCPClients.length && - !TCPServers.length && - !HTTPClients.length && - !HTTPServers.length && - !remoteClients.length && - !remoteServers.length && ( - - - - )} - - {!!TCPClients.length && ( - - )} - - {!!TCPServers.length && ( - - )} - - {!!HTTPClients.length && ( - - )} - - {!!HTTPServers.length && ( - - )} - - {!!remoteClients.length && ( - - )} - - {!!remoteServers.length && ( - - )} - - - ); -}; - -export default ProcessPairsList; diff --git a/src/pages/Processes/hooks/useProcessPairsData.ts b/src/pages/Processes/hooks/useProcessPairsData.ts index 95d4c56e..28de4a3b 100644 --- a/src/pages/Processes/hooks/useProcessPairsData.ts +++ b/src/pages/Processes/hooks/useProcessPairsData.ts @@ -17,12 +17,8 @@ export const useProcessPairData = ({ id }: useProcessPairDataProps) => { const sourceId = data.results.sourceId; const destId = data.results.destinationId; - const [{ data: processPairs }, { data: source }, { data: destination }] = useSuspenseQueries({ + const [{ data: source }, { data: destination }] = useSuspenseQueries({ queries: [ - { - queryKey: [QueriesProcesses.GetProcessPair, id], - queryFn: () => RESTApi.fetchProcessesPair(id) - }, { queryKey: [QueriesProcesses.GetProcess, sourceId], queryFn: () => RESTApi.fetchProcess(sourceId) @@ -34,5 +30,5 @@ export const useProcessPairData = ({ id }: useProcessPairDataProps) => { ] }); - return { processPairs: processPairs.results, source: source.results, destination: destination.results }; + return { processPair: data.results, source: source.results, destination: destination.results }; }; diff --git a/src/pages/Processes/routes.tsx b/src/pages/Processes/routes.tsx index 7ea5cce9..b2ec510a 100644 --- a/src/pages/Processes/routes.tsx +++ b/src/pages/Processes/routes.tsx @@ -4,7 +4,7 @@ import { ProcessesRoutesPaths } from './Processes.enum'; const Processes = lazy(() => import(/* webpackChunkName: "processes" */ './views/Processes')); const Process = lazy(() => import(/* webpackChunkName: "process" */ './views/Process')); -const ProcessFlowPairs = lazy(() => import(/* webpackChunkName: "process-flowpairs" */ './views/ProcessPairs')); +const ProcessPair = lazy(() => import(/* webpackChunkName: "process-pair" */ './views/ProcessPair')); export const processesRoutes = [ { @@ -17,6 +17,6 @@ export const processesRoutes = [ }, { path: `${ProcessesRoutesPaths.Processes}/:process/:id`, - element: + element: } ]; diff --git a/src/pages/Processes/views/Process.tsx b/src/pages/Processes/views/Process.tsx index 46dac80d..5dffeff5 100644 --- a/src/pages/Processes/views/Process.tsx +++ b/src/pages/Processes/views/Process.tsx @@ -11,7 +11,7 @@ import useUpdateQueryStringValueWithoutNavigation from 'hooks/useUpdateQueryStri import Details from '../components/Details'; import Overview from '../components/Overview'; -import ProcessPairsList from '../components/ProcessPairsList'; +import PairsList from '../components/PairsList'; import { useProcessData } from '../hooks/useProcessData'; import { ProcessesLabels } from '../Processes.enum'; @@ -57,7 +57,7 @@ const ProcessContent: FC = function ({ id, defaultTab }) { <> {tabSelected === ProcessesLabels.Overview && } {tabSelected === ProcessesLabels.Details &&
} - {tabSelected === ProcessesLabels.ProcessPairs && } + {tabSelected === ProcessesLabels.ProcessPairs && } } /> diff --git a/src/pages/Processes/views/ProcessPairs.tsx b/src/pages/Processes/views/ProcessPair.tsx similarity index 77% rename from src/pages/Processes/views/ProcessPairs.tsx rename to src/pages/Processes/views/ProcessPair.tsx index 4b5158db..476736f2 100644 --- a/src/pages/Processes/views/ProcessPairs.tsx +++ b/src/pages/Processes/views/ProcessPair.tsx @@ -9,16 +9,16 @@ import MainContainer from '@layout/MainContainer'; import { TopologyRoutesPaths, TopologyURLQueyParams, TopologyViews } from '@pages/Topology/Topology.enum'; import FlowPairsList from '../components/FlowPairsList'; -import ProcessPairDetails from '../components/ProcessPairsDetails'; +import ProcessPairDetails from '../components/ProcessPairDetails'; import { useProcessPairData } from '../hooks/useProcessPairsData'; import { ProcessesLabels } from '../Processes.enum'; -export interface ProcessPairsProps { +export interface ProcessPairProps { id: string; } -export const ProcessPairsContent: FC = function ({ id }) { - const { processPairs, source, destination } = useProcessPairData({ id }); +export const ProcessPairContent: FC = function ({ id }) { + const { processPair, source, destination } = useProcessPairData({ id }); return ( = function ({ id }) { @@ -44,11 +44,11 @@ export const ProcessPairsContent: FC = function ({ id }) { ); }; -const ProcessPairs = function () { +const ProcessPair = function () { const { id: paramId } = useParams(); const { id } = getIdAndNameFromUrlParams(paramId as string); - return ; + return ; }; -export default ProcessPairs; +export default ProcessPair; diff --git a/src/pages/Services/Services.constants.ts b/src/pages/Services/Services.constants.ts index 5cc261c8..21bc80f4 100644 --- a/src/pages/Services/Services.constants.ts +++ b/src/pages/Services/Services.constants.ts @@ -4,7 +4,7 @@ import SkEndTimeCell from '@core/components/SkEndTimeCell'; import SkLinkCell, { SkLinkCellProps } from '@core/components/SkLinkCell'; import { sankeyMetricOptions } from '@core/components/SKSanckeyChart/SkSankey.constants'; import { ProcessesLabels } from '@pages/Processes/Processes.enum'; -import { ServiceResponse, ProcessResponse, QueryFilters } from '@sk-types/REST.interfaces'; +import { ServiceResponse, QueryFilters } from '@sk-types/REST.interfaces'; import { SKTableColumn } from 'types/SkTable.interfaces'; import { ServicesRoutesPaths, ServicesLabels } from './Services.enum'; @@ -68,31 +68,6 @@ export const ServiceColumns: SKTableColumn[] = [ } ]; -// Server Table -export const tcpServerColumns: SKTableColumn[] = [ - { - name: ProcessesLabels.Name, - prop: 'name' as keyof ProcessResponse, - customCellName: 'linkCell' - }, - { - name: ProcessesLabels.Site, - prop: 'parentName' as keyof ProcessResponse, - customCellName: 'linkCellSite' - }, - { - name: ProcessesLabels.Component, - prop: 'groupName' as keyof ProcessResponse, - customCellName: 'linkComponentCell' - }, - { - name: ProcessesLabels.Created, - prop: 'startTime' as keyof ProcessResponse, - customCellName: 'TimestampCell', - width: 15 - } -]; - export const servicesSelectOptions: { name: string; id: string }[] = [ { name: 'Routing key', diff --git a/src/pages/Services/components/NavigationMenu.tsx b/src/pages/Services/components/NavigationMenu.tsx index d0d5a668..c46109cc 100644 --- a/src/pages/Services/components/NavigationMenu.tsx +++ b/src/pages/Services/components/NavigationMenu.tsx @@ -2,7 +2,7 @@ import { FC } from 'react'; import { Tab, TabTitleText, Tabs } from '@patternfly/react-core'; -import { AvailableProtocols } from '@API/REST.enum'; +import { Protocols } from '@API/REST.enum'; import { TopologyURLQueyParams } from '@pages/Topology/Topology.enum'; import useUpdateQueryStringValueWithoutNavigation from 'hooks/useUpdateQueryStringValueWithoutNavigation'; @@ -10,7 +10,7 @@ import { TAB_0_KEY, TAB_1_KEY, TAB_2_KEY, TAB_3_KEY, TAB_4_KEY } from '../Servic import { ServicesLabels } from '../Services.enum'; interface NavigationMenuProps { - protocol: AvailableProtocols | undefined; + protocol: Protocols | undefined; serverCount: number; requestsCount: number; tcpActiveConnectionCount: number; @@ -51,7 +51,7 @@ const NavigationMenu: FC = function ({ eventKey={TAB_4_KEY} title={{ServicesLabels.OldConnections}} /> - {protocol !== AvailableProtocols.Tcp && ( + {protocol !== Protocols.Tcp && ( = function ({ id }) { + const { processes } = useServersData(id); + + return ( + + ); +}; + +export default ProcessServerList; diff --git a/src/pages/Services/components/Servers.tsx b/src/pages/Services/components/Servers.tsx deleted file mode 100644 index e0471c88..00000000 --- a/src/pages/Services/components/Servers.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { FC } from 'react'; - -import { BIG_PAGINATION_SIZE } from '@config/config'; -import SkTable from '@core/components/SkTable'; -import { CustomProcessCells } from '@pages/Processes/Processes.constants'; - -import { useServersData } from '../hooks/useServersData'; -import { tcpServerColumns } from '../Services.constants'; - -interface ServersProps { - id: string; -} - -const Servers: FC = function ({ id }) { - const { servers } = useServersData(id); - - return ( - - ); -}; - -export default Servers; diff --git a/src/pages/Services/hooks/useServersData.ts b/src/pages/Services/hooks/useServersData.ts index f090c750..72b0f9ee 100644 --- a/src/pages/Services/hooks/useServersData.ts +++ b/src/pages/Services/hooks/useServersData.ts @@ -8,11 +8,11 @@ import { QueriesServices } from '../Services.enum'; const initServersQueryParams = { limit: BIG_PAGINATION_SIZE }; export const useServersData = (id: string) => { - const { data: servers } = useSuspenseQuery({ + const { data: processes } = useSuspenseQuery({ queryKey: [QueriesServices.GetProcessesByService, { ...initServersQueryParams, addresses: [id] }], queryFn: () => RESTApi.fetchProcesses({ ...initServersQueryParams, addresses: [id] }), refetchInterval: UPDATE_INTERVAL }); - return { servers: servers.results }; + return { processes: processes.results }; }; diff --git a/src/pages/Services/hooks/useServiceData.ts b/src/pages/Services/hooks/useServiceData.ts index 08c4e05c..ea5777d6 100644 --- a/src/pages/Services/hooks/useServiceData.ts +++ b/src/pages/Services/hooks/useServiceData.ts @@ -1,7 +1,7 @@ import { useQuery, useSuspenseQuery } from '@tanstack/react-query'; import { RESTApi } from '@API/REST.api'; -import { AvailableProtocols, TcpStatus } from '@API/REST.enum'; +import { Protocols, TcpStatus } from '@API/REST.enum'; import { UPDATE_INTERVAL } from '@config/config'; import { QueriesServices } from '../Services.enum'; @@ -39,7 +39,7 @@ const useServiceData = (serviceId: string) => { const { data: requestsData } = useQuery({ queryKey: [QueriesServices.GetFlowPairsByService, initServersQueryParams], queryFn: () => RESTApi.fetchFlowPairs({ ...initServersQueryParams, routingKey: service.results.name }), - enabled: service.results.protocol !== AvailableProtocols.Tcp, + enabled: service.results.protocol !== Protocols.Tcp, refetchInterval: UPDATE_INTERVAL }); @@ -52,7 +52,7 @@ const useServiceData = (serviceId: string) => { const { data: terminatedConnectionsData } = useQuery({ queryKey: [QueriesServices.GetFlowPairsByService, terminatedConnectionsQueryParams], queryFn: () => RESTApi.fetchFlowPairs({ ...terminatedConnectionsQueryParams, routingKey: service.results.name }), - enabled: service.results.protocol === AvailableProtocols.Tcp, + enabled: service.results.protocol === Protocols.Tcp, refetchInterval: UPDATE_INTERVAL }); diff --git a/src/pages/Services/views/Service.tsx b/src/pages/Services/views/Service.tsx index 22687563..1bdc1b17 100644 --- a/src/pages/Services/views/Service.tsx +++ b/src/pages/Services/views/Service.tsx @@ -2,7 +2,7 @@ import { FC, useState } from 'react'; import { useParams, useSearchParams } from 'react-router-dom'; -import { AvailableProtocols } from '@API/REST.enum'; +import { Protocols } from '@API/REST.enum'; import { getTestsIds } from '@config/testIds'; import { getIdAndNameFromUrlParams } from '@core/utils/getIdAndNameFromUrlParams'; import MainContainer from '@layout/MainContainer'; @@ -11,7 +11,7 @@ import { TopologyRoutesPaths, TopologyURLQueyParams, TopologyViews } from '@page import HttpRequests from '../components/HttpRequests'; import NavigationMenu from '../components/NavigationMenu'; import Overview from '../components/Overview'; -import Servers from '../components/Servers'; +import ProcessServerList from '../components/ProcessServerList'; import TcpConnections from '../components/TcpConnections'; import TcpTerminatedConnections from '../components/TcpTerminatedConnections'; import useServiceData from '../hooks/useServiceData'; @@ -49,10 +49,10 @@ const ServiceComponent: FC = function ({ id, defaultTab }) { mainContentChildren={ <> {menuSelected === TAB_0_KEY && } - {menuSelected === TAB_1_KEY && } + {menuSelected === TAB_1_KEY && } {menuSelected === TAB_3_KEY && } {menuSelected === TAB_4_KEY && } - {menuSelected === TAB_2_KEY && protocol !== AvailableProtocols.Tcp && } + {menuSelected === TAB_2_KEY && protocol !== Protocols.Tcp && } } /> diff --git a/src/pages/Sites/Sites.enum.ts b/src/pages/Sites/Sites.enum.ts index ed2fcbc8..bfe26e8b 100644 --- a/src/pages/Sites/Sites.enum.ts +++ b/src/pages/Sites/Sites.enum.ts @@ -6,8 +6,6 @@ export enum QueriesSites { GetSites = 'get-sites-query', GetSite = 'get-site-query', GetSitesPairs = 'get-sites-pairs-query', - GetHostsBySiteId = 'get-hosts-by-site-id-query', - GetLinksBySiteId = 'get-links-by-site-id-query', GetRouters = 'get-routers-query', GetLinks = 'get-links-query', GetProcessesBySiteId = 'get-processes-by-site-id-query' diff --git a/src/pages/Sites/components/Details.tsx b/src/pages/Sites/components/Details.tsx index 50f27f9a..b35687cd 100644 --- a/src/pages/Sites/components/Details.tsx +++ b/src/pages/Sites/components/Details.tsx @@ -22,7 +22,6 @@ import ResourceIcon from '@core/components/ResourceIcon'; import { SiteResponse } from '@sk-types/REST.interfaces'; import { useSiteDetailsData } from '../hooks/useSiteDetailsData'; -import SitesController from '../services'; import { SitesRoutesPaths, SiteLabels } from '../Sites.enum'; interface DetailsProps { @@ -30,13 +29,7 @@ interface DetailsProps { } const Details: FC = function ({ site: { identity: id, nameSpace, siteVersion, platform } }) { - const { sites, links } = useSiteDetailsData(id); - - const { linkSiteIds } = SitesController.bindLinksWithSiteIds( - sites.filter(({ identity }) => identity === id), - links - )[0]; - const linkedSites = sites.filter(({ identity }) => linkSiteIds.map(({ targetId }) => targetId).includes(identity)); + const { links } = useSiteDetailsData(id); return ( @@ -57,13 +50,13 @@ const Details: FC = function ({ site: { identity: id, nameSpace, s {SiteLabels.Links} - {linkedSites.length - ? linkedSites.map(({ identity, name: linkedSiteName }) => ( + {links.length + ? links.map(({ identity, destinationSiteId, destinationSiteName }) => ( - - {linkedSiteName} + + {destinationSiteName} diff --git a/src/pages/Sites/components/Overview..tsx b/src/pages/Sites/components/Overview..tsx index 3cad27ad..3844477c 100644 --- a/src/pages/Sites/components/Overview..tsx +++ b/src/pages/Sites/components/Overview..tsx @@ -4,7 +4,7 @@ import { extractUniqueValues } from '@core/utils/extractUniqueValues'; import { mapDataToMetricFilterOptions } from '@core/utils/getResourcesFromPairs'; import { removeDuplicatesFromArrayOfObjects } from '@core/utils/removeDuplicatesFromArrayOfObjects'; import Metrics from '@pages/shared/Metrics'; -import { useMetricSessionHandlers } from '@pages/shared/Metrics/hooks/useSessionHandler'; +import { useMetricSessionHandlers } from '@pages/shared/Metrics/hooks/useMetricsSessionHandler'; import { SitePairsResponse, SiteResponse } from '@sk-types/REST.interfaces'; import { useSiteOverviewData } from '../hooks/useOverviewData'; diff --git a/src/pages/Sites/components/ProcessList.tsx b/src/pages/Sites/components/ProcessList.tsx index 407d7f7c..8e67e8ba 100644 --- a/src/pages/Sites/components/ProcessList.tsx +++ b/src/pages/Sites/components/ProcessList.tsx @@ -3,7 +3,6 @@ import { FC } from 'react'; import SkTable from '@core/components/SkTable'; import { setColumnVisibility } from '@core/components/SkTable/SkTable.utils'; import { CustomProcessCells, processesTableColumns } from '@pages/Processes/Processes.constants'; -import { ProcessesLabels } from '@pages/Processes/Processes.enum'; import { SiteResponse } from '@sk-types/REST.interfaces'; import { useSiteProcessListData } from '../hooks/useSiteProcessListData'; @@ -17,7 +16,6 @@ const ProcessList: FC = function ({ site: { identity: id } }) return ( { - const [{ data: sites }, { data: links }] = useSuspenseQueries({ - queries: [ - { queryKey: [QueriesSites.GetSites], queryFn: () => RESTApi.fetchSites(), refetchInterval: UPDATE_INTERVAL }, - { - queryKey: [QueriesSites.GetLinksBySiteId, id, linkQueryParams], - queryFn: () => RESTApi.fetchLinksBySite(id, linkQueryParams), - refetchInterval: UPDATE_INTERVAL - } - ] + const queryParams = (idKey: keyof RouterLinkResponse) => ({ [idKey]: id }); + + const { data: links } = useSuspenseQuery({ + queryKey: [QueriesSites.GetLinks, queryParams('sourceSiteId')], + queryFn: () => RESTApi.fetchLinks(queryParams('sourceSiteId')), + refetchInterval: UPDATE_INTERVAL }); - return { sites: sites.results, links: links.results }; + return { links: links.results }; }; diff --git a/src/pages/Sites/services/index.ts b/src/pages/Sites/services/index.ts deleted file mode 100644 index 1a9467e5..00000000 --- a/src/pages/Sites/services/index.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { LinkResponse, SiteResponse } from '@sk-types/REST.interfaces'; - -const SitesController = { - // The output is an object that assigns to each source site id the ids of the connected - bindLinksWithSiteIds: (sites: SiteResponse[], links: LinkResponse[]) => { - const linksExtendedMap = links.reduce( - function (acc, { sourceSiteId, destinationSiteId, linkCost }) { - const existingLink = (acc[sourceSiteId] || []).find((link) => link.targetId === destinationSiteId); - if ( - !existingLink || - (existingLink.linkCost !== undefined && linkCost !== undefined && linkCost > existingLink.linkCost) - ) { - acc[sourceSiteId] = acc[sourceSiteId] || []; - acc[sourceSiteId].push({ targetId: destinationSiteId, linkCost }); - } else if (existingLink && existingLink.linkCost === undefined) { - existingLink.linkCost = linkCost; - } - - return acc; - }, - {} as Record - ); - - return sites.map((site) => ({ - ...site, - // Filters the target ids to remove duplicates - linkSiteIds: (linksExtendedMap[site.identity] || []) - ?.filter((obj, index, self) => index === self.findIndex((o) => o.targetId === obj.targetId)) - .filter(Boolean) - })); - } -}; - -export default SitesController; diff --git a/src/pages/Topology/Topology.constants.ts b/src/pages/Topology/Topology.constants.ts index 2c0db4de..77a79092 100644 --- a/src/pages/Topology/Topology.constants.ts +++ b/src/pages/Topology/Topology.constants.ts @@ -18,26 +18,6 @@ export const SHOW_LINK_METRIC_DISTRIBUTION = 'show-metric-distribution'; export const SHOW_LINK_METRIC_VALUE = 'show-metric-value'; export const displayOptionsForProcesses: TopologyDisplayOptionsMenu[] = [ - { - title: TopologyLabels.TitleGroupDisplayOptionsMenuMetrics, - items: [ - { - key: SHOW_LINK_BYTES, - value: SHOW_LINK_BYTES, - label: TopologyLabels.CheckboxShowTotalBytes - }, - { - key: SHOW_LINK_BYTERATE, - value: SHOW_LINK_BYTERATE, - label: TopologyLabels.CheckboxShowCurrentByteRate - }, - { - key: SHOW_LINK_LATENCY, - value: SHOW_LINK_LATENCY, - label: TopologyLabels.CheckboxShowLatency - } - ] - }, { title: TopologyLabels.TitleGroupDisplayOptionsMenuMetricVisualization, items: [ @@ -58,6 +38,27 @@ export const displayOptionsForProcesses: TopologyDisplayOptionsMenu[] = [ } ] }, + { + title: TopologyLabels.TitleGroupDisplayOptionsMenuMetrics, + items: [ + { + key: SHOW_LINK_BYTES, + value: SHOW_LINK_BYTES, + label: TopologyLabels.CheckboxShowTotalBytes + }, + { + key: SHOW_LINK_BYTERATE, + value: SHOW_LINK_BYTERATE, + label: TopologyLabels.CheckboxShowCurrentByteRate + }, + { + key: SHOW_LINK_LATENCY, + value: SHOW_LINK_LATENCY, + label: TopologyLabels.CheckboxShowLatency + } + ] + }, + { title: TopologyLabels.TitleGroupDisplayOptionsMenuOther, items: [ @@ -95,26 +96,6 @@ export const displayOptionsForSites: TopologyDisplayOptionsMenu[] = [ } ] }, - { - title: TopologyLabels.TitleGroupDisplayOptionsMenuMetrics, - items: [ - { - key: SHOW_LINK_BYTES, - value: SHOW_LINK_BYTES, - label: TopologyLabels.CheckboxShowTotalBytes - }, - { - key: SHOW_LINK_BYTERATE, - value: SHOW_LINK_BYTERATE, - label: TopologyLabels.CheckboxShowCurrentByteRate - }, - { - key: SHOW_LINK_LATENCY, - value: SHOW_LINK_LATENCY, - label: TopologyLabels.CheckboxShowLatency - } - ] - }, { title: TopologyLabels.TitleGroupDisplayOptionsMenuMetricVisualization, items: [ @@ -134,6 +115,26 @@ export const displayOptionsForSites: TopologyDisplayOptionsMenu[] = [ label: TopologyLabels.CheckboxShowLabelReverse } ] + }, + { + title: TopologyLabels.TitleGroupDisplayOptionsMenuMetrics, + items: [ + { + key: SHOW_LINK_BYTES, + value: SHOW_LINK_BYTES, + label: TopologyLabels.CheckboxShowTotalBytes + }, + { + key: SHOW_LINK_BYTERATE, + value: SHOW_LINK_BYTERATE, + label: TopologyLabels.CheckboxShowCurrentByteRate + }, + { + key: SHOW_LINK_LATENCY, + value: SHOW_LINK_LATENCY, + label: TopologyLabels.CheckboxShowLatency + } + ] } ]; diff --git a/src/pages/Topology/components/DisplayOptions.tsx b/src/pages/Topology/components/DisplayOptions.tsx index d8e09905..79736768 100644 --- a/src/pages/Topology/components/DisplayOptions.tsx +++ b/src/pages/Topology/components/DisplayOptions.tsx @@ -1,71 +1,17 @@ -import { FC, Ref, useCallback, useState } from 'react'; +import { FC, MouseEvent as ReactMouseEvent, Ref, useCallback, useState } from 'react'; import { MenuToggle, MenuToggleElement, Select, SelectGroup, SelectOption } from '@patternfly/react-core'; import { TopologyDisplayOptionsMenu } from '@sk-types/Topology.interfaces'; -import { - SHOW_DATA_LINKS, - SHOW_LINK_BYTERATE, - SHOW_LINK_BYTES, - SHOW_LINK_LATENCY, - SHOW_ROUTER_LINKS -} from '../Topology.constants'; +import { DisplayUseOptionsStateProps, useDisplayOptionsState } from '../hooks/useDisplayOptionsState'; import { TopologyLabels } from '../Topology.enum'; -interface DisplayUseOptionsProps { - defaultSelected?: string[]; - onSelected: (items: string[], item: string) => void; -} - -interface DisplayOptionsProps extends DisplayUseOptionsProps { +interface DisplayOptionsProps extends DisplayUseOptionsStateProps { options: TopologyDisplayOptionsMenu[]; optionsDisabled?: Record; } -export const useDisplayOptions = ({ defaultSelected = [], onSelected }: DisplayUseOptionsProps) => { - const [displayOptionsSelected, setDisplayOptions] = useState(defaultSelected); - - const selectDisplayOptions = useCallback( - (selectedOption: string) => { - const isOptionSelected = displayOptionsSelected.includes(selectedOption); - - let updatedDisplayOptions = isOptionSelected - ? displayOptionsSelected.filter((option) => option !== selectedOption) - : [...displayOptionsSelected, selectedOption]; - - if ( - selectedOption === SHOW_LINK_BYTES || - selectedOption === SHOW_LINK_BYTERATE || - selectedOption === SHOW_LINK_LATENCY - ) { - updatedDisplayOptions = isOptionSelected - ? displayOptionsSelected.filter((option) => option !== selectedOption) - : [ - ...displayOptionsSelected.filter( - (option) => option !== SHOW_LINK_BYTES && option !== SHOW_LINK_BYTERATE && option !== SHOW_LINK_LATENCY - ), - selectedOption - ]; - } - - if (selectedOption === SHOW_DATA_LINKS || selectedOption === SHOW_ROUTER_LINKS) { - const otherOption = selectedOption === SHOW_DATA_LINKS ? SHOW_ROUTER_LINKS : SHOW_DATA_LINKS; - - updatedDisplayOptions = isOptionSelected - ? [...updatedDisplayOptions, otherOption] - : updatedDisplayOptions.filter((option) => option !== otherOption); - } - - setDisplayOptions(updatedDisplayOptions); - onSelected(updatedDisplayOptions, selectedOption); - }, - [displayOptionsSelected, onSelected] - ); - - return { displayOptionsSelected, selectDisplayOptions }; -}; - const DisplayOptions: FC = function ({ defaultSelected, options, @@ -73,41 +19,45 @@ const DisplayOptions: FC = function ({ optionsDisabled = {} }) { const [isDisplayMenuOpen, setIsDisplayMenuOpen] = useState(false); - const { displayOptionsSelected, selectDisplayOptions } = useDisplayOptions({ + const { displayOptionsSelected, selectDisplayOptions } = useDisplayOptionsState({ defaultSelected, onSelected }); - function toggleDisplayMenu() { - setIsDisplayMenuOpen(!isDisplayMenuOpen); - } + const toggleDisplayMenu = useCallback(() => { + setIsDisplayMenuOpen((prev) => !prev); + }, []); - const toggle = (toggleRef: Ref) => ( - - {TopologyLabels.DisplayPlaceholderText} - + const handleSelect = useCallback( + (_?: ReactMouseEvent, selection?: string | number) => + selectDisplayOptions(selection!.toString()), + [selectDisplayOptions] ); + const handleOpenChange = useCallback((open: boolean) => { + if (!open) { + setIsDisplayMenuOpen(false); + } + }, []); + return ( handleSelectProtocol(selection as AvailableProtocols)} + onSelect={(_, selection) => handleSelectProtocol(selection as Protocols)} toggle={toggle} > {[ diff --git a/src/pages/shared/Metrics/components/TcpConnection.tsx b/src/pages/shared/Metrics/components/TcpConnection.tsx index 3f106136..6ebfa9e4 100644 --- a/src/pages/shared/Metrics/components/TcpConnection.tsx +++ b/src/pages/shared/Metrics/components/TcpConnection.tsx @@ -15,7 +15,7 @@ import { import { SearchIcon } from '@patternfly/react-icons'; import { useQuery } from '@tanstack/react-query'; -import { AvailableProtocols } from '@API/REST.enum'; +import { Protocols } from '@API/REST.enum'; import SkChartArea from '@core/components/SkChartArea'; import SKEmptyData from '@core/components/SkEmptyData'; import SkIsLoading from '@core/components/SkIsLoading'; @@ -51,7 +51,7 @@ const TcpConnection: FC = function ({ isLoading } = useQuery({ queryKey: [QueriesMetrics.GetConnection, selectedFilters], - queryFn: () => MetricsController.getConnections({ ...selectedFilters, protocol: AvailableProtocols.Tcp }), + queryFn: () => MetricsController.getConnections({ ...selectedFilters, protocol: Protocols.Tcp }), refetchInterval, enabled: isExpanded }); diff --git a/src/pages/shared/Metrics/hooks/useSessionHandler.ts b/src/pages/shared/Metrics/hooks/useMetricsSessionHandler.ts similarity index 99% rename from src/pages/shared/Metrics/hooks/useSessionHandler.ts rename to src/pages/shared/Metrics/hooks/useMetricsSessionHandler.ts index 9cf398ab..da463bd6 100644 --- a/src/pages/shared/Metrics/hooks/useSessionHandler.ts +++ b/src/pages/shared/Metrics/hooks/useMetricsSessionHandler.ts @@ -20,7 +20,6 @@ export const useMetricSessionHandlers = (id: string) => { ); const selectedFilters = getDataFromSession(`${PREFIX_METRIC_FILTERS_KEY}-${id}`); - const visibleMetrics = getDataFromSession(`${PREFIX_VISIBLE_METRICS_KEY}-${id}`) || undefined; return { diff --git a/src/pages/shared/Metrics/index.tsx b/src/pages/shared/Metrics/index.tsx index 7a512d90..02be8bcd 100644 --- a/src/pages/shared/Metrics/index.tsx +++ b/src/pages/shared/Metrics/index.tsx @@ -2,7 +2,7 @@ import { FC, useCallback, useRef, useState, startTransition } from 'react'; import { Stack, StackItem } from '@patternfly/react-core'; -import { AvailableProtocols, Direction } from '@API/REST.enum'; +import { Protocols, Direction } from '@API/REST.enum'; import { ConfigMetricFilters, ExpandedMetricSections, QueryMetricsParams } from '@sk-types/Metrics.interfaces'; import MetricFilters from './components/Filters'; @@ -75,7 +75,7 @@ export interface MetricsProps { destSites?: { destinationName: string }[]; sourceProcesses?: { destinationName: string; siteName?: string }[]; destProcesses?: { destinationName: string; siteName?: string }[]; - availableProtocols?: AvailableProtocols[]; + availableProtocols?: Protocols[]; configFilters?: ConfigMetricFilters; onGetMetricFiltersConfig?: Function; onGetExpandedSectionsConfig?: Function; @@ -104,9 +104,9 @@ const Metrics: FC = function (props) { // case: hide if We select TCP from the protocol filter or the protocol list has only 1 item and this item is TCP const showHttp = !!availableProtocols?.length && - queryParams.protocol !== AvailableProtocols.Tcp && - !(availableProtocols?.length === 1 && availableProtocols[0] === AvailableProtocols.Tcp); - const showTcp = !queryParams.protocol || queryParams.protocol === AvailableProtocols.Tcp; + queryParams.protocol !== Protocols.Tcp && + !(availableProtocols?.length === 1 && availableProtocols[0] === Protocols.Tcp); + const showTcp = !queryParams.protocol || queryParams.protocol === Protocols.Tcp; return ( diff --git a/src/types/Graph.interfaces.ts b/src/types/Graph.interfaces.ts index 58bd556c..77012f4b 100644 --- a/src/types/Graph.interfaces.ts +++ b/src/types/Graph.interfaces.ts @@ -52,7 +52,7 @@ export interface GraphEdge { id: string; type: GraphElementNames; source: string; - target: string; + target: string | 'unknown'; sourceName?: string; targetName?: string; label?: string; diff --git a/src/types/Metrics.interfaces.ts b/src/types/Metrics.interfaces.ts index eae6bba7..d296f214 100644 --- a/src/types/Metrics.interfaces.ts +++ b/src/types/Metrics.interfaces.ts @@ -1,4 +1,4 @@ -import { AvailableProtocols, Direction } from '@API/REST.enum'; +import { Protocols, Direction } from '@API/REST.enum'; import { PrometheusMetric } from './Prometheus.interfaces'; import { skAxisXY } from './SkChartArea.interfaces'; @@ -28,7 +28,7 @@ export interface QueryMetricsParams { sourceComponent?: string; destComponent?: string; service?: string; - protocol?: AvailableProtocols; + protocol?: Protocols; start?: number; end?: number; duration?: number; diff --git a/src/types/Processes.interfaces.ts b/src/types/Processes.interfaces.ts index 027e9947..06467050 100644 --- a/src/types/Processes.interfaces.ts +++ b/src/types/Processes.interfaces.ts @@ -1,11 +1,11 @@ -import { AvailableProtocols } from '@API/REST.enum'; +import { Protocols } from '@API/REST.enum'; import { QueryFilters } from './REST.interfaces'; export interface QueryFiltersProtocolMap { - [AvailableProtocols.Http]: QueryFilters; - [AvailableProtocols.Http2]: QueryFilters; - [AvailableProtocols.Tcp]: { + [Protocols.Http]: QueryFilters; + [Protocols.Http2]: QueryFilters; + [Protocols.Tcp]: { active: QueryFilters; old: QueryFilters; }; diff --git a/src/types/Prometheus.interfaces.ts b/src/types/Prometheus.interfaces.ts index 005506cb..e581efaa 100644 --- a/src/types/Prometheus.interfaces.ts +++ b/src/types/Prometheus.interfaces.ts @@ -1,6 +1,6 @@ import { FlowDirections } from './REST.interfaces'; import { skAxisXY } from './SkChartArea.interfaces'; -import { AvailableProtocols, Quantiles } from '../API/REST.enum'; +import { Protocols, Quantiles } from '../API/REST.enum'; // Common base type for result types type PrometheusResultType = 'matrix' | 'vector' | 'scalar' | 'string'; @@ -40,7 +40,7 @@ export interface PrometheusLabels { service?: string; code?: string; direction?: FlowDirections; - protocol?: AvailableProtocols; + protocol?: Protocols; } export interface PrometheusQueryParams extends PrometheusLabels { diff --git a/src/types/REST.interfaces.ts b/src/types/REST.interfaces.ts index 32c0f110..3ea3a212 100644 --- a/src/types/REST.interfaces.ts +++ b/src/types/REST.interfaces.ts @@ -1,6 +1,6 @@ import { AxiosError, AxiosRequestConfig } from 'axios'; -import { AvailableProtocols, Binding, Direction, Role, SortDirection } from '@API/REST.enum'; +import { Protocols, Binding, Direction, Role, SortDirection } from '@API/REST.enum'; export type FetchWithOptions = AxiosRequestConfig; export type FlowDirections = Direction.Outgoing | Direction.Incoming; @@ -29,6 +29,15 @@ export interface HTTPError extends AxiosError { httpStatus?: string; } +/** + * Represents a response wrapper that contains the results, count, and time range count. + * The `results` property holds the actual data based on the Response interface. + * The `count` property represents the total count of the results. + * The `timeRangeCount` property represents the count of results within the specified time range. + * + * @typeParam T - The type of the results based on the Response interface. + * @returns A ResponseWrapper object containing the results, count, and time range count. + */ export type ResponseWrapper = { results: T; // Type based on the Response interface count: number; @@ -81,7 +90,7 @@ export interface SitePairsResponse extends BaseResponse { sourceName: string; destinationId: string; destinationName: string; - protocol: AvailableProtocols; + protocol: Protocols; } export type ComponentPairsResponse = SitePairsResponse; @@ -95,7 +104,7 @@ export interface ProcessPairsResponse extends ComponentPairsResponse { export interface ServiceResponse extends BaseResponse { name: string; - protocol: AvailableProtocols; + protocol: Protocols; connectorCount: number; listenerCount: number; isBound: boolean; @@ -119,7 +128,7 @@ interface BiFlow extends BaseResponse { latencyReverse: number; traceRouters: string[]; traceSites: string[]; - protocol: AvailableProtocols; + protocol: Protocols; connectorError: null; connectorId: string; listenerId: string; @@ -160,12 +169,17 @@ export interface RouterResponse extends BaseResponse { buildVersion: string; } -export interface LinkResponse extends BaseResponse { - name?: string; - parent: string; - mode: string; - direction: FlowDirections; - linkCost: number; +export interface RouterLinkResponse extends BaseResponse { + cost: number | null; + destinationSiteId: string | null; + destinationSiteName: string | null; + name: string; + octets: number; + octetsReverse: number; + peer: string | null; + routerId: string; sourceSiteId: string; - destinationSiteId: string; + sourceSiteName: string; + role: 'inter-router' | 'edge-router'; + status: 'up' | 'down'; } diff --git a/src/types/Topology.interfaces.ts b/src/types/Topology.interfaces.ts index b38cc453..7f869d8b 100644 --- a/src/types/Topology.interfaces.ts +++ b/src/types/Topology.interfaces.ts @@ -52,8 +52,14 @@ export interface TopologyShowOptionsSelected { } export interface ProcessPairsWithMetrics { - processesPairs?: ProcessPairsResponse[]; - metrics?: TopologyMetrics; + processesPairs: ProcessPairsResponse[]; prometheusKey: PrometheusLabelsV2; processPairsKey: 'sourceName' | 'destinationName'; + metrics?: TopologyMetrics; +} + +export interface ProcessPairsWithStats extends ProcessPairsResponse { + bytes: number; + byteRate: number; + latency: number; } diff --git a/yarn.lock b/yarn.lock index 569eae84..4c870604 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1952,10 +1952,10 @@ "@types/prop-types" "*" csstype "^3.0.2" -"@types/react@^18.3.6": - version "18.3.6" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.6.tgz#1cb5895c5ea0d99d8bc7d659e42f72713cbd3942" - integrity sha512-CnGaRYNu2iZlkGXGrOYtdg5mLK8neySj0woZ4e2wF/eli2E6Sazmq5X+Nrj6OBrrFVQfJWTUFeqAzoRhWQXYvg== +"@types/react@^18.3.7": + version "18.3.7" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.7.tgz#6decbfbb01f8d82d56ff5403394121940faa6569" + integrity sha512-KUnDCJF5+AiZd8owLIeVHqmW9yM4sqmDVf2JRJiBMFkGvkoZ4/WyV2lL4zVsoinmRS/W3FeEdZLEWFRofnT2FQ== dependencies: "@types/prop-types" "*" csstype "^3.0.2" @@ -2042,30 +2042,30 @@ dependencies: "@types/node" "*" -"@typescript-eslint/eslint-plugin@^8.5.0": - version "8.5.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.5.0.tgz#7c1863693a98371703686e1c0fac64ffc576cdb1" - integrity sha512-lHS5hvz33iUFQKuPFGheAB84LwcJ60G8vKnEhnfcK1l8kGVLro2SFYW6K0/tj8FUhRJ0VHyg1oAfg50QGbPPHw== +"@typescript-eslint/eslint-plugin@^8.6.0": + version "8.6.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.6.0.tgz#20049754ff9f6d3a09bf240297f029ce04290999" + integrity sha512-UOaz/wFowmoh2G6Mr9gw60B1mm0MzUtm6Ic8G2yM1Le6gyj5Loi/N+O5mocugRGY+8OeeKmkMmbxNqUCq3B4Sg== dependencies: "@eslint-community/regexpp" "^4.10.0" - "@typescript-eslint/scope-manager" "8.5.0" - "@typescript-eslint/type-utils" "8.5.0" - "@typescript-eslint/utils" "8.5.0" - "@typescript-eslint/visitor-keys" "8.5.0" + "@typescript-eslint/scope-manager" "8.6.0" + "@typescript-eslint/type-utils" "8.6.0" + "@typescript-eslint/utils" "8.6.0" + "@typescript-eslint/visitor-keys" "8.6.0" graphemer "^1.4.0" ignore "^5.3.1" natural-compare "^1.4.0" ts-api-utils "^1.3.0" -"@typescript-eslint/parser@^8.5.0": - version "8.5.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.5.0.tgz#d590e1ef9f31f26d423999ad3f687723247e6bcc" - integrity sha512-gF77eNv0Xz2UJg/NbpWJ0kqAm35UMsvZf1GHj8D9MRFTj/V3tAciIWXfmPLsAAF/vUlpWPvUDyH1jjsr0cMVWw== +"@typescript-eslint/parser@^8.6.0": + version "8.6.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.6.0.tgz#02e092b9dc8b4e319172af620d0d39b337d948f6" + integrity sha512-eQcbCuA2Vmw45iGfcyG4y6rS7BhWfz9MQuk409WD47qMM+bKCGQWXxvoOs1DUp+T7UBMTtRTVT+kXr7Sh4O9Ow== dependencies: - "@typescript-eslint/scope-manager" "8.5.0" - "@typescript-eslint/types" "8.5.0" - "@typescript-eslint/typescript-estree" "8.5.0" - "@typescript-eslint/visitor-keys" "8.5.0" + "@typescript-eslint/scope-manager" "8.6.0" + "@typescript-eslint/types" "8.6.0" + "@typescript-eslint/typescript-estree" "8.6.0" + "@typescript-eslint/visitor-keys" "8.6.0" debug "^4.3.4" "@typescript-eslint/scope-manager@8.0.0": @@ -2076,21 +2076,21 @@ "@typescript-eslint/types" "8.0.0" "@typescript-eslint/visitor-keys" "8.0.0" -"@typescript-eslint/scope-manager@8.5.0": - version "8.5.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.5.0.tgz#385341de65b976f02b295b8aca54bb4ffd6b5f07" - integrity sha512-06JOQ9Qgj33yvBEx6tpC8ecP9o860rsR22hWMEd12WcTRrfaFgHr2RB/CA/B+7BMhHkXT4chg2MyboGdFGawYg== +"@typescript-eslint/scope-manager@8.6.0": + version "8.6.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.6.0.tgz#28cc2fc26a84b75addf45091a2c6283e29e2c982" + integrity sha512-ZuoutoS5y9UOxKvpc/GkvF4cuEmpokda4wRg64JEia27wX+PysIE9q+lzDtlHHgblwUWwo5/Qn+/WyTUvDwBHw== dependencies: - "@typescript-eslint/types" "8.5.0" - "@typescript-eslint/visitor-keys" "8.5.0" + "@typescript-eslint/types" "8.6.0" + "@typescript-eslint/visitor-keys" "8.6.0" -"@typescript-eslint/type-utils@8.5.0": - version "8.5.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.5.0.tgz#6215b23aa39dbbd8dde0a4ef9ee0f745410c29b1" - integrity sha512-N1K8Ix+lUM+cIDhL2uekVn/ZD7TZW+9/rwz8DclQpcQ9rk4sIL5CAlBC0CugWKREmDjBzI/kQqU4wkg46jWLYA== +"@typescript-eslint/type-utils@8.6.0": + version "8.6.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.6.0.tgz#d4347e637478bef88cee1db691fcfa20ade9b8a0" + integrity sha512-dtePl4gsuenXVwC7dVNlb4mGDcKjDT/Ropsk4za/ouMBPplCLyznIaR+W65mvCvsyS97dymoBRrioEXI7k0XIg== dependencies: - "@typescript-eslint/typescript-estree" "8.5.0" - "@typescript-eslint/utils" "8.5.0" + "@typescript-eslint/typescript-estree" "8.6.0" + "@typescript-eslint/utils" "8.6.0" debug "^4.3.4" ts-api-utils "^1.3.0" @@ -2099,10 +2099,10 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.0.0.tgz#7195ea9369fe5ee46b958d7ffca6bd26511cce18" integrity sha512-wgdSGs9BTMWQ7ooeHtu5quddKKs5Z5dS+fHLbrQI+ID0XWJLODGMHRfhwImiHoeO2S5Wir2yXuadJN6/l4JRxw== -"@typescript-eslint/types@8.5.0": - version "8.5.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.5.0.tgz#4465d99331d1276f8fb2030e4f9c73fe01a05bf9" - integrity sha512-qjkormnQS5wF9pjSi6q60bKUHH44j2APxfh9TQRXK8wbYVeDYYdYJGIROL87LGZZ2gz3Rbmjc736qyL8deVtdw== +"@typescript-eslint/types@8.6.0": + version "8.6.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.6.0.tgz#cdc3a16f83f2f0663d6723e9fd032331cdd9f51c" + integrity sha512-rojqFZGd4MQxw33SrOy09qIDS8WEldM8JWtKQLAjf/X5mGSeEFh5ixQlxssMNyPslVIk9yzWqXCsV2eFhYrYUw== "@typescript-eslint/typescript-estree@8.0.0": version "8.0.0" @@ -2118,13 +2118,13 @@ semver "^7.6.0" ts-api-utils "^1.3.0" -"@typescript-eslint/typescript-estree@8.5.0": - version "8.5.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.5.0.tgz#6e5758cf2f63aa86e9ddfa4e284e2e0b81b87557" - integrity sha512-vEG2Sf9P8BPQ+d0pxdfndw3xIXaoSjliG0/Ejk7UggByZPKXmJmw3GW5jV2gHNQNawBUyfahoSiCFVov0Ruf7Q== +"@typescript-eslint/typescript-estree@8.6.0": + version "8.6.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.6.0.tgz#f945506de42871f04868371cb5bf21e8f7266e01" + integrity sha512-MOVAzsKJIPIlLK239l5s06YXjNqpKTVhBVDnqUumQJja5+Y94V3+4VUFRA0G60y2jNnTVwRCkhyGQpavfsbq/g== dependencies: - "@typescript-eslint/types" "8.5.0" - "@typescript-eslint/visitor-keys" "8.5.0" + "@typescript-eslint/types" "8.6.0" + "@typescript-eslint/visitor-keys" "8.6.0" debug "^4.3.4" fast-glob "^3.3.2" is-glob "^4.0.3" @@ -2132,15 +2132,15 @@ semver "^7.6.0" ts-api-utils "^1.3.0" -"@typescript-eslint/utils@8.5.0": - version "8.5.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.5.0.tgz#4d4ffed96d0654546a37faa5b84bdce16d951634" - integrity sha512-6yyGYVL0e+VzGYp60wvkBHiqDWOpT63pdMV2CVG4LVDd5uR6q1qQN/7LafBZtAtNIn/mqXjsSeS5ggv/P0iECw== +"@typescript-eslint/utils@8.6.0": + version "8.6.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.6.0.tgz#175fe893f32804bed1e72b3364ea6bbe1044181c" + integrity sha512-eNp9cWnYf36NaOVjkEUznf6fEgVy1TWpE0o52e4wtojjBx7D1UV2WAWGzR+8Y5lVFtpMLPwNbC67T83DWSph4A== dependencies: "@eslint-community/eslint-utils" "^4.4.0" - "@typescript-eslint/scope-manager" "8.5.0" - "@typescript-eslint/types" "8.5.0" - "@typescript-eslint/typescript-estree" "8.5.0" + "@typescript-eslint/scope-manager" "8.6.0" + "@typescript-eslint/types" "8.6.0" + "@typescript-eslint/typescript-estree" "8.6.0" "@typescript-eslint/utils@^6.0.0 || ^7.0.0 || ^8.0.0": version "8.0.0" @@ -2160,12 +2160,12 @@ "@typescript-eslint/types" "8.0.0" eslint-visitor-keys "^3.4.3" -"@typescript-eslint/visitor-keys@8.5.0": - version "8.5.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.5.0.tgz#13028df3b866d2e3e2e2cc4193cf2c1e0e04c4bf" - integrity sha512-yTPqMnbAZJNy2Xq2XU8AdtOW9tJIr+UQb64aXB9f3B1498Zx9JorVgFJcZpEc9UBuCCrdzKID2RGAMkYcDtZOw== +"@typescript-eslint/visitor-keys@8.6.0": + version "8.6.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.6.0.tgz#5432af4a1753f376f35ab5b891fc9db237aaf76f" + integrity sha512-wapVFfZg9H0qOYh4grNVQiMklJGluQrOUiOhYRrQWhx7BY/+I1IYb8BczWNbbUpO+pqy0rDciv3lQH5E1bCLrg== dependencies: - "@typescript-eslint/types" "8.5.0" + "@typescript-eslint/types" "8.6.0" eslint-visitor-keys "^3.4.3" "@ungap/structured-clone@^1.2.0":