mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
(cherry picked from commit 91cea8afec
)
Co-authored-by: Lucas F. da Costa <lucas@lucasfcosta.com>
This commit is contained in:
parent
6589c97aea
commit
44391b7aa4
8 changed files with 143 additions and 20 deletions
|
@ -34,6 +34,10 @@ export const UptimePageTemplateComponent: React.FC<Props> = ({ path, pageHeader,
|
|||
.euiPageHeaderContent > .euiFlexGroup {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.euiPageHeaderContent > .euiFlexGroup > .euiFlexItem {
|
||||
align-items: center;
|
||||
}
|
||||
`;
|
||||
}, [PageTemplateComponent]);
|
||||
|
||||
|
|
|
@ -16,14 +16,14 @@ interface Props {
|
|||
|
||||
export const StepImage = ({ step }: Props) => {
|
||||
return (
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s">
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s" wrap>
|
||||
<EuiFlexItem grow={false}>
|
||||
<PingTimestamp
|
||||
checkGroup={step.monitor.check_group}
|
||||
initialStepNo={step.synthetics?.step?.index}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexItem grow={false} style={{ minWidth: 80 }}>
|
||||
<EuiText>{step.synthetics?.step?.name}</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -8,7 +8,8 @@
|
|||
import React from 'react';
|
||||
import { JourneyStep } from '../../../../common/runtime_types/ping';
|
||||
import { StepsList } from './steps_list';
|
||||
import { render } from '../../../lib/helper/rtl_helpers';
|
||||
import { render, forDesktopOnly, forMobileOnly } from '../../../lib/helper/rtl_helpers';
|
||||
import { VIEW_PERFORMANCE } from '../../monitor/synthetics/translations';
|
||||
|
||||
describe('StepList component', () => {
|
||||
let steps: JourneyStep[];
|
||||
|
@ -80,7 +81,7 @@ describe('StepList component', () => {
|
|||
it('renders a link to the step detail view', () => {
|
||||
const { getByTitle, getByTestId } = render(<StepsList data={[steps[0]]} loading={false} />);
|
||||
expect(getByTestId('step-detail-link')).toHaveAttribute('href', '/journey/fake-group/step/1');
|
||||
expect(getByTitle(`Failed`));
|
||||
expect(forDesktopOnly(getByTitle, 'title')(`Failed`));
|
||||
});
|
||||
|
||||
it.each([
|
||||
|
@ -91,7 +92,7 @@ describe('StepList component', () => {
|
|||
const step = steps[0];
|
||||
step.synthetics!.payload!.status = status;
|
||||
const { getByText } = render(<StepsList data={[step]} loading={false} />);
|
||||
expect(getByText(expectedStatus));
|
||||
expect(forDesktopOnly(getByText)(expectedStatus));
|
||||
});
|
||||
|
||||
it('creates expected message for all succeeded', () => {
|
||||
|
@ -149,4 +150,31 @@ describe('StepList component', () => {
|
|||
expect(getByTestId('row-fake-group'));
|
||||
expect(getByTestId('row-fake-group-1'));
|
||||
});
|
||||
|
||||
describe('Mobile Designs', () => {
|
||||
// We don't need to resize the window here because EUI
|
||||
// does all the manipulation of what is displayed through
|
||||
// CSS. Therefore, it's enough to check what's actually
|
||||
// rendered and its classes.
|
||||
|
||||
it('renders the step name and index', () => {
|
||||
const { getByText } = render(<StepsList data={steps} loading={false} />);
|
||||
expect(forMobileOnly(getByText)('1. load page')).toBeInTheDocument();
|
||||
expect(forMobileOnly(getByText)('2. go to login')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not render the link to view step details', async () => {
|
||||
const { queryByText } = render(<StepsList data={steps} loading={false} />);
|
||||
expect(forMobileOnly(queryByText)(VIEW_PERFORMANCE)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the status label', () => {
|
||||
steps[0].synthetics!.payload!.status = 'succeeded';
|
||||
steps[1].synthetics!.payload!.status = 'skipped';
|
||||
|
||||
const { getByText } = render(<StepsList data={steps} loading={false} />);
|
||||
expect(forMobileOnly(getByText)('Succeeded')).toBeInTheDocument();
|
||||
expect(forMobileOnly(getByText)('Skipped')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,7 +5,15 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiBasicTable, EuiBasicTableColumn, EuiButtonIcon, EuiTitle } from '@elastic/eui';
|
||||
import {
|
||||
EuiBasicTable,
|
||||
EuiBasicTableColumn,
|
||||
EuiButtonIcon,
|
||||
EuiTitle,
|
||||
EuiFlexItem,
|
||||
EuiText,
|
||||
RIGHT_ALIGNMENT,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { MouseEvent, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
@ -89,12 +97,37 @@ export const StepsList = ({ data, error, loading }: Props) => {
|
|||
render: (pingStatus: string, item) => (
|
||||
<StatusBadge status={pingStatus} stepNo={item.synthetics?.step?.index!} />
|
||||
),
|
||||
mobileOptions: {
|
||||
render: (item) => (
|
||||
<EuiFlexItem grow={false}>
|
||||
<StatusBadge
|
||||
isMobile={true}
|
||||
status={item.synthetics?.payload?.status}
|
||||
stepNo={item.synthetics?.step?.index!}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
),
|
||||
width: '20%',
|
||||
header: STATUS_LABEL,
|
||||
enlarge: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
field: 'timestamp',
|
||||
name: STEP_NAME_LABEL,
|
||||
render: (_timestamp: string, item) => <StepImage step={item} />,
|
||||
mobileOptions: {
|
||||
render: (item: JourneyStep) => (
|
||||
<EuiText>
|
||||
<strong>
|
||||
{item.synthetics?.step?.index!}. {item.synthetics?.step?.name}
|
||||
</strong>
|
||||
</EuiText>
|
||||
),
|
||||
header: 'Step',
|
||||
enlarge: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Step duration',
|
||||
|
@ -107,6 +140,12 @@ export const StepsList = ({ data, error, loading }: Props) => {
|
|||
/>
|
||||
);
|
||||
},
|
||||
mobileOptions: {
|
||||
header: i18n.translate('xpack.uptime.pingList.stepDurationHeader', {
|
||||
defaultMessage: 'Step duration',
|
||||
}),
|
||||
show: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
|
@ -120,11 +159,12 @@ export const StepsList = ({ data, error, loading }: Props) => {
|
|||
{VIEW_PERFORMANCE}
|
||||
</StepDetailLink>
|
||||
),
|
||||
mobileOptions: { show: false },
|
||||
},
|
||||
|
||||
{
|
||||
align: 'right',
|
||||
width: '24px',
|
||||
width: '40px',
|
||||
align: RIGHT_ALIGNMENT,
|
||||
isExpander: true,
|
||||
render: (journeyStep: JourneyStep) => {
|
||||
return (
|
||||
|
@ -143,7 +183,6 @@ export const StepsList = ({ data, error, loading }: Props) => {
|
|||
const { monitor } = item;
|
||||
|
||||
return {
|
||||
height: '85px',
|
||||
'data-test-subj': `row-${monitor.check_group}`,
|
||||
onClick: (evt: MouseEvent) => {
|
||||
const targetElem = evt.target as HTMLElement;
|
||||
|
|
|
@ -30,4 +30,10 @@ describe('StatusBadge', () => {
|
|||
expect(getByText('3.'));
|
||||
expect(getByText('Skipped'));
|
||||
});
|
||||
|
||||
it('hides the step number on mobile', () => {
|
||||
const { queryByText } = render(<StatusBadge status="skipped" stepNo={3} isMobile />);
|
||||
expect(queryByText('3.')).not.toBeInTheDocument();
|
||||
expect(queryByText('Skipped')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -12,6 +12,7 @@ import { UptimeAppColors } from '../../apps/uptime_app';
|
|||
import { UptimeThemeContext } from '../../contexts';
|
||||
|
||||
interface StatusBadgeProps {
|
||||
isMobile?: boolean;
|
||||
status?: string;
|
||||
stepNo: number;
|
||||
}
|
||||
|
@ -46,15 +47,17 @@ export function textFromStatus(status?: string) {
|
|||
}
|
||||
}
|
||||
|
||||
export const StatusBadge: FC<StatusBadgeProps> = ({ status, stepNo }) => {
|
||||
export const StatusBadge: FC<StatusBadgeProps> = ({ status, stepNo, isMobile }) => {
|
||||
const theme = useContext(UptimeThemeContext);
|
||||
return (
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText>
|
||||
<strong>{stepNo}.</strong>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
{!isMobile && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText>
|
||||
<strong>{stepNo}.</strong>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBadge color={colorFromStatus(theme.colors, status)}>{textFromStatus(status)}</EuiBadge>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -256,3 +256,38 @@ export const makeUptimePermissionsCore = (
|
|||
},
|
||||
};
|
||||
};
|
||||
|
||||
// This function filters out the queried elements which appear only
|
||||
// either on mobile or desktop.
|
||||
//
|
||||
// It does so by filtering those with the class passed as the `classWrapper`.
|
||||
// For mobile, we filter classes which tell elements to be hidden on desktop.
|
||||
// For desktop, we do the opposite.
|
||||
//
|
||||
// We have this function because EUI will manipulate the visibility of some
|
||||
// elements through pure CSS, which we can't assert on tests. Therefore,
|
||||
// we look for the corresponding class wrapper.
|
||||
const finderWithClassWrapper =
|
||||
(classWrapper: string) =>
|
||||
(
|
||||
getterFn: (f: MatcherFunction) => HTMLElement | null,
|
||||
customAttribute?: keyof Element | keyof HTMLElement
|
||||
) =>
|
||||
(text: string): HTMLElement | null =>
|
||||
getterFn((_content: string, node: Nullish<Element>) => {
|
||||
if (!node) return false;
|
||||
// There are actually properties that are not in Element but which
|
||||
// appear on the `node`, so we must cast the customAttribute as a keyof Element
|
||||
const content = node[(customAttribute as keyof Element) ?? 'innerHTML'];
|
||||
if (content === text && wrappedInClass(node, classWrapper)) return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
const wrappedInClass = (element: HTMLElement | Element, classWrapper: string): boolean => {
|
||||
if (element.className.includes(classWrapper)) return true;
|
||||
if (element.parentElement) return wrappedInClass(element.parentElement, classWrapper);
|
||||
return false;
|
||||
};
|
||||
|
||||
export const forMobileOnly = finderWithClassWrapper('hideForDesktop');
|
||||
export const forDesktopOnly = finderWithClassWrapper('hideForMobile');
|
||||
|
|
|
@ -12,6 +12,7 @@ import { useHistory } from 'react-router-dom';
|
|||
import moment from 'moment';
|
||||
import { SyntheticsJourneyApiResponse } from '../../../common/runtime_types/ping';
|
||||
import { getShortTimeStamp } from '../../components/overview/monitor_list/columns/monitor_status_column';
|
||||
import { useBreakpoints } from '../../../public/hooks/use_breakpoints';
|
||||
|
||||
interface Props {
|
||||
timestamp: string;
|
||||
|
@ -20,11 +21,15 @@ interface Props {
|
|||
|
||||
export const ChecksNavigation = ({ timestamp, details }: Props) => {
|
||||
const history = useHistory();
|
||||
const { down } = useBreakpoints();
|
||||
|
||||
const isMobile = down('s');
|
||||
|
||||
return (
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup alignItems="center" responsive={false} gutterSize="none">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
size={isMobile ? 'xs' : 'm'}
|
||||
iconType="arrowLeft"
|
||||
isDisabled={!details?.previous}
|
||||
onClick={() => {
|
||||
|
@ -37,11 +42,14 @@ export const ChecksNavigation = ({ timestamp, details }: Props) => {
|
|||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiText className="eui-textNoWrap">{getShortTimeStamp(moment(timestamp))}</EuiText>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size={isMobile ? 'xs' : 'm'} className="eui-textNoWrap">
|
||||
{getShortTimeStamp(moment(timestamp))}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
size={isMobile ? 'xs' : 'm'}
|
||||
iconType="arrowRight"
|
||||
iconSide="right"
|
||||
isDisabled={!details?.next}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue