mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[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>
This commit is contained in:
parent
aa8aa7f23d
commit
8f83090d74
7 changed files with 180 additions and 21 deletions
|
@ -7,6 +7,7 @@
|
|||
import moment from 'moment';
|
||||
import {
|
||||
colourPalette,
|
||||
formatTooltipHeading,
|
||||
getConnectingTime,
|
||||
getSeriesAndDomain,
|
||||
getSidebarItems,
|
||||
|
@ -729,3 +730,13 @@ describe('getSidebarItems', () => {
|
|||
expect(actual[0].offsetIndex).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatTooltipHeading', () => {
|
||||
it('puts index and URL text together', () => {
|
||||
expect(formatTooltipHeading(1, 'http://www.elastic.co/')).toEqual('1. http://www.elastic.co/');
|
||||
});
|
||||
|
||||
it('returns only the text if `index` is NaN', () => {
|
||||
expect(formatTooltipHeading(NaN, 'http://www.elastic.co/')).toEqual('http://www.elastic.co/');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -450,3 +450,6 @@ const MIME_TYPE_PALETTE = buildMimeTypePalette();
|
|||
type ColourPalette = TimingColourPalette & MimeTypeColourPalette;
|
||||
|
||||
export const colourPalette: ColourPalette = { ...TIMING_PALETTE, ...MIME_TYPE_PALETTE };
|
||||
|
||||
export const formatTooltipHeading = (index: number, fullText: string): string =>
|
||||
isNaN(index) ? fullText : `${index}. ${fullText}`;
|
||||
|
|
|
@ -8,17 +8,19 @@
|
|||
import React, { useMemo } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import {
|
||||
EuiButtonEmpty,
|
||||
EuiScreenReaderOnly,
|
||||
EuiToolTip,
|
||||
EuiButtonEmpty,
|
||||
EuiLink,
|
||||
EuiText,
|
||||
EuiIcon,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { WaterfallTooltipContent } from './waterfall_tooltip_content';
|
||||
import { WaterfallTooltipResponsiveMaxWidth } from './styles';
|
||||
import { FIXED_AXIS_HEIGHT } from './constants';
|
||||
import { euiStyled } from '../../../../../../../../../src/plugins/kibana_react/common';
|
||||
import { formatTooltipHeading } from '../../step_detail/waterfall/data_formatting';
|
||||
|
||||
interface Props {
|
||||
index: number;
|
||||
|
@ -116,7 +118,9 @@ export const MiddleTruncatedText = ({
|
|||
</EuiScreenReaderOnly>
|
||||
<WaterfallTooltipResponsiveMaxWidth
|
||||
as={EuiToolTip}
|
||||
content={`${index}. ${fullText}`}
|
||||
content={
|
||||
<WaterfallTooltipContent {...{ text: formatTooltipHeading(index, fullText), url }} />
|
||||
}
|
||||
data-test-subj="middleTruncatedTextToolTip"
|
||||
delay="long"
|
||||
position="top"
|
||||
|
|
|
@ -153,6 +153,9 @@ export const WaterfallChartTooltip = euiStyled(WaterfallTooltipResponsiveMaxWidt
|
|||
border-radius: ${(props) => props.theme.eui.euiBorderRadius};
|
||||
color: ${(props) => props.theme.eui.euiColorLightestShade};
|
||||
padding: ${(props) => props.theme.eui.paddingSizes.s};
|
||||
.euiToolTip__arrow {
|
||||
background-color: ${(props) => props.theme.eui.euiColorDarkestShade};
|
||||
}
|
||||
`;
|
||||
|
||||
export const NetworkRequestsTotalStyle = euiStyled(EuiText)`
|
||||
|
|
|
@ -18,11 +18,12 @@ import {
|
|||
TickFormatter,
|
||||
TooltipInfo,
|
||||
} from '@elastic/charts';
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { BAR_HEIGHT } from './constants';
|
||||
import { useChartTheme } from '../../../../../hooks/use_chart_theme';
|
||||
import { WaterfallChartChartContainer, WaterfallChartTooltip } from './styles';
|
||||
import { useWaterfallContext, WaterfallData } from '..';
|
||||
import { WaterfallTooltipContent } from './waterfall_tooltip_content';
|
||||
import { formatTooltipHeading } from '../../step_detail/waterfall/data_formatting';
|
||||
|
||||
const getChartHeight = (data: WaterfallData): number => {
|
||||
// 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 => {
|
|||
};
|
||||
|
||||
const Tooltip = (tooltipInfo: TooltipInfo) => {
|
||||
const { data, renderTooltipItem } = useWaterfallContext();
|
||||
const relevantItems = data.filter((item) => {
|
||||
return (
|
||||
item.x === tooltipInfo.header?.value && item.config.showTooltip && item.config.tooltipProps
|
||||
);
|
||||
});
|
||||
return relevantItems.length ? (
|
||||
<WaterfallChartTooltip>
|
||||
<EuiFlexGroup direction="column" gutterSize="none">
|
||||
{relevantItems.map((item, index) => {
|
||||
return (
|
||||
<EuiFlexItem key={index}>{renderTooltipItem(item.config.tooltipProps)}</EuiFlexItem>
|
||||
);
|
||||
})}
|
||||
</EuiFlexGroup>
|
||||
</WaterfallChartTooltip>
|
||||
) : null;
|
||||
const { data, sidebarItems } = 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 ? (
|
||||
<WaterfallChartTooltip>
|
||||
{sidebarItem && (
|
||||
<WaterfallTooltipContent
|
||||
text={formatTooltipHeading(sidebarItem.index + 1, sidebarItem.url)}
|
||||
url={sidebarItem.url}
|
||||
/>
|
||||
)}
|
||||
</WaterfallChartTooltip>
|
||||
) : null;
|
||||
}, [data, sidebarItems, tooltipInfo.header?.value]);
|
||||
};
|
||||
|
||||
interface Props {
|
||||
|
@ -82,7 +85,12 @@ export const WaterfallBarChart = ({
|
|||
<Settings
|
||||
showLegend={false}
|
||||
rotation={90}
|
||||
tooltip={{ customTooltip: Tooltip }}
|
||||
tooltip={{
|
||||
// this is done to prevent the waterfall tooltip from rendering behind Kibana's
|
||||
// stacked header when the user highlights an item at the top of the chart
|
||||
boundary: document.getElementById('app-fixed-viewport') ?? undefined,
|
||||
customTooltip: Tooltip,
|
||||
}}
|
||||
theme={theme}
|
||||
onProjectionClick={handleProjectionClick}
|
||||
onElementClick={handleElementClick}
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { render } from '../../../../../lib/helper/rtl_helpers';
|
||||
import { WaterfallTooltipContent } from './waterfall_tooltip_content';
|
||||
|
||||
jest.mock('../context/waterfall_chart', () => ({
|
||||
useWaterfallContext: jest.fn().mockReturnValue({
|
||||
data: [
|
||||
{
|
||||
x: 0,
|
||||
config: {
|
||||
url: 'https://www.elastic.co',
|
||||
tooltipProps: {
|
||||
colour: '#000000',
|
||||
value: 'test-val',
|
||||
},
|
||||
showTooltip: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
x: 0,
|
||||
config: {
|
||||
url: 'https://www.elastic.co/with/missing/tooltip.props',
|
||||
showTooltip: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
x: 1,
|
||||
config: {
|
||||
url: 'https://www.elastic.co/someresource.path',
|
||||
tooltipProps: {
|
||||
colour: '#010000',
|
||||
value: 'test-val-missing',
|
||||
},
|
||||
showTooltip: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
renderTooltipItem: (props: any) => (
|
||||
<div aria-label="tooltip item">
|
||||
<div>{props.colour}</div>
|
||||
<div>{props.value}</div>
|
||||
</div>
|
||||
),
|
||||
sidebarItems: [
|
||||
{
|
||||
isHighlighted: true,
|
||||
index: 0,
|
||||
offsetIndex: 1,
|
||||
url: 'https://www.elastic.co',
|
||||
status: 200,
|
||||
method: 'GET',
|
||||
},
|
||||
],
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('WaterfallTooltipContent', () => {
|
||||
it('renders tooltip', () => {
|
||||
const { getByText, queryByText } = render(
|
||||
<WaterfallTooltipContent text="1. https://www.elastic.co" url="https://www.elastic.co" />
|
||||
);
|
||||
expect(getByText('#000000')).toBeInTheDocument();
|
||||
expect(getByText('test-val')).toBeInTheDocument();
|
||||
expect(getByText('1. https://www.elastic.co')).toBeInTheDocument();
|
||||
expect(queryByText('#010000')).toBeNull();
|
||||
expect(queryByText('test-val-missing')).toBeNull();
|
||||
});
|
||||
|
||||
it(`doesn't render metric if tooltip props missing`, () => {
|
||||
const { getAllByLabelText, getByText } = render(
|
||||
<WaterfallTooltipContent text="1. https://www.elastic.co" url="https://www.elastic.co" />
|
||||
);
|
||||
const metricElements = getAllByLabelText('tooltip item');
|
||||
expect(metricElements).toHaveLength(1);
|
||||
expect(getByText('test-val')).toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiText } from '@elastic/eui';
|
||||
import { useWaterfallContext } from '../context/waterfall_chart';
|
||||
import { euiStyled } from '../../../../../../../../../src/plugins/kibana_react/common';
|
||||
|
||||
interface Props {
|
||||
text: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
const StyledText = euiStyled(EuiText)`
|
||||
font-weight: bold;
|
||||
`;
|
||||
|
||||
const StyledHorizontalRule = euiStyled(EuiHorizontalRule)`
|
||||
background-color: ${(props) => props.theme.eui.euiColorDarkShade};
|
||||
`;
|
||||
|
||||
export const WaterfallTooltipContent: React.FC<Props> = ({ text, url }) => {
|
||||
const { data, renderTooltipItem, sidebarItems } = useWaterfallContext();
|
||||
|
||||
const tooltipMetrics = data.filter(
|
||||
(datum) =>
|
||||
datum.x === sidebarItems?.find((sidebarItem) => sidebarItem.url === url)?.index &&
|
||||
datum.config.tooltipProps &&
|
||||
datum.config.showTooltip
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<StyledText>{text}</StyledText>
|
||||
<StyledHorizontalRule margin="none" />
|
||||
<EuiFlexGroup direction="column" gutterSize="none">
|
||||
{tooltipMetrics.map((item, idx) => (
|
||||
<EuiFlexItem key={idx}>{renderTooltipItem(item.config.tooltipProps)}</EuiFlexItem>
|
||||
))}
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
);
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue