mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Shahzad <shahzad.muhammad@elastic.co>
This commit is contained in:
parent
39c2a52abc
commit
3f455e8ef6
8 changed files with 141 additions and 39 deletions
|
@ -131,17 +131,18 @@ describe('WaterfallChartWrapper', () => {
|
|||
});
|
||||
|
||||
it('opens flyout on sidebar click and closes on flyout close button', async () => {
|
||||
const { getByText, getAllByText, getByTestId, queryByText, getByRole } = render(
|
||||
const { getByText, getByTestId, queryByText, getByRole } = render(
|
||||
<WaterfallChartWrapper total={mockNetworkItems.length} data={mockNetworkItems} />
|
||||
);
|
||||
|
||||
expect(getByText(`1. ${mockNetworkItems[0].url}`)).toBeInTheDocument();
|
||||
expect(getByText(`${mockNetworkItems[0].url}`)).toBeInTheDocument();
|
||||
expect(getByText(`1.`)).toBeInTheDocument();
|
||||
expect(queryByText('Content type')).not.toBeInTheDocument();
|
||||
expect(queryByText(`${mockNetworkItems[0]?.mimeType}`)).not.toBeInTheDocument();
|
||||
|
||||
// open flyout
|
||||
// selecter matches both button and accessible text. Button is the second element in the array;
|
||||
const sidebarButton = getAllByText(/1./)[1];
|
||||
// selector matches both button and accessible text. Button is the second element in the array;
|
||||
const sidebarButton = getByTestId(`middleTruncatedTextButton1`);
|
||||
fireEvent.click(sidebarButton);
|
||||
|
||||
// check for sample flyout items
|
||||
|
@ -163,17 +164,18 @@ describe('WaterfallChartWrapper', () => {
|
|||
});
|
||||
|
||||
it('opens flyout on sidebar click and closes on second sidebar click', async () => {
|
||||
const { getByText, getAllByText, getByTestId, queryByText } = render(
|
||||
const { getByText, getByTestId, queryByText } = render(
|
||||
<WaterfallChartWrapper total={mockNetworkItems.length} data={mockNetworkItems} />
|
||||
);
|
||||
|
||||
expect(getByText(`1. ${mockNetworkItems[0].url}`)).toBeInTheDocument();
|
||||
expect(getByText(`${mockNetworkItems[0].url}`)).toBeInTheDocument();
|
||||
expect(getByText(`1.`)).toBeInTheDocument();
|
||||
expect(queryByText('Content type')).not.toBeInTheDocument();
|
||||
expect(queryByText(`${mockNetworkItems[0]?.mimeType}`)).not.toBeInTheDocument();
|
||||
|
||||
// open flyout
|
||||
// selecter matches both button and accessible text. Button is the second element in the array;
|
||||
const sidebarButton = getAllByText(/1./)[1];
|
||||
// selector matches both button and accessible text. Button is the second element in the array;
|
||||
const sidebarButton = getByTestId(`middleTruncatedTextButton1`);
|
||||
fireEvent.click(sidebarButton);
|
||||
|
||||
// check for sample flyout items and that the flyout is focused
|
||||
|
|
|
@ -81,6 +81,8 @@ export const WaterfallChartWrapper: React.FC<Props> = ({ data, total }) => {
|
|||
);
|
||||
}, [flyoutData, isFlyoutVisible, onFlyoutClose]);
|
||||
|
||||
const highestSideBarIndex = Math.max(...series.map((sr) => sr.x));
|
||||
|
||||
const renderSidebarItem: RenderItem<SidebarItem> = useCallback(
|
||||
(item) => {
|
||||
return (
|
||||
|
@ -88,10 +90,11 @@ export const WaterfallChartWrapper: React.FC<Props> = ({ data, total }) => {
|
|||
item={item}
|
||||
renderFilterScreenReaderText={hasFilters && !onlyHighlighted}
|
||||
onClick={onSidebarClick}
|
||||
highestIndex={highestSideBarIndex}
|
||||
/>
|
||||
);
|
||||
},
|
||||
[hasFilters, onlyHighlighted, onSidebarClick]
|
||||
[hasFilters, onlyHighlighted, onSidebarClick, highestSideBarIndex]
|
||||
);
|
||||
|
||||
useTrackMetric({ app: 'uptime', metric: 'waterfall_chart_view', metricType: METRIC_TYPE.COUNT });
|
||||
|
|
|
@ -77,7 +77,7 @@ export const WaterfallFlyout = ({
|
|||
return null;
|
||||
}
|
||||
|
||||
const { url, details, certificates, requestHeaders, responseHeaders } = flyoutData;
|
||||
const { x, url, details, certificates, requestHeaders, responseHeaders } = flyoutData;
|
||||
|
||||
trackMetric({ metric: 'waterfall_flyout', metricType: METRIC_TYPE.CLICK });
|
||||
|
||||
|
@ -93,7 +93,13 @@ export const WaterfallFlyout = ({
|
|||
<EuiTitle size="s">
|
||||
<h2 id="flyoutTitle">
|
||||
<EuiFlexItem>
|
||||
<MiddleTruncatedText text={url} url={url} ariaLabel={url} />
|
||||
<MiddleTruncatedText
|
||||
index={x + 1}
|
||||
text={url}
|
||||
url={url}
|
||||
ariaLabel={url}
|
||||
highestIndex={x + 1}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
|
|
|
@ -13,9 +13,10 @@ import { SidebarItem } from '../waterfall/types';
|
|||
import { render } from '../../../../../lib/helper/rtl_helpers';
|
||||
import { WaterfallSidebarItem } from './waterfall_sidebar_item';
|
||||
import { SIDEBAR_FILTER_MATCHES_SCREENREADER_LABEL } from '../../waterfall/components/translations';
|
||||
import { getChunks } from '../../waterfall/components/middle_truncated_text';
|
||||
|
||||
describe('waterfall filter', () => {
|
||||
const url = 'http://www.elastic.co';
|
||||
const url = 'http://www.elastic.co/observability/uptime';
|
||||
const index = 0;
|
||||
const offsetIndex = index + 1;
|
||||
const item: SidebarItem = {
|
||||
|
@ -25,26 +26,34 @@ describe('waterfall filter', () => {
|
|||
offsetIndex,
|
||||
};
|
||||
|
||||
it('renders sidbar item', () => {
|
||||
const { getByText } = render(<WaterfallSidebarItem item={item} />);
|
||||
it('renders sidebar item', () => {
|
||||
const { getByText } = render(<WaterfallSidebarItem item={item} highestIndex={10} />);
|
||||
|
||||
expect(getByText(`${offsetIndex}. ${url}`));
|
||||
const chunks = getChunks(url.replace('http://www.', ''));
|
||||
|
||||
expect(getByText(`${offsetIndex}. ${chunks.first}`));
|
||||
expect(getByText(`${chunks.last}`));
|
||||
});
|
||||
|
||||
it('render screen reader text when renderFilterScreenReaderText is true', () => {
|
||||
const { getByLabelText } = render(
|
||||
<WaterfallSidebarItem item={item} renderFilterScreenReaderText={true} />
|
||||
<WaterfallSidebarItem item={item} renderFilterScreenReaderText={true} highestIndex={10} />
|
||||
);
|
||||
|
||||
expect(
|
||||
getByLabelText(`${SIDEBAR_FILTER_MATCHES_SCREENREADER_LABEL} ${offsetIndex}. ${url}`)
|
||||
getByLabelText(`${SIDEBAR_FILTER_MATCHES_SCREENREADER_LABEL} ${url}`)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not render screen reader text when renderFilterScreenReaderText is false', () => {
|
||||
const onClick = jest.fn();
|
||||
const { getByRole } = render(
|
||||
<WaterfallSidebarItem item={item} renderFilterScreenReaderText={false} onClick={onClick} />
|
||||
<WaterfallSidebarItem
|
||||
item={item}
|
||||
renderFilterScreenReaderText={false}
|
||||
onClick={onClick}
|
||||
highestIndex={10}
|
||||
/>
|
||||
);
|
||||
const button = getByRole('button');
|
||||
fireEvent.click(button);
|
|
@ -17,10 +17,12 @@ interface SidebarItemProps {
|
|||
item: SidebarItem;
|
||||
renderFilterScreenReaderText?: boolean;
|
||||
onClick?: OnSidebarClick;
|
||||
highestIndex: number;
|
||||
}
|
||||
|
||||
export const WaterfallSidebarItem = ({
|
||||
item,
|
||||
highestIndex,
|
||||
renderFilterScreenReaderText,
|
||||
onClick,
|
||||
}: SidebarItemProps) => {
|
||||
|
@ -42,7 +44,8 @@ export const WaterfallSidebarItem = ({
|
|||
return is400 || is500 || isSpecific300;
|
||||
};
|
||||
|
||||
const text = `${offsetIndex}. ${item.url}`;
|
||||
const text = item.url;
|
||||
|
||||
const ariaLabel = `${
|
||||
isHighlighted && renderFilterScreenReaderText
|
||||
? `${SIDEBAR_FILTER_MATCHES_SCREENREADER_LABEL} `
|
||||
|
@ -58,11 +61,13 @@ export const WaterfallSidebarItem = ({
|
|||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false} style={{ minWidth: 0 }}>
|
||||
<MiddleTruncatedText
|
||||
index={offsetIndex}
|
||||
text={text}
|
||||
url={url}
|
||||
ariaLabel={ariaLabel}
|
||||
onClick={handleSidebarClick}
|
||||
setButtonRef={setRef}
|
||||
highestIndex={highestIndex}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
@ -70,11 +75,13 @@ export const WaterfallSidebarItem = ({
|
|||
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
|
||||
<EuiFlexItem grow={false} style={{ minWidth: 0 }}>
|
||||
<MiddleTruncatedText
|
||||
index={offsetIndex}
|
||||
text={text}
|
||||
url={url}
|
||||
ariaLabel={ariaLabel}
|
||||
onClick={handleSidebarClick}
|
||||
setButtonRef={setRef}
|
||||
highestIndex={highestIndex}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem component="span" grow={false}>
|
||||
|
|
|
@ -5,9 +5,10 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { getChunks, MiddleTruncatedText } from './middle_truncated_text';
|
||||
import { render, within, fireEvent, waitFor } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { within, fireEvent, waitFor } from '@testing-library/react';
|
||||
import { getChunks, MiddleTruncatedText } from './middle_truncated_text';
|
||||
import { render } from '../../../../../lib/helper/rtl_helpers';
|
||||
|
||||
const longString =
|
||||
'this-is-a-really-really-really-really-really-really-really-really-long-string.madeup.extension';
|
||||
|
@ -28,7 +29,14 @@ describe('Component', () => {
|
|||
const url = 'http://www.elastic.co';
|
||||
it('renders truncated text and aria label', () => {
|
||||
const { getByText, getByLabelText } = render(
|
||||
<MiddleTruncatedText text={longString} ariaLabel={longString} url={url} />
|
||||
<MiddleTruncatedText
|
||||
index={1}
|
||||
text={longString}
|
||||
ariaLabel={longString}
|
||||
url={url}
|
||||
onClick={jest.fn()}
|
||||
highestIndex={10}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(getByText(first)).toBeInTheDocument();
|
||||
|
@ -39,7 +47,13 @@ describe('Component', () => {
|
|||
|
||||
it('renders screen reader only text', () => {
|
||||
const { getByTestId } = render(
|
||||
<MiddleTruncatedText text={longString} ariaLabel={longString} url={url} />
|
||||
<MiddleTruncatedText
|
||||
index={1}
|
||||
text={longString}
|
||||
ariaLabel={longString}
|
||||
url={url}
|
||||
highestIndex={10}
|
||||
/>
|
||||
);
|
||||
|
||||
const { getByText } = within(getByTestId('middleTruncatedTextSROnly'));
|
||||
|
@ -49,7 +63,13 @@ describe('Component', () => {
|
|||
|
||||
it('renders external link', () => {
|
||||
const { getByText } = render(
|
||||
<MiddleTruncatedText text={longString} ariaLabel={longString} url={url} />
|
||||
<MiddleTruncatedText
|
||||
index={1}
|
||||
text={longString}
|
||||
ariaLabel={longString}
|
||||
url={url}
|
||||
highestIndex={10}
|
||||
/>
|
||||
);
|
||||
const link = getByText('Open resource in new tab').closest('a');
|
||||
|
||||
|
@ -61,13 +81,15 @@ describe('Component', () => {
|
|||
const handleClick = jest.fn();
|
||||
const { getByTestId } = render(
|
||||
<MiddleTruncatedText
|
||||
index={1}
|
||||
text={longString}
|
||||
ariaLabel={longString}
|
||||
url={url}
|
||||
onClick={handleClick}
|
||||
highestIndex={10}
|
||||
/>
|
||||
);
|
||||
const button = getByTestId('middleTruncatedTextButton');
|
||||
const button = getByTestId('middleTruncatedTextButton1');
|
||||
fireEvent.click(button);
|
||||
|
||||
await waitFor(() => {
|
||||
|
|
|
@ -6,12 +6,22 @@
|
|||
*/
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiScreenReaderOnly, EuiToolTip, EuiButtonEmpty, EuiLink } from '@elastic/eui';
|
||||
import {
|
||||
EuiScreenReaderOnly,
|
||||
EuiToolTip,
|
||||
EuiButtonEmpty,
|
||||
EuiLink,
|
||||
EuiText,
|
||||
EuiIcon,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FIXED_AXIS_HEIGHT } from './constants';
|
||||
import { euiStyled } from '../../../../../../../../../src/plugins/kibana_react/common';
|
||||
|
||||
interface Props {
|
||||
index: number;
|
||||
highestIndex: number;
|
||||
ariaLabel: string;
|
||||
text: string;
|
||||
onClick?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
||||
|
@ -19,7 +29,7 @@ interface Props {
|
|||
url: string;
|
||||
}
|
||||
|
||||
const OuterContainer = styled.span`
|
||||
const OuterContainer = euiStyled.span`
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
@ -28,13 +38,21 @@ const OuterContainer = styled.span`
|
|||
}
|
||||
`; // NOTE: min-width: 0 ensures flexbox and no-wrap children can co-exist
|
||||
|
||||
const InnerContainer = styled.span`
|
||||
const InnerContainer = euiStyled.span`
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const FirstChunk = styled.span`
|
||||
const IndexNumber = euiStyled(EuiText)`
|
||||
font-family: ${(props) => props.theme.eui.euiCodeFontFamily};
|
||||
margin-right: ${(props) => props.theme.eui.euiSizeXS};
|
||||
line-height: ${FIXED_AXIS_HEIGHT}px;
|
||||
text-align: right;
|
||||
background-color: ${(props) => props.theme.eui.euiColorLightestShade};
|
||||
`;
|
||||
|
||||
const FirstChunk = euiStyled.span`
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
|
@ -42,13 +60,13 @@ const FirstChunk = styled.span`
|
|||
text-align: left;
|
||||
`; // safari doesn't auto align text left in some cases
|
||||
|
||||
const LastChunk = styled.span`
|
||||
const LastChunk = euiStyled.span`
|
||||
flex-shrink: 0;
|
||||
line-height: ${FIXED_AXIS_HEIGHT}px;
|
||||
text-align: left;
|
||||
`; // safari doesn't auto align text left in some cases
|
||||
|
||||
const StyledButton = styled(EuiButtonEmpty)`
|
||||
const StyledButton = euiStyled(EuiButtonEmpty)`
|
||||
&&& {
|
||||
border: none;
|
||||
|
||||
|
@ -59,6 +77,10 @@ const StyledButton = styled(EuiButtonEmpty)`
|
|||
}
|
||||
`;
|
||||
|
||||
const SecureIcon = euiStyled(EuiIcon)`
|
||||
margin-right: ${(props) => props.theme.eui.euiSizeXS};
|
||||
`;
|
||||
|
||||
export const getChunks = (text: string = '') => {
|
||||
const END_CHARS = 12;
|
||||
const chars = text.split('');
|
||||
|
@ -70,7 +92,18 @@ export const getChunks = (text: string = '') => {
|
|||
// Helper component for adding middle text truncation, e.g.
|
||||
// really-really-really-long....ompressed.js
|
||||
// Can be used to accomodate content in sidebar item rendering.
|
||||
export const MiddleTruncatedText = ({ ariaLabel, text, onClick, setButtonRef, url }: Props) => {
|
||||
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 chunks = useMemo(() => {
|
||||
return getChunks(text);
|
||||
}, [text]);
|
||||
|
@ -78,24 +111,44 @@ export const MiddleTruncatedText = ({ ariaLabel, text, onClick, setButtonRef, ur
|
|||
return (
|
||||
<OuterContainer aria-label={ariaLabel} data-test-subj="middleTruncatedTextContainer">
|
||||
<EuiScreenReaderOnly>
|
||||
<span data-test-subj="middleTruncatedTextSROnly">{text}</span>
|
||||
<span data-test-subj="middleTruncatedTextSROnly">{fullText}</span>
|
||||
</EuiScreenReaderOnly>
|
||||
<EuiToolTip content={text} position="top" data-test-subj="middleTruncatedTextToolTip">
|
||||
<EuiToolTip content={fullText} position="top" data-test-subj="middleTruncatedTextToolTip">
|
||||
<>
|
||||
{onClick ? (
|
||||
<StyledButton
|
||||
onClick={onClick}
|
||||
data-test-subj="middleTruncatedTextButton"
|
||||
data-test-subj={`middleTruncatedTextButton${index}`}
|
||||
buttonRef={setButtonRef}
|
||||
flush={'left'}
|
||||
>
|
||||
<InnerContainer>
|
||||
<IndexNumber
|
||||
color="subdued"
|
||||
size="s"
|
||||
style={{ minWidth: String(highestIndex).length + 1 + 'ch' }}
|
||||
>
|
||||
{index + '.'}
|
||||
</IndexNumber>
|
||||
{secureHttps && (
|
||||
<SecureIcon
|
||||
type="lock"
|
||||
size="s"
|
||||
color="secondary"
|
||||
aria-label={i18n.translate('xpack.uptime.waterfallChart.sidebar.url.https', {
|
||||
defaultMessage: 'https',
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
<FirstChunk>{chunks.first}</FirstChunk>
|
||||
<LastChunk>{chunks.last}</LastChunk>
|
||||
</InnerContainer>
|
||||
</StyledButton>
|
||||
) : (
|
||||
<InnerContainer aria-hidden={true}>
|
||||
<FirstChunk>{chunks.first}</FirstChunk>
|
||||
<FirstChunk>
|
||||
{index}. {chunks.first}
|
||||
</FirstChunk>
|
||||
<LastChunk>{chunks.last}</LastChunk>
|
||||
</InnerContainer>
|
||||
)}
|
||||
|
|
|
@ -90,6 +90,7 @@ export const WaterfallChartSidebarWrapper = euiStyled(EuiFlexItem)`
|
|||
export const WaterfallChartSidebarContainer = euiStyled.div<WaterfallChartSidebarContainer>`
|
||||
height: ${(props) => `${props.height}px`};
|
||||
overflow-y: hidden;
|
||||
overflow-x: hidden;
|
||||
`;
|
||||
|
||||
export const WaterfallChartSidebarContainerInnerPanel: StyledComponent<
|
||||
|
@ -107,8 +108,7 @@ export const WaterfallChartSidebarContainerFlexGroup = euiStyled(EuiFlexGroup)`
|
|||
// Ensures flex items honour no-wrap of children, rather than trying to extend to the full width of children.
|
||||
export const WaterfallChartSidebarFlexItem = euiStyled(EuiFlexItem)`
|
||||
min-width: 0;
|
||||
padding-left: ${(props) => props.theme.eui.paddingSizes.m};
|
||||
padding-right: ${(props) => props.theme.eui.paddingSizes.m};
|
||||
padding-right: ${(props) => props.theme.eui.paddingSizes.s};
|
||||
justify-content: space-around;
|
||||
`;
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue