Skip to content

Commit 8f83090

Browse files
[Uptime] Show URL and metrics on sidebar and waterfall item tooltips (#99985)
* Add URL to metrics tooltip. * Add screenreader label for URL container. * Add metrics to URL sidebar tooltip. * Rename vars. * Delete unnecessary code. * Undo rename. * Extract component to dedicated file, add tests. * Fix error in test. * Add offset index to heading of waterfall chart tooltip. * Format the waterfall tool tip header. * Add horizontal rule and bold text for waterfall tooltip. * Extract inline helper function to module-level for reuse. * Reuse waterfall tooltip style. * Style reusable tooltip content. * Adapt existing chart tooltip to use tooltip content component for better consistency. * Delete test code. * Style EUI tooltip arrow. * Revert whitespace change. * Delete obsolete test. * Implement and use common tooltip heading formatter function. * Add tests for new formatter function. * Fix a typo. * Add a comment explaining a style hack. * Add optional chaining to avoid breaking a test. * Revert previous change, use RTL wrapper, rename describe block. Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
1 parent aa8aa7f commit 8f83090

File tree

7 files changed

+180
-21
lines changed

7 files changed

+180
-21
lines changed

x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/data_formatting.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import moment from 'moment';
88
import {
99
colourPalette,
10+
formatTooltipHeading,
1011
getConnectingTime,
1112
getSeriesAndDomain,
1213
getSidebarItems,
@@ -729,3 +730,13 @@ describe('getSidebarItems', () => {
729730
expect(actual[0].offsetIndex).toBe(1);
730731
});
731732
});
733+
734+
describe('formatTooltipHeading', () => {
735+
it('puts index and URL text together', () => {
736+
expect(formatTooltipHeading(1, 'http://www.elastic.co/')).toEqual('1. http://www.elastic.co/');
737+
});
738+
739+
it('returns only the text if `index` is NaN', () => {
740+
expect(formatTooltipHeading(NaN, 'http://www.elastic.co/')).toEqual('http://www.elastic.co/');
741+
});
742+
});

x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/data_formatting.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,3 +450,6 @@ const MIME_TYPE_PALETTE = buildMimeTypePalette();
450450
type ColourPalette = TimingColourPalette & MimeTypeColourPalette;
451451

452452
export const colourPalette: ColourPalette = { ...TIMING_PALETTE, ...MIME_TYPE_PALETTE };
453+
454+
export const formatTooltipHeading = (index: number, fullText: string): string =>
455+
isNaN(index) ? fullText : `${index}. ${fullText}`;

x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/middle_truncated_text.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,19 @@
88
import React, { useMemo } from 'react';
99
import { FormattedMessage } from '@kbn/i18n/react';
1010
import {
11+
EuiButtonEmpty,
1112
EuiScreenReaderOnly,
1213
EuiToolTip,
13-
EuiButtonEmpty,
1414
EuiLink,
1515
EuiText,
1616
EuiIcon,
1717
} from '@elastic/eui';
1818
import { i18n } from '@kbn/i18n';
19+
import { WaterfallTooltipContent } from './waterfall_tooltip_content';
1920
import { WaterfallTooltipResponsiveMaxWidth } from './styles';
2021
import { FIXED_AXIS_HEIGHT } from './constants';
2122
import { euiStyled } from '../../../../../../../../../src/plugins/kibana_react/common';
23+
import { formatTooltipHeading } from '../../step_detail/waterfall/data_formatting';
2224

