Skip to content

Commit

Permalink
synthetics - waterfall chart - handle cached resources
Browse files Browse the repository at this point in the history
  • Loading branch information
dominiqueclarke committed Sep 16, 2024
1 parent 7a0fe2e commit 65cef05
Show file tree
Hide file tree
Showing 9 changed files with 83 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -167,10 +167,10 @@ 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 isHighlighted = isHighlightedItem(item, queryMatcher, filterMatcher);
if (isHighlighted) {
totalHighlightedRequests++;
Expand All @@ -190,14 +190,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,
Expand All @@ -206,15 +218,6 @@ export const getSeriesAndDomain = (
id: index,
colour,
isHighlighted,
showTooltip: true,
tooltipProps: {
value: getFriendlyTooltipValue({
value: y - currentOffset,
timing,
mimeType: item.mimeType,
}),
colour,
},
},
});
currentOffset = y;
Expand All @@ -225,29 +228,40 @@ 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,
y: hasTotal ? currentOffset + item.timings.total : 0,
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: currentOffset,
dateFormatter,
networkItemTooltipProps,
})
);
});

const yValues = series.map((serie) => serie.y);
Expand Down Expand Up @@ -282,11 +296,15 @@ const formatMetadata = ({
index,
requestStart,
dateFormatter,
showTooltip,
networkItemTooltipProps,
}: {
item: NetworkEvent;
index: number;
requestStart: number;
dateFormatter: DateFormatter;
showTooltip: boolean;
networkItemTooltipProps?: Array<Record<string, string | number>>;
}) => {
const {
certificates,
Expand All @@ -304,6 +322,8 @@ const formatMetadata = ({
return {
x: index,
url,
networkItemTooltipProps,
showTooltip,
requestHeaders: formatHeaders(requestHeaders),
responseHeaders: formatHeaders(responseHeaders),
certificates: certificates
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ export const MimeTypesMap: Record<string, MimeType> = {
'application/json': MimeType.XHR,
};

export type SidebarItem = Pick<NetworkEvent, 'url' | 'status' | 'method'> & {
export type WaterfallNetworkItem = Pick<NetworkEvent, 'url' | 'status' | 'method'> & {
isHighlighted: boolean;
index: number;
offsetIndex: number;
Expand Down Expand Up @@ -310,27 +310,29 @@ interface PlotProperties {
y0: number;
}

export interface WaterfallDataSeriesConfigProperties {
tooltipProps?: Record<string, string | number>;
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<string, unknown>;
config: Record<string, unknown>;
};

export type WaterfallMetadata = WaterfallMetadataEntry[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<EuiThemeProvider />` so that the waterfall chart can apply styled-component styles based on the EUI theme.

Expand All @@ -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

Expand Down Expand Up @@ -90,7 +90,7 @@ A legend is optional.
Pulling all of this together, things look like this (for a specific solution):

```
const renderSidebarItem: RenderItem<SidebarItem> = (item, index) => {
const renderSidebarItem: RenderItem<WaterfallNetworkItem> = (item, index) => {
return <MiddleTruncatedText text={`${index + 1}. ${item.url}`} />;
};
Expand Down Expand Up @@ -119,5 +119,3 @@ const renderLegendItem: RenderItem<LegendItem> = (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.


Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -43,7 +43,7 @@ export interface IWaterfallContext {
onSidebarClick?: OnSidebarClick;
showOnlyHighlightedNetworkRequests: boolean;
showCustomMarks: boolean;
sidebarItems?: SidebarItem[];
sidebarItems?: WaterfallNetworkItem[];
metadata: WaterfallMetadata;
renderTooltipItem: (
item: WaterfallDataEntry['config']['tooltipProps'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,14 +102,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);
Expand All @@ -118,15 +117,17 @@ export const MiddleTruncatedText = ({
return (
<OuterContainer aria-label={ariaLabel} data-test-subj="middleTruncatedTextContainer">
<EuiScreenReaderOnly>
<span data-test-subj="middleTruncatedTextSROnly">{fullText}</span>
<span data-test-subj="middleTruncatedTextSROnly">{url}</span>
</EuiScreenReaderOnly>
<WaterfallChartTooltip
as={EuiToolTip}
content={
<WaterfallTooltipContent {...{ text: formatTooltipHeading(index, fullText), url }} />
<WaterfallTooltipContent
{...{ text: formatTooltipHeading(index, url), url }}
index={index}
/>
}
data-test-subj="middleTruncatedTextToolTip"
delay="long"
position="top"
>
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -92,7 +92,7 @@ export const WaterfallChartWrapper: React.FC<Props> = ({

const highestSideBarIndex = Math.max(...series.map((sr: WaterfallDataEntry) => sr.x));

const renderSidebarItem: RenderItem<SidebarItem> = useCallback(
const renderSidebarItem: RenderItem<WaterfallNetworkItem> = useCallback(
(item) => {
return (
<WaterfallSidebarItem
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,7 @@ export const WaterfallFlyout = ({
<EuiTitle size="s">
<h2 id="flyoutTitle">
<EuiFlexItem>
<MiddleTruncatedText
index={x + 1}
text={url}
url={url}
ariaLabel={url}
highestIndex={x + 1}
/>
<MiddleTruncatedText index={x + 1} url={url} ariaLabel={url} highestIndex={x + 1} />
</EuiFlexItem>
</h2>
</EuiTitle>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { useWaterfallContext } from './context/waterfall_context';
interface Props {
text: string;
url: string;
index: string;
}

const StyledText = euiStyled(EuiText)`
Expand All @@ -23,22 +24,23 @@ const StyledHorizontalRule = euiStyled(EuiHorizontalRule)`
background-color: ${(props) => props.theme.eui.euiColorDarkShade};
`;

export const WaterfallTooltipContent: React.FC<Props> = ({ text, url }) => {
const { data, renderTooltipItem, sidebarItems } = useWaterfallContext();
export const WaterfallTooltipContent: React.FC<Props> = ({ text, url, index }) => {
const { data, renderTooltipItem, sidebarItems, metadata } = useWaterfallContext();
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 (
<div style={{ maxWidth: 500, height: '100%' }}>
<StyledText size="xs">{text}</StyledText>
<StyledHorizontalRule margin="none" />
<EuiFlexGroup direction="column" gutterSize="none">
{tooltipMetrics.map((item, idx) => (
<EuiFlexItem key={idx}>{renderTooltipItem(item.config.tooltipProps)}</EuiFlexItem>
{tooltipItems.map((item, idx) => (
<EuiFlexItem key={idx}>{renderTooltipItem(item)}</EuiFlexItem>
))}
</EuiFlexGroup>
</div>
Expand Down

0 comments on commit 65cef05

Please sign in to comment.