From 6f4be61d13431f2327cabc50c71b999451821948 Mon Sep 17 00:00:00 2001 From: Dominique Clarke Date: Wed, 18 Sep 2024 21:32:45 -0400 Subject: [PATCH] [Synthetics] waterfall chart - handle cached resources (#193089) ## Summary Resolves https://github.com/elastic/kibana/issues/184794 Ensures that the cached resources display accurate timing information on the waterfall chart tooltips. The information displayed should match the information displayed in the flyout when the request url is clicked. Tooltip Screenshot 2024-09-16 at 8 49 55 PM Flyout Screenshot 2024-09-16 at 2 07 56 PM ### Release note Synthetics - resolves an issue for multi step browser journeys where timings for cached resources within the same step were inaccurate within the waterfall chart. ### Testing 1. Create a browser monitor with duplicate requests. For example: ``` step("multi resource step", async () => { await page.goto('https://github.com'); await page.goto('https://github.com'); await page.goto('https://github.com'); }) ``` 2. Navigate to the monitor details page 3. Find the last test run panel, click the view test details button, then click the view performance breakdown button ![image](https://github.com/user-attachments/assets/b66addcb-21f6-4eac-8c60-dc3387b33853) ![image](https://github.com/user-attachments/assets/67f04b9f-4ff6-4ce6-85d1-2a89869e4a2c) 4. Scroll down to the waterfall chart. If you use github, requests after about 115 should be cached. Note: some request may have been aborted and their waterfall tooltip won't show. Find a request that was not aborted, hover to see the tooltip, then click the request to view the flyout and confirm the information. --- .../network_data/data_formatting.test.ts | 125 +++--------------- .../common/network_data/data_formatting.ts | 72 ++++++---- .../common/network_data/types.ts | 16 ++- .../step_waterfall_chart/waterfall/README.md | 12 +- .../waterfall/context/waterfall_context.tsx | 4 +- .../waterfall/middle_truncated_text.test.tsx | 25 +--- .../waterfall/middle_truncated_text.tsx | 14 +- .../waterfall/waterfall_bar_chart.tsx | 20 +-- .../waterfall/waterfall_chart_wrapper.tsx | 4 +- .../waterfall_flyout/use_flyout.test.tsx | 2 + .../waterfall_flyout.test.tsx | 2 + .../waterfall_flyout/waterfall_flyout.tsx | 8 +- .../waterfall/waterfall_sidebar_item.test.tsx | 4 +- .../waterfall/waterfall_sidebar_item.tsx | 10 +- .../waterfall_tooltip_content.test.tsx | 32 ++++- .../waterfall/waterfall_tooltip_content.tsx | 23 ++-- 16 files changed, 157 insertions(+), 216 deletions(-) diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/common/network_data/data_formatting.test.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/common/network_data/data_formatting.test.ts index 2f4136d4259637..7313d21dd3ffb1 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/common/network_data/data_formatting.test.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/common/network_data/data_formatting.test.ts @@ -12,8 +12,14 @@ import { getSeriesAndDomain, getSidebarItems, } from './data_formatting'; -import { MimeType, FriendlyFlyoutLabels, FriendlyTimingLabels, Timings, Metadata } from './types'; -import { WaterfallDataEntry } from './types'; +import { + MimeType, + FriendlyFlyoutLabels, + FriendlyTimingLabels, + Timings, + Metadata, + WaterfallTooltipItem, +} from './types'; import type { DateFormatter } from '../../../../../../hooks/use_date_format'; import { mockMoment } from '../../../../utils/formatting/test_helpers'; import { NetworkEvent } from '../../../../../../../common/runtime_types'; @@ -247,11 +253,6 @@ describe('getSeriesAndDomain', () => { "colour": "#b0c9e0", "id": 0, "isHighlighted": true, - "showTooltip": true, - "tooltipProps": Object { - "colour": "#b0c9e0", - "value": "Queued / Blocked: 0.9ms", - }, }, "x": 0, "y": 0.8540000017092098, @@ -262,11 +263,6 @@ describe('getSeriesAndDomain', () => { "colour": "#aad9cc", "id": 0, "isHighlighted": true, - "showTooltip": true, - "tooltipProps": Object { - "colour": "#aad9cc", - "value": "DNS: 4ms", - }, }, "x": 0, "y": 4.413999999087537, @@ -277,11 +273,6 @@ describe('getSeriesAndDomain', () => { "colour": "#c8b8dc", "id": 0, "isHighlighted": true, - "showTooltip": true, - "tooltipProps": Object { - "colour": "#c8b8dc", - "value": "Connecting: 26ms", - }, }, "x": 0, "y": 30.135000000882428, @@ -292,11 +283,6 @@ describe('getSeriesAndDomain', () => { "colour": "#e5c7d7", "id": 0, "isHighlighted": true, - "showTooltip": true, - "tooltipProps": Object { - "colour": "#e5c7d7", - "value": "TLS: 55ms", - }, }, "x": 0, "y": 85.52200000121957, @@ -307,11 +293,6 @@ describe('getSeriesAndDomain', () => { "colour": "#f3b3a6", "id": 0, "isHighlighted": true, - "showTooltip": true, - "tooltipProps": Object { - "colour": "#f3b3a6", - "value": "Sending request: 0.4ms", - }, }, "x": 0, "y": 85.88200000303914, @@ -322,11 +303,6 @@ describe('getSeriesAndDomain', () => { "colour": "#e7664c", "id": 0, "isHighlighted": true, - "showTooltip": true, - "tooltipProps": Object { - "colour": "#e7664c", - "value": "Waiting (TTFB): 35ms", - }, }, "x": 0, "y": 120.4600000019127, @@ -337,11 +313,6 @@ describe('getSeriesAndDomain', () => { "colour": "#9170b8", "id": 0, "isHighlighted": true, - "showTooltip": true, - "tooltipProps": Object { - "colour": "#9170b8", - "value": "Content downloading (CSS): 0.6ms", - }, }, "x": 0, "y": 121.01200000324752, @@ -352,11 +323,6 @@ describe('getSeriesAndDomain', () => { "colour": "#b0c9e0", "id": 1, "isHighlighted": true, - "showTooltip": true, - "tooltipProps": Object { - "colour": "#b0c9e0", - "value": "Queued / Blocked: 85ms", - }, }, "x": 1, "y": 84.90799999795854, @@ -367,11 +333,6 @@ describe('getSeriesAndDomain', () => { "colour": "#f3b3a6", "id": 1, "isHighlighted": true, - "showTooltip": true, - "tooltipProps": Object { - "colour": "#f3b3a6", - "value": "Sending request: 0.2ms", - }, }, "x": 1, "y": 85.14699999883305, @@ -382,11 +343,6 @@ describe('getSeriesAndDomain', () => { "colour": "#e7664c", "id": 1, "isHighlighted": true, - "showTooltip": true, - "tooltipProps": Object { - "colour": "#e7664c", - "value": "Waiting (TTFB): 53ms", - }, }, "x": 1, "y": 137.70799999925657, @@ -397,11 +353,6 @@ describe('getSeriesAndDomain', () => { "colour": "#da8b45", "id": 1, "isHighlighted": true, - "showTooltip": true, - "tooltipProps": Object { - "colour": "#da8b45", - "value": "Content downloading (JS): 3ms", - }, }, "x": 1, "y": 140.7760000010603, @@ -420,11 +371,6 @@ describe('getSeriesAndDomain', () => { "colour": "#b0c9e0", "id": 0, "isHighlighted": true, - "showTooltip": true, - "tooltipProps": Object { - "colour": "#b0c9e0", - "value": "Queued / Blocked: 0.9ms", - }, }, "x": 0, "y": 0.8540000017092098, @@ -435,11 +381,6 @@ describe('getSeriesAndDomain', () => { "colour": "#aad9cc", "id": 0, "isHighlighted": true, - "showTooltip": true, - "tooltipProps": Object { - "colour": "#aad9cc", - "value": "DNS: 4ms", - }, }, "x": 0, "y": 4.413999999087537, @@ -450,11 +391,6 @@ describe('getSeriesAndDomain', () => { "colour": "#c8b8dc", "id": 0, "isHighlighted": true, - "showTooltip": true, - "tooltipProps": Object { - "colour": "#c8b8dc", - "value": "Connecting: 26ms", - }, }, "x": 0, "y": 30.135000000882428, @@ -465,11 +401,6 @@ describe('getSeriesAndDomain', () => { "colour": "#e5c7d7", "id": 0, "isHighlighted": true, - "showTooltip": true, - "tooltipProps": Object { - "colour": "#e5c7d7", - "value": "TLS: 55ms", - }, }, "x": 0, "y": 85.52200000121957, @@ -480,11 +411,6 @@ describe('getSeriesAndDomain', () => { "colour": "#f3b3a6", "id": 0, "isHighlighted": true, - "showTooltip": true, - "tooltipProps": Object { - "colour": "#f3b3a6", - "value": "Sending request: 0.4ms", - }, }, "x": 0, "y": 85.88200000303914, @@ -495,11 +421,6 @@ describe('getSeriesAndDomain', () => { "colour": "#e7664c", "id": 0, "isHighlighted": true, - "showTooltip": true, - "tooltipProps": Object { - "colour": "#e7664c", - "value": "Waiting (TTFB): 35ms", - }, }, "x": 0, "y": 120.4600000019127, @@ -510,11 +431,6 @@ describe('getSeriesAndDomain', () => { "colour": "#9170b8", "id": 0, "isHighlighted": true, - "showTooltip": true, - "tooltipProps": Object { - "colour": "#9170b8", - "value": "Content downloading (CSS): 0.6ms", - }, }, "x": 0, "y": 121.01200000324752, @@ -524,11 +440,6 @@ describe('getSeriesAndDomain', () => { "config": Object { "colour": "#da8b45", "isHighlighted": true, - "showTooltip": true, - "tooltipProps": Object { - "colour": "#da8b45", - "value": "Content downloading (JS): 3ms", - }, }, "x": 1, "y": 3.714999998046551, @@ -546,8 +457,6 @@ describe('getSeriesAndDomain', () => { "config": Object { "colour": "", "isHighlighted": true, - "showTooltip": false, - "tooltipProps": undefined, }, "x": 0, "y": 0, @@ -614,8 +523,10 @@ describe('getSeriesAndDomain', () => { "value": undefined, }, ], + "networkItemTooltipProps": Array [], "requestHeaders": undefined, "responseHeaders": undefined, + "showTooltip": false, "url": "file:///Users/dominiqueclarke/dev/synthetics/examples/todos/app/app.js", "x": 0, }, @@ -625,8 +536,6 @@ describe('getSeriesAndDomain', () => { "config": Object { "colour": "", "isHighlighted": true, - "showTooltip": false, - "tooltipProps": undefined, }, "x": 0, "y": 0, @@ -660,23 +569,21 @@ describe('getSeriesAndDomain', () => { }); it('handles formatting when mime type is not mapped to a specific mime type bucket', () => { - const { series } = getSeriesAndDomain( + const { metadata } = getSeriesAndDomain( networkItemsWithUnknownMimeType, false, mockDateFormatter ); /* verify that raw mime type appears in the tooltip config and that * the colour is mapped to mime type other */ - const contentDownloadingConfigItem = series.find((item: WaterfallDataEntry) => { - const { tooltipProps } = item.config; - if (tooltipProps && typeof tooltipProps.value === 'string') { + const contentDownloadingConfigItem = metadata[0].networkItemTooltipProps.find( + (item: WaterfallTooltipItem) => { return ( - tooltipProps.value.includes('application/x-unknown') && - tooltipProps.colour === colourPalette[MimeType.Other] + item.value.includes('application/x-unknown') && + item.colour === colourPalette[MimeType.Other] ); } - return false; - }); + ); expect(contentDownloadingConfigItem).toBeDefined(); }); diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/common/network_data/data_formatting.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/common/network_data/data_formatting.ts index 8ad55e56ee40af..31fac41865d6a1 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/common/network_data/data_formatting.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/common/network_data/data_formatting.ts @@ -20,7 +20,8 @@ import { Metadata, MimeType, MimeTypesMap, - SidebarItem, + WaterfallNetworkItem, + WaterfallTooltipItem, TIMING_ORDER, Timings, } from './types'; @@ -167,10 +168,11 @@ export const getSeriesAndDomain = ( const queryMatcher = getQueryMatcher(query); const filterMatcher = getFilterMatcher(activeFilters); items.forEach((item, index) => { + let showTooltip = true; const mimeTypeColour = getColourForMimeType(item.mimeType); const offsetValue = getValueForOffset(item); let currentOffset = offsetValue - zeroOffset; - metadata.push(formatMetadata({ item, index, requestStart: currentOffset, dateFormatter })); + const requestStart = currentOffset; const isHighlighted = isHighlightedItem(item, queryMatcher, filterMatcher); if (isHighlighted) { totalHighlightedRequests++; @@ -190,14 +192,26 @@ export const getSeriesAndDomain = ( } let timingValueFound = false; + const networkItemTooltipProps = []; TIMING_ORDER.forEach((timing) => { const value = getValue(item.timings, timing); - if (value && value >= 0) { + const colour = timing === Timings.Receive ? mimeTypeColour : colourPalette[timing]; + + if (value !== null && value !== undefined && value >= 0) { timingValueFound = true; - const colour = timing === Timings.Receive ? mimeTypeColour : colourPalette[timing]; const y = currentOffset + value; + const tooltipProps = { + value: getFriendlyTooltipValue({ + value: y - currentOffset, + timing, + mimeType: item.mimeType, + }), + colour, + }; + networkItemTooltipProps.push(tooltipProps); + series.push({ x: index, y0: currentOffset, @@ -206,15 +220,6 @@ export const getSeriesAndDomain = ( id: index, colour, isHighlighted, - showTooltip: true, - tooltipProps: { - value: getFriendlyTooltipValue({ - value: y - currentOffset, - timing, - mimeType: item.mimeType, - }), - colour, - }, }, }); currentOffset = y; @@ -225,8 +230,19 @@ export const getSeriesAndDomain = ( * if total time is not available use 0, set showTooltip to false, * and omit tooltip props */ if (!timingValueFound) { + showTooltip = false; const total = item.timings.total; const hasTotal = total !== -1; + if (hasTotal) { + networkItemTooltipProps.push({ + value: getFriendlyTooltipValue({ + value: total, + timing: Timings.Receive, + mimeType: item.mimeType, + }), + colour: mimeTypeColour, + }); + } series.push({ x: index, y0: hasTotal ? currentOffset : 0, @@ -234,20 +250,20 @@ export const getSeriesAndDomain = ( config: { isHighlighted, colour: hasTotal ? mimeTypeColour : '', - showTooltip: hasTotal, - tooltipProps: hasTotal - ? { - value: getFriendlyTooltipValue({ - value: total, - timing: Timings.Receive, - mimeType: item.mimeType, - }), - colour: mimeTypeColour, - } - : undefined, }, }); } + + metadata.push( + formatMetadata({ + item, + index, + showTooltip, + requestStart, + dateFormatter, + networkItemTooltipProps, + }) + ); }); const yValues = series.map((serie) => serie.y); @@ -282,11 +298,15 @@ const formatMetadata = ({ index, requestStart, dateFormatter, + showTooltip, + networkItemTooltipProps, }: { item: NetworkEvent; index: number; requestStart: number; dateFormatter: DateFormatter; + showTooltip: boolean; + networkItemTooltipProps: WaterfallTooltipItem[]; }) => { const { certificates, @@ -304,6 +324,8 @@ const formatMetadata = ({ return { x: index, url, + networkItemTooltipProps, + showTooltip, requestHeaders: formatHeaders(requestHeaders), responseHeaders: formatHeaders(responseHeaders), certificates: certificates @@ -383,7 +405,7 @@ export const getSidebarItems = ( onlyHighlighted: boolean, query: string, activeFilters: string[] -): SidebarItem[] => { +): WaterfallNetworkItem[] => { const queryMatcher = getQueryMatcher(query); const filterMatcher = getFilterMatcher(activeFilters); const sideBarItems = items.map((item, index) => { diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/common/network_data/types.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/common/network_data/types.ts index 78076be872dbb2..4496009aaa7962 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/common/network_data/types.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/common/network_data/types.ts @@ -256,7 +256,7 @@ export const MimeTypesMap: Record = { 'application/json': MimeType.XHR, }; -export type SidebarItem = Pick & { +export type WaterfallNetworkItem = Pick & { isHighlighted: boolean; index: number; offsetIndex: number; @@ -310,27 +310,29 @@ interface PlotProperties { y0: number; } -export interface WaterfallDataSeriesConfigProperties { - tooltipProps?: Record; - showTooltip: boolean; -} - export interface WaterfallMetadataItem { name: string; value?: string; } +export interface WaterfallTooltipItem { + colour: string; + value: string; +} + export interface WaterfallMetadataEntry { x: number; url: string; requestHeaders?: WaterfallMetadataItem[]; responseHeaders?: WaterfallMetadataItem[]; certificates?: WaterfallMetadataItem[]; + networkItemTooltipProps: WaterfallTooltipItem[]; + showTooltip: boolean; details: WaterfallMetadataItem[]; } export type WaterfallDataEntry = PlotProperties & { - config: WaterfallDataSeriesConfigProperties & Record; + config: Record; }; export type WaterfallMetadata = WaterfallMetadataEntry[]; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/README.md b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/README.md index cf8d3b5345eaab..61e5de6249c4fb 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/README.md +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/README.md @@ -6,7 +6,7 @@ The waterfall chart component aims to be agnostic in it's approach, so that a va ## Requirements for usage -The waterfall chart component asssumes that the consumer is making use of `KibanaReactContext`, and as such things like `useKibana` can be called. +The waterfall chart component asssumes that the consumer is making use of `KibanaReactContext`, and as such things like `useKibana` can be called. Consumers are also expected to be using the `` so that the waterfall chart can apply styled-component styles based on the EUI theme. @@ -24,13 +24,13 @@ This section aims to cover some things that are non-standard. By default the formatting of tooltip values is very basic, but for a waterfall chart there needs to be a great deal of flexibility to represent whatever breakdown you're trying to show. -As such a custom tooltip component is used. This custom component would usually only have access to some basic props that pertain to the values of the hovered bar. The waterfall chart component extends this by making us of a waterfall chart context. +As such a custom tooltip component is used. This custom component would usually only have access to some basic props that pertain to the values of the hovered bar. The waterfall chart component extends this by making us of a waterfall chart context. -The custom tooltip component can use the context to access the full set of chart data, find the relevant items (those with the same `x` value) and call a custom `renderTooltipItem` for each item, `renderTooltipItem` will be passed `item.config.tooltipProps`. Every consumer can choose what they use for their `tooltipProps`. +The custom tooltip component can use the context to access the full set of chart data, find the relevant items (those with the same `x` value) and call a custom `renderTooltipItem` for each item, `renderTooltipItem` will be passed `item.config.tooltipProps`. Every consumer can choose what they use for their `tooltipProps`. Some consumers might need colours, some might need iconography and so on. The waterfall chart doesn't make assumptions, and will render out the React content returned by `renderTooltipItem`. -IMPORTANT: `renderTooltipItem` is provided via context and not as a direct prop due to the fact the custom tooltip component would usually only have access to the props provided directly to it from Elastic Charts. +IMPORTANT: `renderTooltipItem` is provided via context and not as a direct prop due to the fact the custom tooltip component would usually only have access to the props provided directly to it from Elastic Charts. ### Colours @@ -90,7 +90,7 @@ A legend is optional. Pulling all of this together, things look like this (for a specific solution): ``` -const renderSidebarItem: RenderItem = (item, index) => { +const renderSidebarItem: RenderItem = (item, index) => { return ; }; @@ -119,5 +119,3 @@ const renderLegendItem: RenderItem = (item) => { ``` A solution could easily forego a sidebar and legend for a more minimalistic view, e.g. maybe a mini waterfall within a table column. - - diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/context/waterfall_context.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/context/waterfall_context.tsx index 39b8479560cbbc..1ccc95f562d5aa 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/context/waterfall_context.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/context/waterfall_context.tsx @@ -20,7 +20,7 @@ import { WaterfallMetadata, } from '../../../common/network_data/types'; import { OnSidebarClick, OnElementClick, OnProjectionClick } from '../waterfall_flyout/use_flyout'; -import { SidebarItem } from '../../../common/network_data/types'; +import { WaterfallNetworkItem } from '../../../common/network_data/types'; export type MarkerItems = Array<{ id: @@ -43,7 +43,7 @@ export interface IWaterfallContext { onSidebarClick?: OnSidebarClick; showOnlyHighlightedNetworkRequests: boolean; showCustomMarks: boolean; - sidebarItems?: SidebarItem[]; + sidebarItems?: WaterfallNetworkItem[]; metadata: WaterfallMetadata; renderTooltipItem: ( item: WaterfallDataEntry['config']['tooltipProps'], diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/middle_truncated_text.test.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/middle_truncated_text.test.tsx index e6b29cfb9faa7b..035eaba33cb90d 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/middle_truncated_text.test.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/middle_truncated_text.test.tsx @@ -26,14 +26,12 @@ describe('getChunks', () => { }); describe('Component', () => { - const url = 'http://www.elastic.co'; it('renders truncated text and aria label', () => { const { getByText, getByLabelText } = render( @@ -47,13 +45,7 @@ describe('Component', () => { it('renders screen reader only text', () => { const { getByTestId } = render( - + ); const { getByText } = within(getByTestId('middleTruncatedTextSROnly')); @@ -63,17 +55,11 @@ describe('Component', () => { it('renders external link', () => { const { getByText } = render( - + ); const link = getByText('Open resource in new tab').closest('a'); - expect(link).toHaveAttribute('href', url); + expect(link).toHaveAttribute('href', longString); expect(link).toHaveAttribute('target', '_blank'); }); @@ -82,9 +68,8 @@ describe('Component', () => { const { getByTestId } = render( diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/middle_truncated_text.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/middle_truncated_text.tsx index 7f95069c56f087..701790799dfbaa 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/middle_truncated_text.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/middle_truncated_text.tsx @@ -26,7 +26,6 @@ interface Props { index: number; highestIndex: number; ariaLabel: string; - text: string; onClick?: (event: React.MouseEvent) => void; setButtonRef?: (ref: HTMLButtonElement | HTMLAnchorElement | null) => void; url: string; @@ -102,14 +101,13 @@ export const getChunks = (text: string = '') => { export const MiddleTruncatedText = ({ index, ariaLabel, - text: fullText, onClick, setButtonRef, url, highestIndex, }: Props) => { - const secureHttps = fullText.startsWith('https://'); - const text = fullText.replace(/https:\/\/www.|http:\/\/www.|http:\/\/|https:\/\//, ''); + const secureHttps = url.startsWith('https://'); + const text = url.replace(/https:\/\/www.|http:\/\/www.|http:\/\/|https:\/\//, ''); const chunks = useMemo(() => { return getChunks(text); @@ -118,15 +116,17 @@ export const MiddleTruncatedText = ({ return ( - {fullText} + {url} + } data-test-subj="middleTruncatedTextToolTip" - delay="long" position="top" > <> diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_bar_chart.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_bar_chart.tsx index 2d44735857f404..3f0a80082aec6b 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_bar_chart.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_bar_chart.tsx @@ -39,27 +39,29 @@ const getChartHeight = (data: WaterfallData): number => { }; const CustomTooltip: CustomChartTooltip = (tooltipInfo) => { - const { data, sidebarItems } = useWaterfallContext(); + const { sidebarItems, metadata } = useWaterfallContext(); return useMemo(() => { const sidebarItem = sidebarItems?.find((item) => item.index === tooltipInfo.header?.value); - const relevantItems = data.filter((item) => { - return ( - item.x === tooltipInfo.header?.value && item.config.showTooltip && item.config.tooltipProps - ); - }); - return relevantItems.length ? ( + if (!sidebarItem) { + return null; + } + const metadataEntry = metadata?.[sidebarItem.index]; + const showTooltip = + metadataEntry?.showTooltip && metadataEntry?.networkItemTooltipProps.length > 1; + return showTooltip ? ( {sidebarItem && ( )} ) : null; - }, [data, sidebarItems, tooltipInfo.header?.value]); + }, [sidebarItems, tooltipInfo.header?.value, metadata]); }; interface Props { diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_chart_wrapper.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_chart_wrapper.tsx index 4f155d56db833d..ec43aa4508f15d 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_chart_wrapper.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_chart_wrapper.tsx @@ -11,7 +11,7 @@ import { EuiHealth } from '@elastic/eui'; import { JourneyStep, NetworkEvent } from '../../../../../../../common/runtime_types'; import { useDateFormat } from '../../../../../../hooks/use_date_format'; import { getSeriesAndDomain, getSidebarItems } from '../../common/network_data/data_formatting'; -import { SidebarItem, LegendItem } from '../../common/network_data/types'; +import { WaterfallNetworkItem, LegendItem } from '../../common/network_data/types'; import { RenderItem, WaterfallDataEntry } from '../../common/network_data/types'; import { useFlyout } from './waterfall_flyout/use_flyout'; import { WaterfallFlyout } from './waterfall_flyout/waterfall_flyout'; @@ -92,7 +92,7 @@ export const WaterfallChartWrapper: React.FC = ({ const highestSideBarIndex = Math.max(...series.map((sr: WaterfallDataEntry) => sr.x)); - const renderSidebarItem: RenderItem = useCallback( + const renderSidebarItem: RenderItem = useCallback( (item) => { return ( { value: 'text/html', }, ], + showTooltip: false, + networkItemTooltipProps: [], }, ]; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_flyout/waterfall_flyout.test.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_flyout/waterfall_flyout.test.tsx index 6e91c479221705..e2d3e8e990066d 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_flyout/waterfall_flyout.test.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_flyout/waterfall_flyout.test.tsx @@ -29,6 +29,8 @@ describe('WaterfallFlyout', () => { value: 'text/html', }, ], + showTooltip: false, + networkItemTooltipProps: [], }; const defaultProps = { diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_flyout/waterfall_flyout.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_flyout/waterfall_flyout.tsx index cb3babffd16b42..7b7c5c9b5a73eb 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_flyout/waterfall_flyout.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_flyout/waterfall_flyout.tsx @@ -93,13 +93,7 @@ export const WaterfallFlyout = ({

- +

diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_sidebar_item.test.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_sidebar_item.test.tsx index 786978e3647a73..8f7516abe9083b 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_sidebar_item.test.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_sidebar_item.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import 'jest-canvas-mock'; import { fireEvent } from '@testing-library/react'; -import { SidebarItem } from '../../common/network_data/types'; +import { WaterfallNetworkItem } from '../../common/network_data/types'; import { WaterfallSidebarItem } from './waterfall_sidebar_item'; import { SIDEBAR_FILTER_MATCHES_SCREENREADER_LABEL } from './translations'; import { getChunks } from './middle_truncated_text'; @@ -19,7 +19,7 @@ describe('waterfall filter', () => { const url = 'http://www.elastic.co/observability/uptime'; const index = 0; const offsetIndex = index + 1; - const item: SidebarItem = { + const item: WaterfallNetworkItem = { url, isHighlighted: true, index, diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_sidebar_item.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_sidebar_item.tsx index a5d26e33a9b5ab..f553c8a81337ce 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_sidebar_item.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_sidebar_item.tsx @@ -7,14 +7,14 @@ import React, { RefObject, useMemo, useCallback, useState } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiBadge } from '@elastic/eui'; -import { SidebarItem } from '../../common/network_data/types'; +import { WaterfallNetworkItem } from '../../common/network_data/types'; import { MiddleTruncatedText } from './middle_truncated_text'; import { SideBarItemHighlighter } from './styles'; import { SIDEBAR_FILTER_MATCHES_SCREENREADER_LABEL } from './translations'; import { OnSidebarClick } from './waterfall_flyout/use_flyout'; interface SidebarItemProps { - item: SidebarItem; + item: WaterfallNetworkItem; renderFilterScreenReaderText?: boolean; onClick?: OnSidebarClick; highestIndex: number; @@ -44,13 +44,11 @@ export const WaterfallSidebarItem = React.memo(function WaterfallSidebarItem({ return is400 || is500 || isSpecific300; }; - const text = item.url; - const ariaLabel = `${ isHighlighted && renderFilterScreenReaderText ? `${SIDEBAR_FILTER_MATCHES_SCREENREADER_LABEL} ` : '' - }${text}`; + }${url}`; return ( ({ }, }, ], + metadata: { + 0: { + networkItemTooltipProps: [ + { + colour: '#000000', + value: 'test-val', + }, + ], + showTooltip: true, + }, + 1: { + networkItemTooltipProps: [ + { + colour: '#010000', + value: 'test-val-missing', + }, + ], + showTooltip: true, + }, + }, renderTooltipItem: (props: any) => (
{props.colour}
@@ -64,7 +84,11 @@ jest.mock('./context/waterfall_context', () => ({ describe('WaterfallTooltipContent', () => { it('renders tooltip', () => { const { getByText, queryByText } = render( - + ); expect(getByText('#000000')).toBeInTheDocument(); expect(getByText('test-val')).toBeInTheDocument(); @@ -75,7 +99,11 @@ describe('WaterfallTooltipContent', () => { it(`doesn't render metric if tooltip props missing`, () => { const { getAllByLabelText, getByText } = render( - + ); const metricElements = getAllByLabelText('tooltip item'); expect(metricElements).toHaveLength(1); diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_tooltip_content.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_tooltip_content.tsx index b4e4ff040d6c61..e783cf08588679 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_tooltip_content.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_tooltip_content.tsx @@ -13,6 +13,7 @@ import { useWaterfallContext } from './context/waterfall_context'; interface Props { text: string; url: string; + index: number; } const StyledText = euiStyled(EuiText)` @@ -23,22 +24,24 @@ const StyledHorizontalRule = euiStyled(EuiHorizontalRule)` background-color: ${(props) => props.theme.eui.euiColorDarkShade}; `; -export const WaterfallTooltipContent: React.FC = ({ text, url }) => { - const { data, renderTooltipItem, sidebarItems } = useWaterfallContext(); +export const WaterfallTooltipContent: React.FC = ({ text, url, index }) => { + const { renderTooltipItem, metadata } = useWaterfallContext(); + // the passed index is base 1, so we need to subtract 1 to get the correct index + const metadataEntry = metadata?.[index - 1]; + const tooltipItems = metadataEntry?.networkItemTooltipProps; + const showTooltip = metadataEntry?.showTooltip; + + if (!tooltipItems || !showTooltip) { + return null; + } - const tooltipMetrics = data.filter( - (datum) => - datum.x === sidebarItems?.find((sidebarItem) => sidebarItem.url === url)?.index && - datum.config.tooltipProps && - datum.config.showTooltip - ); return (
{text} - {tooltipMetrics.map((item, idx) => ( - {renderTooltipItem(item.config.tooltipProps)} + {tooltipItems.map((item, idx) => ( + {renderTooltipItem(item)} ))}