2325
interface Props {
2426
index: number;
@@ -116,7 +118,9 @@ export const MiddleTruncatedText = ({
116118
</EuiScreenReaderOnly>
117119
<WaterfallTooltipResponsiveMaxWidth
118120
as={EuiToolTip}
119-
content={`${index}. ${fullText}`}
121+
content={
122+
<WaterfallTooltipContent {...{ text: formatTooltipHeading(index, fullText), url }} />
123+
}
120124
data-test-subj="middleTruncatedTextToolTip"
121125
delay="long"
122126
position="top"

x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/styles.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,9 @@ export const WaterfallChartTooltip = euiStyled(WaterfallTooltipResponsiveMaxWidt
153153
border-radius: ${(props) => props.theme.eui.euiBorderRadius};
154154
color: ${(props) => props.theme.eui.euiColorLightestShade};
155155
padding: ${(props) => props.theme.eui.paddingSizes.s};
156+
.euiToolTip__arrow {
157+
background-color: ${(props) => props.theme.eui.euiColorDarkestShade};
158+
}
156159
`;
157160

158161
export const NetworkRequestsTotalStyle = euiStyled(EuiText)`

x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/waterfall_bar_chart.tsx

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@ import {
1818
TickFormatter,
1919
TooltipInfo,
2020
} from '@elastic/charts';
21-
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
2221
import { BAR_HEIGHT } from './constants';
2322
import { useChartTheme } from '../../../../../hooks/use_chart_theme';
2423
import { WaterfallChartChartContainer, WaterfallChartTooltip } from './styles';
2524
import { useWaterfallContext, WaterfallData } from '..';
25+
import { WaterfallTooltipContent } from './waterfall_tooltip_content';
26+
import { formatTooltipHeading } from '../../step_detail/waterfall/data_formatting';
2627

2728
const getChartHeight = (data: WaterfallData): number => {
2829
// We get the last item x(number of bars) and adds 1 to cater for 0 index
@@ -32,23 +33,25 @@ const getChartHeight = (data: WaterfallData): number => {
3233
};
3334

3435
const Tooltip = (tooltipInfo: TooltipInfo) => {
35-
const { data, renderTooltipItem } = useWaterfallContext();
36-
const relevantItems = data.filter((item) => {
37-
return (
38-
item.x === tooltipInfo.header?.value && item.config.showTooltip && item.config.tooltipProps
39-
);
40-
});
41-
return relevantItems.length ? (
42-
<WaterfallChartTooltip>
43-
<EuiFlexGroup direction="column" gutterSize="none">
44-
{relevantItems.map((item, index) => {
45-
return (
46-
<EuiFlexItem key={index}>{renderTooltipItem(item.config.tooltipProps)}</EuiFlexItem>
47-
);
48-
})}
49-
</EuiFlexGroup>
50-
</WaterfallChartTooltip>
51-
) : null;
36+
const { data, sidebarItems } = useWaterfallContext();
37+
return useMemo(() => {
38+
const sidebarItem = sidebarItems?.find((item) => item.index === tooltipInfo.header?.value);
39+
const relevantItems = data.filter((item) => {
40+
return (
41+
item.x === tooltipInfo.header?.value && item.config.showTooltip && item.config.tooltipProps
42+
);
43+
});
44+
return relevantItems.length ? (
45+
<WaterfallChartTooltip>
46+
{sidebarItem && (
47+
<WaterfallTooltipContent
48+
text={formatTooltipHeading(sidebarItem.index + 1, sidebarItem.url)}
49+
url={sidebarItem.url}
50+
/>
51+
)}
52+
</WaterfallChartTooltip>
53+
) : null;
54+
}, [data, sidebarItems, tooltipInfo.header?.value]);
5255
};
5356

5457
interface Props {
@@ -82,7 +85,12 @@ export const WaterfallBarChart = ({
8285
<Settings
8386
showLegend={false}
8487
rotation={90}
85-
tooltip={{ customTooltip: Tooltip }}
88+
tooltip={{
89+
// this is done to prevent the waterfall tooltip from rendering behind Kibana's
90+
// stacked header when the user highlights an item at the top of the chart
91+
boundary: document.getElementById('app-fixed-viewport') ?? undefined,
92+
customTooltip: Tooltip,
93+
}}
8694
theme={theme}
8795
onProjectionClick={handleProjectionClick}
8896
onElementClick={handleElementClick}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import React from 'react';
9+
import { render } from '../../../../../lib/helper/rtl_helpers';
10+
import { WaterfallTooltipContent } from './waterfall_tooltip_content';
11+
12+
jest.mock('../context/waterfall_chart', () => ({
13+
useWaterfallContext: jest.fn().mockReturnValue({
14+
data: [
15+
{
16+
x: 0,
17+
config: {
18+
url: 'https://www.elastic.co',
19+
tooltipProps: {
20+
colour: '#000000',
21+
value: 'test-val',
22+
},
23+
showTooltip: true,
24+
},
25+
},
26+
{
27+
x: 0,
28+
config: {
29+
url: 'https://www.elastic.co/with/missing/tooltip.props',
30+
showTooltip: true,
31+
},
32+
},
33+
{
34+
x: 1,
35+
config: {
36+
url: 'https://www.elastic.co/someresource.path',
37+
tooltipProps: {
38+
colour: '#010000',
39+
value: 'test-val-missing',
40+
},
41+
showTooltip: true,
42+
},
43+
},
44+
],
45+
renderTooltipItem: (props: any) => (
46+
<div aria-label="tooltip item">
47+
<div>{props.colour}</div>
48+
<div>{props.value}</div>
49+
</div>
50+
),
51+
sidebarItems: [
52+
{
53+
isHighlighted: true,
54+
index: 0,
55+
offsetIndex: 1,
56+
url: 'https://www.elastic.co',
57+
status: 200,
58+
method: 'GET',
59+
},
60+
],
61+
}),
62+
}));
63+
64+
describe('WaterfallTooltipContent', () => {
65+
it('renders tooltip', () => {
66+
const { getByText, queryByText } = render(
67+
<WaterfallTooltipContent text="1. https://www.elastic.co" url="https://www.elastic.co" />
68+
);
69+
expect(getByText('#000000')).toBeInTheDocument();
70+
expect(getByText('test-val')).toBeInTheDocument();
71+
expect(getByText('1. https://www.elastic.co')).toBeInTheDocument();
72+
expect(queryByText('#010000')).toBeNull();
73+
expect(queryByText('test-val-missing')).toBeNull();
74+
});
75+
76+
it(`doesn't render metric if tooltip props missing`, () => {
77+
const { getAllByLabelText, getByText } = render(
78+
<WaterfallTooltipContent text="1. https://www.elastic.co" url="https://www.elastic.co" />
79+
);
80+
const metricElements = getAllByLabelText('tooltip item');
81+
expect(metricElements).toHaveLength(1);
82+
expect(getByText('test-val')).toBeInTheDocument();
83+
});
84+
});
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import React from 'react';
9+
import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiText } from '@elastic/eui';
10+
import { useWaterfallContext } from '../context/waterfall_chart';
11+
import { euiStyled } from '../../../../../../../../../src/plugins/kibana_react/common';
12+
13+
interface Props {
14+
text: string;
15+
url: string;
16+
}
17+
18+
const StyledText = euiStyled(EuiText)`
19+
font-weight: bold;
20+
`;
21+
22+
const StyledHorizontalRule = euiStyled(EuiHorizontalRule)`
23+
background-color: ${(props) => props.theme.eui.euiColorDarkShade};
24+
`;
25+
26+
export const WaterfallTooltipContent: React.FC<Props> = ({ text, url }) => {
27+
const { data, renderTooltipItem, sidebarItems } = useWaterfallContext();
28+
29+
const tooltipMetrics = data.filter(
30+
(datum) =>
31+
datum.x === sidebarItems?.find((sidebarItem) => sidebarItem.url === url)?.index &&
32+
datum.config.tooltipProps &&
33+
datum.config.showTooltip
34+
);
35+
return (
36+
<>
37+
<StyledText>{text}</StyledText>
38+
<StyledHorizontalRule margin="none" />
39+
<EuiFlexGroup direction="column" gutterSize="none">
40+
{tooltipMetrics.map((item, idx) => (
41+
<EuiFlexItem key={idx}>{renderTooltipItem(item.config.tooltipProps)}</EuiFlexItem>
42+
))}
43+
</EuiFlexGroup>
44+
</>
45+
);
46+
};

0 commit comments

Comments
 (0)