[Synthetics] Adjust disrupted UI elements on small screens and when flyout is open (#152812)

Fixes #150615
Fixes #147944

## Summary

Takes care of wrapping and overflowing of panels on small screens or
when monitor test run flyout is open in push mode.

The PR addresses the Monitor Overview, Management, Monitor Details and Monitor Add/Edit
pages.
This commit is contained in:
Abdul Wahab Zahid 2023-04-11 13:56:22 +02:00 committed by GitHub
parent b06400373c
commit e29265e51c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 586 additions and 198 deletions

View file

@ -136,8 +136,10 @@ journey(`TestNowMode`, async ({ page, params }) => {
await services.addTestSummaryDocument({ testRunId, docType: 'stepEnd', stepIndex: 1 });
await page.waitForSelector('text=1 step completed');
await page.waitForSelector('text=Go to https://www.google.com');
await page.waitForSelector('text=1.42 s');
await page.waitForSelector(
'.euiTableRowCell--hideForMobile :has-text("Go to https://www.google.com")'
);
await page.waitForSelector('.euiTableRowCell--hideForMobile :has-text("1.42 s")');
await page.waitForSelector('text=Complete');
});
@ -146,7 +148,7 @@ journey(`TestNowMode`, async ({ page, params }) => {
await retry.tryForTime(90 * 1000, async () => {
await page.waitForSelector('text=2 steps completed');
await page.waitForSelector('text="Go to step 2"');
await page.waitForSelector('text=788 ms');
await page.waitForSelector('div:has-text("788 ms")');
await page.waitForSelector('text=IN PROGRESS');
});
});

View file

@ -65,6 +65,6 @@ journey(`TestRunDetailsPage`, async ({ page, params }) => {
await page.waitForSelector('text=Test run details');
await page.waitForSelector('text=Go to https://www.google.com');
await page.waitForSelector('text=After 2.12 s');
await page.waitForSelector('div:has-text("After 2.12 s")');
});
});

View file

@ -38,9 +38,18 @@ export const LocationStatusBadges = ({
const locationsToDisplay = locations.slice(0, toDisplay);
return (
<EuiFlexGroup wrap gutterSize="xs" css={{ maxWidth: 450, overflow: 'hidden' }}>
<EuiFlexGroup
gutterSize="xs"
css={{ maxWidth: 450, overflow: 'hidden' }}
wrap
responsive={false}
>
{locationsToDisplay.map((loc) => (
<EuiFlexItem key={loc.id} grow={false} css={{ overflow: 'hidden' }}>
<EuiFlexItem
key={loc.id}
grow={false}
css={{ overflow: 'hidden', flexBasis: 'fit-content' }}
>
<MonitorDetailLinkForLocation
configId={configId}
locationId={loc.id}

View file

@ -68,6 +68,7 @@ export const MonitorDetailsPanel = ({
return (
<PanelWithTitle
paddingSize="m"
margin="none"
title={MONITOR_DETAILS_LABEL}
titleLeftAlign
hasBorder={hasBorder}

View file

@ -9,8 +9,16 @@ import { EuiPanel, EuiTitle, useEuiTheme, EuiPanelProps } from '@elastic/eui';
import React from 'react';
export const PanelWithTitle: React.FC<
{ title?: string; titleLeftAlign?: boolean } & EuiPanelProps
> = ({ title, hasBorder = true, hasShadow = false, children, titleLeftAlign, ...props }) => {
{ title?: string; titleLeftAlign?: boolean; margin?: string } & EuiPanelProps
> = ({
title,
hasBorder = true,
hasShadow = false,
children,
titleLeftAlign,
margin,
...props
}) => {
const { euiTheme } = useEuiTheme();
return (
@ -19,7 +27,7 @@ export const PanelWithTitle: React.FC<
<EuiTitle size="xs">
<h3
css={{
margin: euiTheme.size.s,
margin: margin ?? euiTheme.size.s,
marginBottom: 0,
...(titleLeftAlign ? { marginLeft: 0 } : {}),
}}

View file

@ -5,11 +5,13 @@
* 2.0.
*/
import { EuiButtonEmpty, EuiButtonIcon } from '@elastic/eui';
import React from 'react';
import { i18n } from '@kbn/i18n';
import { useSelectedLocation } from '../../monitor_details/hooks/use_selected_location';
import { EuiButtonEmpty, EuiButtonIcon } from '@elastic/eui';
import { CommonProps } from '@elastic/eui/src/components/common';
import { useSyntheticsSettingsContext } from '../../../contexts';
import { useSelectedLocation } from '../../monitor_details/hooks/use_selected_location';
export const StepDetailsLinkIcon = ({
stepIndex,
@ -18,7 +20,8 @@ export const StepDetailsLinkIcon = ({
asButton,
label,
target = '_self',
}: {
...commonProps
}: CommonProps & {
checkGroup: string;
label?: string;
configId: string;
@ -29,13 +32,16 @@ export const StepDetailsLinkIcon = ({
const { basePath } = useSyntheticsSettingsContext();
const selectedLocation = useSelectedLocation();
const stepDetailsLink = `${basePath}/app/synthetics/monitor/${configId}/test-run/${checkGroup}/step/${stepIndex}?locationId=${selectedLocation?.id}`;
if (asButton) {
return (
<EuiButtonEmpty
data-test-subj="syntheticsStepDetailsLinkIconButton"
{...commonProps}
flush="left"
iconType="apmTrace"
href={`${basePath}/app/synthetics/monitor/${configId}/test-run/${checkGroup}/step/${stepIndex}?locationId=${selectedLocation?.id}`}
href={stepDetailsLink}
>
{label ?? VIEW_DETAILS}
</EuiButtonEmpty>
@ -44,10 +50,11 @@ export const StepDetailsLinkIcon = ({
return (
<EuiButtonIcon
{...commonProps}
aria-label={VIEW_DETAILS}
title={VIEW_DETAILS}
size="s"
href={`${basePath}/app/synthetics/monitor/${configId}/test-run/${checkGroup}/step/${stepIndex}?locationId=${selectedLocation?.id}`}
href={stepDetailsLink}
target={target}
iconType="apmTrace"
/>

View file

@ -6,7 +6,14 @@
*/
import { i18n } from '@kbn/i18n';
import React, { CSSProperties, ReactElement, useCallback, useEffect, useState } from 'react';
import React, {
CSSProperties,
ReactElement,
PropsWithChildren,
useCallback,
useEffect,
useState,
} from 'react';
import {
EuiBasicTable,
EuiBasicTableColumn,
@ -14,7 +21,10 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiText,
EuiTextProps,
EuiTitle,
useEuiTheme,
useIsWithinMinBreakpoint,
} from '@elastic/eui';
import { EuiThemeComputed } from '@elastic/eui/src/services/theme/types';
@ -22,7 +32,11 @@ import { StepTabs } from '../../test_run_details/step_tabs';
import { ResultDetails } from './result_details';
import { JourneyStep } from '../../../../../../common/runtime_types';
import { JourneyStepScreenshotContainer } from '../screenshot/journey_step_screenshot_container';
import { ScreenshotImageSize, THUMBNAIL_SCREENSHOT_SIZE } from '../screenshot/screenshot_size';
import {
ScreenshotImageSize,
THUMBNAIL_SCREENSHOT_SIZE,
THUMBNAIL_SCREENSHOT_SIZE_MOBILE,
} from '../screenshot/screenshot_size';
import { StepDetailsLinkIcon } from '../links/step_details_link';
import { parseBadgeStatus, getTextColorForMonitorStatus } from './status_badge';
@ -61,6 +75,7 @@ export const BrowserStepsList = ({
const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState<
Record<string, ReactElement>
>({});
const isTabletOrGreater = useIsWithinMinBreakpoint('s');
const toggleDetails = useCallback(
(item: JourneyStep) => {
@ -118,8 +133,13 @@ export const BrowserStepsList = ({
field: 'synthetics.step.index',
name: '#',
render: (stepIndex: number, item: JourneyStep) => (
<StepNumber stepIndex={stepIndex} step={item} euiTheme={euiTheme} />
<StyleForStepStatus step={item} euiTheme={euiTheme}>
{stepIndex}
</StyleForStepStatus>
),
mobileOptions: {
show: false,
},
},
]
: []),
@ -139,15 +159,20 @@ export const BrowserStepsList = ({
/>
),
mobileOptions: {
render: (item: JourneyStep) => (
<EuiText>
<strong>
{item.synthetics?.step?.index!}. {item.synthetics?.step?.name}
</strong>
</EuiText>
render: (step: JourneyStep) => (
<MobileRowDetails
journeyStep={step}
stepsLoading={loading}
showStepNumber={showStepNumber}
showLastSuccessful={showLastSuccessful}
isExpanded={Boolean(itemIdToExpandedRowMap[step._id])}
isTestNowMode={testNowMode}
euiTheme={euiTheme}
/>
),
header: SCREENSHOT_LABEL,
header: false,
enlarge: true,
width: '100%',
},
},
{
@ -166,6 +191,9 @@ export const BrowserStepsList = ({
</EuiText>
);
},
mobileOptions: {
show: false,
},
},
{
field: 'synthetics.step.status',
@ -177,6 +205,9 @@ export const BrowserStepsList = ({
isExpanded={Boolean(itemIdToExpandedRowMap[item._id]) && !testNowMode}
/>
),
mobileOptions: {
show: false,
},
},
...(showLastSuccessful
? [
@ -189,6 +220,9 @@ export const BrowserStepsList = ({
isExpanded={Boolean(itemIdToExpandedRowMap[item._id])}
/>
),
mobileOptions: {
show: false,
},
},
]
: [
@ -208,7 +242,6 @@ export const BrowserStepsList = ({
align: 'right',
field: 'timestamp',
name: '',
mobileOptions: { show: false },
render: (_val: string, item) => (
<StepDetailsLinkIcon
checkGroup={item.monitor.check_group}
@ -217,12 +250,14 @@ export const BrowserStepsList = ({
target={testNowMode ? '_blank' : undefined}
/>
),
mobileOptions: { show: false },
},
];
return (
<>
<EuiBasicTable
css={{ overflowX: isTabletOrGreater ? 'auto' : undefined }}
cellProps={(row) => {
if (itemIdToExpandedRowMap[row._id]) {
return {
@ -234,8 +269,8 @@ export const BrowserStepsList = ({
loading={loading}
columns={columns}
error={error?.message}
isExpandable={true}
hasActions={true}
isExpandable={showExpand}
hasActions={false}
items={stepEnds}
noItemsMessage={
loading
@ -254,15 +289,16 @@ export const BrowserStepsList = ({
);
};
const StepNumber = ({
stepIndex,
const StyleForStepStatus = ({
step,
textSize = 's',
euiTheme,
}: {
stepIndex: number;
children,
}: PropsWithChildren<{
step: JourneyStep;
textSize?: EuiTextProps['size'];
euiTheme: EuiThemeComputed;
}) => {
}>) => {
const status = parseBadgeStatus(step.synthetics?.step?.status ?? '');
return (
@ -270,14 +306,107 @@ const StepNumber = ({
css={{
fontWeight: euiTheme.font.weight.bold,
}}
size="s"
size={textSize}
color={euiTheme.colors[getTextColorForMonitorStatus(status)] as CSSProperties['color']}
>
{stepIndex}
{children}
</EuiText>
);
};
const MobileRowDetails = ({
journeyStep,
showStepNumber,
showLastSuccessful,
stepsLoading,
isExpanded,
isTestNowMode,
euiTheme,
}: {
journeyStep: JourneyStep;
showStepNumber: boolean;
showLastSuccessful: boolean;
stepsLoading: boolean;
isExpanded: boolean;
isTestNowMode: boolean;
euiTheme: EuiThemeComputed;
}) => {
return (
<EuiFlexGroup direction="column" gutterSize="s">
<EuiTitle size="s">
<h4>
<StyleForStepStatus step={journeyStep} textSize="relative" euiTheme={euiTheme}>
{showStepNumber && journeyStep.synthetics?.step?.index
? `${journeyStep.synthetics.step.index}. `
: null}{' '}
{journeyStep.synthetics?.step?.name}
</StyleForStepStatus>
</h4>
</EuiTitle>
<EuiFlexGroup justifyContent="spaceEvenly" responsive={false} wrap={true} gutterSize="xl">
<JourneyStepScreenshotContainer
checkGroup={journeyStep.monitor.check_group}
initialStepNumber={journeyStep.synthetics?.step?.index}
stepStatus={journeyStep.synthetics.payload?.status}
allStepsLoaded={!stepsLoading}
retryFetchOnRevisit={true}
size={THUMBNAIL_SCREENSHOT_SIZE_MOBILE}
timestamp={journeyStep?.['@timestamp']}
/>
<div>
<EuiFlexGroup direction="column" gutterSize="s">
{[
{
title: RESULT_LABEL,
description: (
<ResultDetails
step={journeyStep}
pingStatus={journeyStep?.synthetics?.step?.status ?? 'skipped'}
isExpanded={isExpanded && !isTestNowMode}
/>
),
},
...[
showLastSuccessful
? {
title: LAST_SUCCESSFUL,
description: (
<ResultDetailsSuccessful step={journeyStep} isExpanded={isExpanded} />
),
}
: {
title: STEP_DURATION,
description: <StepDurationText step={journeyStep} />,
},
],
].map(({ title, description }) => (
<EuiFlexGroup
key={title}
css={{ maxWidth: 'fit-content' }}
direction="row"
alignItems="baseline"
gutterSize="xs"
responsive={false}
wrap={true}
>
<EuiText size="xs">{title}</EuiText>
{description}
</EuiFlexGroup>
))}
</EuiFlexGroup>
</div>
</EuiFlexGroup>
<StepDetailsLinkIcon
css={{ marginLeft: 'auto' }}
checkGroup={journeyStep.monitor.check_group}
stepIndex={journeyStep.synthetics?.step?.index}
configId={journeyStep.config_id!}
asButton={true}
/>
</EuiFlexGroup>
);
};
const RESULT_LABEL = i18n.translate('xpack.synthetics.monitor.result.label', {
defaultMessage: 'Result',
});

View file

@ -16,7 +16,7 @@ export const StatusBadge = ({ status }: { status: MonitorStatus }) => {
}
return (
<EuiBadge color={getBadgeColorForMonitorStatus(status)}>
<EuiBadge color={getBadgeColorForMonitorStatus(status)} css={{ maxWidth: 'max-content' }}>
{status === 'succeeded' ? COMPLETE_LABEL : status === 'failed' ? FAILED_LABEL : SKIPPED_LABEL}
</EuiBadge>
);

View file

@ -0,0 +1,31 @@
/*
* 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 { LazyObservabilityPageTemplateProps } from '@kbn/observability-plugin/public';
import React from 'react';
import { euiStyled } from '@kbn/kibana-react-plugin/common';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { ClientPluginsStart } from '../../../../../plugin';
export const WrappedPageTemplate = (props: LazyObservabilityPageTemplateProps) => {
const { observability } = useKibana<ClientPluginsStart>().services;
const PageTemplateComponent = observability.navigation.PageTemplate;
return <PageTemplateComponent {...props} />;
};
export const SyntheticsPageTemplateComponent = euiStyled(WrappedPageTemplate)`
&&& {
.euiPageHeaderContent__top {
flex-wrap: wrap;
.euiTitle {
min-width: 160px;
}
}
}
`;

View file

@ -27,6 +27,7 @@ import {
EuiOutsideClickDetector,
useIsWithinMaxBreakpoint,
} from '@elastic/eui';
import { euiStyled } from '@kbn/kibana-react-plugin/common';
import { SYNTHETICS_API_URLS } from '../../../../../../common/constants';
import { SyntheticsSettingsContext } from '../../../contexts';
@ -118,7 +119,7 @@ export const JourneyScreenshotDialog = ({
}}
onKeyDown={onKeyDown}
>
<EuiModalBody>
<ModalBodyStyled css={{ display: 'flex' }}>
<ScreenshotImage
label={stepCountLabel}
imgSrc={imgSrc}
@ -127,7 +128,7 @@ export const JourneyScreenshotDialog = ({
hasBorder={false}
size={'full'}
/>
</EuiModalBody>
</ModalBodyStyled>
<EuiModalFooter
css={{
@ -149,7 +150,7 @@ export const JourneyScreenshotDialog = ({
{loading ? (
<EuiProgress data-test-subj="screenshotImageLoadingProgress" size="xs" />
) : null}
<EuiFlexGroup alignItems="center" justifyContent="center">
<EuiFlexGroup alignItems="center" justifyContent="center" responsive={false}>
<EuiFlexItem grow={true}>
<EuiButtonEmpty
data-test-subj="screenshotImagePreviousButton"
@ -165,7 +166,7 @@ export const JourneyScreenshotDialog = ({
{prevAriaLabel}
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFlexItem grow={false} css={{ flexBasis: 'fit-content' }}>
<EuiText color={euiTheme.colors.text}>{stepCountLabel}</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={true}>
@ -206,6 +207,17 @@ export const JourneyScreenshotDialog = ({
) : null;
};
const ModalBodyStyled = euiStyled(EuiModalBody)`
&&& {
& > div {
display: flex;
justify-content: center;
align-items: center;
margin-top: 24px;
}
}
`;
export const getScreenshotUrl = ({
basePath,
checkGroup,

View file

@ -16,6 +16,7 @@ export type ScreenshotImageSize =
| 'full';
export const THUMBNAIL_SCREENSHOT_SIZE: ScreenshotImageSize = [96, 64];
export const THUMBNAIL_SCREENSHOT_SIZE_MOBILE: ScreenshotImageSize = [180, 112];
export const POPOVER_SCREENSHOT_SIZE: ScreenshotImageSize = [640, 360];
export function getConfinedScreenshotSize(

View file

@ -41,8 +41,8 @@ export const AdvancedConfig = ({ readOnly }: { readOnly: boolean }) => {
title={<h4>{configGroup.title}</h4>}
fullWidth
key={configGroup.title}
descriptionFlexItemProps={{ style: { minWidth: 200 } }}
fieldFlexItemProps={{ style: { minWidth: 500 } }}
descriptionFlexItemProps={{ style: { minWidth: 208 } }}
fieldFlexItemProps={{ style: { minWidth: 208 } }}
style={{ flexWrap: 'wrap' }}
>
{configGroup.components.map((field) => {

View file

@ -7,12 +7,14 @@
import React from 'react';
import styled from 'styled-components';
import useThrottle from 'react-use/lib/useThrottle';
import { EuiPanel } from '@elastic/eui';
import { euiStyled } from '@kbn/kibana-react-plugin/common';
import { CodeEditor as MonacoCodeEditor } from '@kbn/kibana-react-plugin/public';
import { MonacoEditorLangId } from '../types';
import { useDimensions } from '../../../hooks';
const CodeEditorContainer = styled(EuiPanel)`
padding: 0;
@ -39,29 +41,39 @@ export const CodeEditor = ({
height = '250px',
readOnly,
}: CodeEditorProps) => {
const { elementRef: containerRef, width: containerWidth } = useDimensions<HTMLDivElement>();
const containerWidthThrottled = useThrottle(containerWidth, 500);
return (
<CodeEditorContainer borderRadius="none" hasShadow={false} hasBorder={true}>
<MonacoCodeContainer
id={`${id}-editor`}
aria-label={ariaLabel}
data-test-subj="codeEditorContainer"
<>
<CodeEditorContainer
panelRef={containerRef as React.Ref<HTMLDivElement>}
borderRadius="none"
hasShadow={false}
hasBorder={true}
>
<MonacoCodeEditor
languageId={languageId}
width="100%"
height={height}
value={value}
onChange={onChange}
options={{
renderValidationDecorations: value ? 'on' : 'off',
readOnly,
}}
isCopyable={true}
allowFullScreen={true}
placeholder={placeholder}
/>
</MonacoCodeContainer>
</CodeEditorContainer>
<MonacoCodeContainer
id={`${id}-editor`}
aria-label={ariaLabel}
data-test-subj="codeEditorContainer"
>
<MonacoCodeEditor
languageId={languageId}
width={containerWidthThrottled ?? '100%'}
height={height}
value={value}
onChange={onChange}
options={{
renderValidationDecorations: value ? 'on' : 'off',
readOnly,
}}
isCopyable={true}
allowFullScreen={true}
placeholder={placeholder}
/>
</MonacoCodeContainer>
</CodeEditorContainer>
</>
);
};

View file

@ -10,10 +10,13 @@ import 'jest-canvas-mock';
import React, { useState, useCallback } from 'react';
import userEvent from '@testing-library/user-event';
import { fireEvent, waitFor } from '@testing-library/react';
import { mockGlobals } from '../../../utils/testing';
import { render } from '../../../utils/testing/rtl_helpers';
import { RequestBodyField } from './request_body_field';
import { Mode } from '../types';
mockGlobals();
jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({
htmlIdGenerator: () => () => `id-${Math.random()}`,
}));

View file

@ -7,9 +7,12 @@
import React from 'react';
import userEvent from '@testing-library/user-event';
import { mockGlobals } from '../../../utils/testing';
import { render } from '../../../utils/testing/rtl_helpers';
import { SourceField } from './source_field';
mockGlobals();
jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({
...jest.requireActual('@elastic/eui/lib/services/accessibility/html_id_generator'),
htmlIdGenerator: () => () => `id-${Math.random()}`,

View file

@ -1118,7 +1118,7 @@ export const FIELD = (readOnly?: boolean): FieldMap => ({
{
value: DEFAULT_BROWSER_ADVANCED_FIELDS[ConfigKey.THROTTLING_CONFIG],
inputDisplay: (
<EuiFlexGroup alignItems="baseline" gutterSize="xs">
<EuiFlexGroup alignItems="baseline" gutterSize="xs" responsive={false}>
<EuiFlexItem grow={false}>
<EuiText>
{i18n.translate('xpack.synthetics.monitorConfig.throttling.options.default', {

View file

@ -56,7 +56,7 @@ export const ActionBar = ({ readOnly = false }: { readOnly: boolean }) => {
<Redirect to={MONITORS_ROUTE} />
) : (
<>
<EuiFlexGroup alignItems="center">
<EuiFlexGroup alignItems="center" wrap={true}>
<EuiFlexItem grow={true}>
{isEdit && defaultValues && (
<div>
@ -84,7 +84,7 @@ export const ActionBar = ({ readOnly = false }: { readOnly: boolean }) => {
<EuiFlexItem grow={false}>
<RunTestButton />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFlexItem grow={false} css={{ marginLeft: 'auto' }}>
<NoPermissionsTooltip
canEditSynthetics={canEditSynthetics}
canAddPrivateMonitor={isEdit || canSavePrivateLocation}

View file

@ -6,9 +6,12 @@
*/
import React from 'react';
import { mockGlobals } from '../../utils/testing';
import { render } from '../../utils/testing/rtl_helpers';
import { MonitorAddPage } from './monitor_add_page';
mockGlobals();
jest.mock('@kbn/kibana-react-plugin/public', () => {
const original = jest.requireActual('@kbn/kibana-react-plugin/public');
return {

View file

@ -6,12 +6,15 @@
*/
import React from 'react';
import { mockGlobals } from '../../utils/testing';
import { render } from '../../utils/testing/rtl_helpers';
import { MonitorEditPage } from './monitor_edit_page';
import { ConfigKey } from '../../../../../common/runtime_types';
import * as observabilityPublic from '@kbn/observability-plugin/public';
mockGlobals();
jest.mock('@kbn/observability-plugin/public');
jest.mock('@kbn/kibana-react-plugin/public', () => {

View file

@ -16,10 +16,10 @@ interface Props {
export const Step = ({ description, children }: Props) => {
return (
<EuiFlexGroup gutterSize="s" wrap>
<EuiFlexItem style={{ minWidth: 200 }}>
<EuiFlexItem style={{ minWidth: 208 }}>
<EuiText>{description}</EuiText>
</EuiFlexItem>
<EuiFlexItem style={{ minWidth: 500 }}>{children}</EuiFlexItem>
<EuiFlexItem style={{ minWidth: 208 }}>{children}</EuiFlexItem>
</EuiFlexGroup>
);
};

View file

@ -49,7 +49,7 @@ const MONITOR_DETAILS_STEP = (readOnly: boolean = false): Step => ({
});
const SCRIPT_RECORDER_BTNS = (
<EuiFlexGroup justifyContent="flexStart">
<EuiFlexGroup justifyContent="flexStart" wrap={true}>
<EuiFlexItem grow={false}>
<EuiButton
data-test-subj="syntheticsLaunchSyntheticsRecorderButton"

View file

@ -14,6 +14,7 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiBadge,
useIsWithinMinBreakpoint,
} from '@elastic/eui';
import { useHistory, useParams } from 'react-router-dom';
import moment from 'moment';
@ -55,6 +56,8 @@ export const ErrorsList = ({
return moment(b.state.started_at).valueOf() - moment(a.state.started_at).valueOf();
})?.[0];
const isTabletOrGreater = useIsWithinMinBreakpoint('s');
const columns = [
{
field: 'item.state.started_at',
@ -77,18 +80,21 @@ export const ErrorsList = ({
}
return (
<EuiFlexGroup gutterSize="m" alignItems="center">
<EuiFlexGroup gutterSize="m" alignItems="center" wrap={true}>
<EuiFlexItem grow={false} className="eui-textNoWrap">
{link}
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiBadge iconType="clock" iconSide="right">
<EuiBadge iconType="clock" iconSide="right" css={{ maxWidth: 'max-content' }}>
{ACTIVE_LABEL}
</EuiBadge>
</EuiFlexItem>
</EuiFlexGroup>
);
},
mobileOptions: {
header: false,
},
},
...(isBrowserType
? [
@ -169,6 +175,7 @@ export const ErrorsList = ({
<div>
<EuiSpacer />
<EuiInMemoryTable
css={{ overflowX: isTabletOrGreater ? 'auto' : undefined }}
tableLayout="auto"
tableCaption={ERRORS_LIST_LABEL}
loading={loading}

View file

@ -31,10 +31,10 @@ export const ErrorsTabContent = ({
return (
<>
<EuiFlexGroup gutterSize="m">
<EuiFlexGroup gutterSize="m" wrap={true}>
<EuiFlexItem grow={1}>
<PanelWithTitle title={OVERVIEW_LABEL} titleLeftAlign>
<EuiFlexGroup>
<PanelWithTitle title={OVERVIEW_LABEL} titleLeftAlign css={{ minWidth: 260 }}>
<EuiFlexGroup wrap={true} responsive={false}>
<EuiFlexItem>
{monitorId && (
<MonitorErrorsCount
@ -58,8 +58,8 @@ export const ErrorsTabContent = ({
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="m" />
<EuiFlexGroup gutterSize="m">
<EuiFlexItem grow={2}>
<EuiFlexGroup gutterSize="m" wrap={true}>
<EuiFlexItem grow={2} css={{ minWidth: 260 }}>
<PanelWithTitle title={ERRORS_LABEL}>
<ErrorsList errorStates={errorStates} loading={loading} />
</PanelWithTitle>

View file

@ -55,8 +55,8 @@ export const MonitorHistory = () => {
<SyntheticsDatePicker fullWidth={true} />
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexGroup gutterSize="m">
<EuiFlexItem grow={1}>
<EuiFlexGroup gutterSize="m" wrap={true}>
<EuiFlexItem css={{ flexBasis: '36%' }}>
{/* @ts-expect-error Current @elastic/eui has the wrong types for the ref */}
<EuiPanel hasShadow={false} hasBorder={true} panelRef={statsRef}>
<EuiTitle size="xs">
@ -127,7 +127,7 @@ export const MonitorHistory = () => {
</EuiFlexGrid>
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem grow={2}>
<EuiFlexItem css={{ flexBasis: '60%', minWidth: 260 }}>
<EuiPanel hasShadow={false} hasBorder={true}>
<EuiTitle size="xs">
<h3>{DURATION_TREND_LABEL}</h3>

View file

@ -7,12 +7,12 @@
import React from 'react';
import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiText, EuiTitle } from '@elastic/eui';
import { useHistory } from 'react-router-dom';
import { useSyntheticsSettingsContext } from '../../../contexts';
import { useSelectedLocation } from '../hooks/use_selected_location';
import { ConfigKey } from '../../../../../../common/runtime_types';
import { MONITOR_HISTORY_ROUTE } from '../../../../../../common/constants';
import { stringifyUrlParams } from '../../../utils/url_params';
import { useGetUrlParams } from '../../../hooks';
import { useSelectedMonitor } from '../hooks/use_selected_monitor';
@ -25,9 +25,18 @@ export const MonitorStatusHeader = ({
periodCaption,
showViewHistoryButton,
}: MonitorStatusPanelProps) => {
const history = useHistory();
const params = useGetUrlParams();
const { basePath } = useSyntheticsSettingsContext();
const { monitor } = useSelectedMonitor();
const selectedLocation = useSelectedLocation();
const search = stringifyUrlParams({
locationId: selectedLocation?.id,
dateRangeStart: 'now-24h',
dateRangeEnd: 'now',
});
const viewDetailsUrl = `${basePath}/app/synthetics${MONITOR_HISTORY_ROUTE.replace(
':monitorId',
monitor?.[ConfigKey.CONFIG_ID] ?? ''
)}${search}`;
const isLast24Hours = from === 'now-24h' && to === 'now';
const periodCaptionText = !!periodCaption
@ -43,45 +52,33 @@ export const MonitorStatusHeader = ({
css={{
marginBottom: 0,
}}
wrap={true}
>
<EuiFlexItem grow={false}>
<EuiTitle size="xs">
<h4>{labels.STATUS_LABEL}</h4>
</EuiTitle>
</EuiFlexItem>
{periodCaptionText ? (
<EuiFlexGroup alignItems="center" responsive={false} wrap={false}>
<EuiFlexItem grow={false}>
<EuiText size="xs" color="subdued">
{periodCaptionText}
</EuiText>
<EuiTitle size="xs">
<h4>{labels.STATUS_LABEL}</h4>
</EuiTitle>
</EuiFlexItem>
) : null}
<EuiFlexItem grow={true} />
{periodCaptionText ? (
<EuiFlexItem grow={false}>
<EuiText size="xs" color="subdued">
{periodCaptionText}
</EuiText>
</EuiFlexItem>
) : null}
</EuiFlexGroup>
{showViewHistoryButton ? (
<EuiFlexItem grow={false}>
<EuiButtonEmpty
href={
monitor?.[ConfigKey.CONFIG_ID]
? history.createHref({
pathname: MONITOR_HISTORY_ROUTE.replace(
':monitorId',
monitor[ConfigKey.CONFIG_ID]
),
search: stringifyUrlParams(
{ ...params, dateRangeStart: 'now-24h', dateRangeEnd: 'now' },
true
),
})
: undefined
}
data-test-subj="monitorStatusChartViewHistoryButton"
size="xs"
iconType="list"
>
{labels.VIEW_HISTORY_LABEL}
</EuiButtonEmpty>
</EuiFlexItem>
<EuiButtonEmpty
css={{ marginLeft: 'auto' }}
href={viewDetailsUrl}
data-test-subj="monitorStatusChartViewHistoryButton"
size="xs"
iconType="list"
>
{labels.VIEW_HISTORY_LABEL}
</EuiButtonEmpty>
) : null}
</EuiFlexGroup>
);

View file

@ -60,6 +60,7 @@ export const LastTestRun = () => {
latestPing={latestPing}
loading={loading}
stepsLoading={stepsLoading}
isErrorDetails={false}
/>
);
};
@ -99,9 +100,7 @@ export const LastTestRunComponent = ({
color="danger"
iconType="warning"
>
{isErrorDetails ? (
<></>
) : (
{isErrorDetails ? null : (
<EuiButton
data-test-subj="monitorTestRunViewErrorDetails"
color="danger"
@ -186,21 +185,21 @@ const PanelHeader = ({
return (
<>
<EuiFlexGroup alignItems="center" gutterSize="s">
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false} wrap={true}>
<EuiFlexItem grow={false}>{TitleNode}</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFlexItem grow={false} css={{ flexBasis: 'fit-content' }}>
<StatusBadge
status={parseBadgeStatus(latestPing?.summary?.down! > 0 ? 'fail' : 'success')}
/>
</EuiFlexItem>
<EuiFlexItem grow={true}>
<EuiText size="xs" color={euiTheme.colors.darkShade}>
<EuiText css={{ whiteSpace: 'nowrap' }} size="xs" color={euiTheme.colors.darkShade}>
{lastRunTimestamp}
</EuiText>
</EuiFlexItem>
{isBrowserMonitor ? (
<EuiFlexItem grow={false}>
<EuiFlexItem css={{ marginLeft: 'auto' }} grow={false}>
<EuiButtonEmpty
data-test-subj="monitorSummaryViewLastTestRun"
size="xs"

View file

@ -47,7 +47,7 @@ export const MonitorAlerts = ({
return (
<EuiPanel hasShadow={false} paddingSize="m" hasBorder>
<EuiFlexGroup alignItems="center" gutterSize="m">
<EuiFlexGroup alignItems="center" gutterSize="m" wrap={true}>
<EuiFlexItem grow={false}>
<EuiTitle size="xs">
<h3>
@ -98,7 +98,7 @@ export const MonitorAlerts = ({
<AlertActions monitorId={monitorId} from={from} to={to} />
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexGroup gutterSize="xs">
<EuiFlexGroup gutterSize="xs" wrap={true}>
<EuiFlexItem style={{ width: 80 }} grow={false}>
<ExploratoryViewEmbeddable
dataTestSubj="monitorActiveAlertsCount"
@ -130,7 +130,7 @@ export const MonitorAlerts = ({
]}
/>
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexItem css={{ minWidth: 80 }}>
<ExploratoryViewEmbeddable
sparklineMode
customHeight="100px"
@ -193,7 +193,7 @@ export const MonitorAlerts = ({
]}
/>
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexItem css={{ minWidth: 80 }}>
<ExploratoryViewEmbeddable
sparklineMode
customHeight="100px"

View file

@ -9,6 +9,7 @@ import React from 'react';
import { EuiTitle, EuiPanel, EuiFlexGroup, EuiFlexItem, EuiText, EuiSpacer } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { LoadWhenInView } from '@kbn/observability-plugin/public';
import { useTestFlyoutOpen } from '../../test_now_mode/hooks/use_test_flyout_open';
import { useMonitorDetailsPage } from '../use_monitor_details_page';
import { useMonitorRangeFrom } from '../hooks/use_monitor_range_from';
@ -32,6 +33,7 @@ export const MonitorSummary = () => {
const { from, to } = useMonitorRangeFrom();
const monitorId = useMonitorQueryId();
const isFlyoutOpen = !!useTestFlyoutOpen();
const dateLabel = from === 'now-30d/d' ? LAST_30_DAYS_LABEL : TO_DATE_LABEL;
@ -42,12 +44,12 @@ export const MonitorSummary = () => {
return (
<MonitorPendingWrapper>
<EuiFlexGroup gutterSize="m">
<EuiFlexItem grow={1}>
<EuiFlexGroup gutterSize="m" wrap={true} responsive={false}>
<EuiFlexItem grow={1} css={{ flexBasis: '36%', minWidth: 260 }}>
<MonitorDetailsPanelContainer />
</EuiFlexItem>
<EuiFlexItem grow={2}>
<EuiPanel hasShadow={false} hasBorder paddingSize="m" css={{ height: 120 }}>
<EuiFlexItem grow={1} css={{ flexBasis: '60%' }}>
<EuiPanel hasShadow={false} grow={false} hasBorder paddingSize="m">
<EuiFlexGroup alignItems="center" gutterSize="m">
<EuiFlexItem grow={false}>
<EuiTitle size="xs">
@ -60,39 +62,45 @@ export const MonitorSummary = () => {
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexGroup gutterSize="s">
<EuiFlexItem grow={false}>
<AvailabilityPanel from={from} to={to} id="availabilityPercentageSummary" />
</EuiFlexItem>
<EuiFlexItem>
<AvailabilitySparklines from={from} to={to} id="availabilitySparklineSummary" />
</EuiFlexItem>
<EuiFlexItem grow={false} style={{ marginLeft: 40 }}>
<DurationPanel from={from} to={to} id="durationAvgValueSummary" />
</EuiFlexItem>
<EuiFlexItem>
<DurationSparklines from={from} to={to} id="durationAvgSparklineSummary" />
</EuiFlexItem>
<EuiFlexItem grow={false} style={{ marginLeft: 40 }}>
{monitorId && (
<MonitorErrorsCount
from={from}
to={to}
monitorId={[monitorId]}
id="monitorErrorsCountSummary"
/>
)}
</EuiFlexItem>
<EuiFlexItem>
{monitorId && (
<MonitorErrorSparklines
from={from}
to={to}
monitorId={[monitorId]}
id="monitorErrorsSparklineSummary"
/>
)}
</EuiFlexItem>
<EuiFlexGroup gutterSize="s" wrap={true}>
<EuiFlexGroup gutterSize="s" wrap={false} responsive={false}>
<EuiFlexItem grow={false}>
<AvailabilityPanel from={from} to={to} id="availabilityPercentageSummary" />
</EuiFlexItem>
<EuiFlexItem css={{ minWidth: 100 }}>
<AvailabilitySparklines from={from} to={to} id="availabilitySparklineSummary" />
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexGroup gutterSize="s" wrap={false} responsive={false}>
<EuiFlexItem grow={false} css={{ minWidth: 86 }}>
<DurationPanel from={from} to={to} id="durationAvgValueSummary" />
</EuiFlexItem>
<EuiFlexItem css={{ minWidth: 100 }}>
<DurationSparklines from={from} to={to} id="durationAvgSparklineSummary" />
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexGroup gutterSize="s" wrap={false} responsive={false}>
<EuiFlexItem grow={false}>
{monitorId && (
<MonitorErrorsCount
from={from}
to={to}
monitorId={[monitorId]}
id="monitorErrorsCountSummary"
/>
)}
</EuiFlexItem>
<EuiFlexItem css={{ minWidth: 100 }}>
{monitorId && (
<MonitorErrorSparklines
from={from}
to={to}
monitorId={[monitorId]}
id="monitorErrorsSparklineSummary"
/>
)}
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexGroup>
</EuiPanel>
<EuiSpacer size="m" />
@ -125,11 +133,11 @@ export const MonitorSummary = () => {
showViewHistoryButton={true}
/>
<EuiSpacer size="m" />
<EuiFlexGroup gutterSize="m">
<EuiFlexItem>
<EuiFlexGroup gutterSize="m" wrap={true}>
<EuiFlexItem css={isFlyoutOpen ? { minWidth: 260 } : undefined}>
<LastTestRun />
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexItem css={{ minWidth: 260 }}>
<MonitorAlerts dateLabel={dateLabel} from={from} to={to} />
<EuiSpacer size="m" />
<StepDurationPanel />

View file

@ -9,9 +9,20 @@ import React, { MouseEvent, useMemo, useState } from 'react';
import { useHistory, useParams } from 'react-router-dom';
import { useSelector } from 'react-redux';
import { i18n } from '@kbn/i18n';
import { EuiBasicTable, EuiBasicTableColumn, EuiPanel, EuiText } from '@elastic/eui';
import {
EuiBasicTable,
EuiBasicTableColumn,
EuiButtonEmpty,
EuiFlexGroup,
EuiFlexItem,
EuiPanel,
EuiText,
useIsWithinMinBreakpoint,
} from '@elastic/eui';
import { Criteria } from '@elastic/eui/src/components/basic_table/basic_table';
import { EuiTableSortingType } from '@elastic/eui/src/components/basic_table/table_types';
import { THUMBNAIL_SCREENSHOT_SIZE_MOBILE } from '../../common/screenshot/screenshot_size';
import { getErrorDetailsUrl } from '../monitor_errors/errors_list';
import { TestRunsTableHeader } from './test_runs_table_header';
import { MONITOR_TYPES } from '../../../../../../common/constants';
@ -29,7 +40,7 @@ import { useSelectedMonitor } from '../hooks/use_selected_monitor';
import { useSelectedLocation } from '../hooks/use_selected_location';
import { useMonitorPings } from '../hooks/use_monitor_pings';
import { JourneyLastScreenshot } from '../../common/screenshot/journey_last_screenshot';
import { useSyntheticsRefreshContext } from '../../../contexts';
import { useSyntheticsRefreshContext, useSyntheticsSettingsContext } from '../../../contexts';
type SortableField = 'timestamp' | 'monitor.status' | 'monitor.duration.us';
@ -47,6 +58,7 @@ export const TestRunsTable = ({
showViewHistoryButton = true,
}: TestRunsTableProps) => {
const history = useHistory();
const { basePath } = useSyntheticsSettingsContext();
const { monitorId } = useParams<{ monitorId: string }>();
const [page, setPage] = useState({ index: 0, size: 10 });
@ -71,6 +83,7 @@ export const TestRunsTable = ({
const pingsError = useSelector(selectPingsError);
const { monitor } = useSelectedMonitor();
const selectedLocation = useSelectedLocation();
const isTabletOrGreater = useIsWithinMinBreakpoint('s');
const isBrowserMonitor = monitor?.[ConfigKey.MONITOR_TYPE] === DataStream.BROWSER;
@ -105,6 +118,18 @@ export const TestRunsTable = ({
timestamp={timestamp}
/>
),
mobileOptions: {
header: false,
render: (item) => (
<EuiFlexGroup css={{ width: '100%', height: '100%' }} alignItems="center">
<JourneyLastScreenshot
checkGroupId={item.monitor.check_group}
size={THUMBNAIL_SCREENSHOT_SIZE_MOBILE}
timestamp={item.timestamp}
/>
</EuiFlexGroup>
),
},
},
]
: []) as Array<EuiBasicTableColumn<Ping>>),
@ -117,6 +142,17 @@ export const TestRunsTable = ({
render: (timestamp: string, ping: Ping) => (
<TestDetailsLink isBrowserMonitor={isBrowserMonitor} timestamp={timestamp} ping={ping} />
),
mobileOptions: {
header: false,
render: (item) => (
<MobileRowDetails
ping={item}
isBrowserMonitor={isBrowserMonitor}
basePath={basePath}
locationId={selectedLocation?.id}
/>
),
},
},
{
align: 'left',
@ -125,6 +161,9 @@ export const TestRunsTable = ({
name: RESULT_LABEL,
sortable: true,
render: (status: string) => <StatusBadge status={parseBadgeStatus(status ?? 'skipped')} />,
mobileOptions: {
show: false,
},
},
{
align: 'left',
@ -134,6 +173,9 @@ export const TestRunsTable = ({
render: (errorMessage: string) => (
<EuiText size="s">{errorMessage?.length > 0 ? errorMessage : '-'}</EuiText>
),
mobileOptions: {
show: false,
},
},
{
align: 'right',
@ -142,6 +184,9 @@ export const TestRunsTable = ({
name: DURATION_LABEL,
sortable: true,
render: (durationUs: number) => <EuiText size="s">{formatTestDuration(durationUs)}</EuiText>,
mobileOptions: {
show: false,
},
},
];
@ -150,7 +195,6 @@ export const TestRunsTable = ({
return {};
}
return {
height: '85px',
'data-test-subj': `row-${item.monitor.check_group}`,
onClick: (evt: MouseEvent) => {
const targetElem = evt.target as HTMLElement;
@ -180,6 +224,7 @@ export const TestRunsTable = ({
pings={pings}
/>
<EuiBasicTable
css={{ overflowX: isTabletOrGreater ? 'auto' : undefined }}
compressed={false}
loading={pingsLoading}
columns={columns}
@ -213,6 +258,77 @@ export const TestRunsTable = ({
);
};
export const MobileRowDetails = ({
ping,
isBrowserMonitor,
basePath,
locationId,
}: {
ping: Ping;
isBrowserMonitor: boolean;
basePath: string;
locationId?: string;
}) => {
return (
<EuiFlexGroup direction="column" gutterSize="m">
<TestDetailsLink isBrowserMonitor={isBrowserMonitor} timestamp={ping.timestamp} ping={ping} />
<EuiFlexGroup
justifyContent="spaceBetween"
alignItems="center"
wrap={false}
responsive={false}
>
<EuiFlexItem css={{ flexBasis: 'fit-content' }}>
<StatusBadge status={parseBadgeStatus(ping?.monitor?.status ?? 'skipped')} />
</EuiFlexItem>
<EuiFlexItem css={{ textAlign: 'right' }}>
{ping?.state?.id! &&
ping.config_id &&
locationId &&
parseBadgeStatus(ping?.monitor?.status ?? 'skipped') === 'failed' ? (
<EuiButtonEmpty
data-test-subj="monitorTestRunsListViewErrorDetails"
color="danger"
href={getErrorDetailsUrl({
basePath,
configId: ping.config_id,
locationId,
stateId: ping?.state?.id!,
})}
>
{i18n.translate('xpack.synthetics.monitorDetails.summary.viewErrorDetails', {
defaultMessage: 'View error details',
})}
</EuiButtonEmpty>
) : null}
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexGroup direction="column" gutterSize="s">
{[
{
title: DURATION_LABEL,
description: formatTestDuration(ping?.monitor?.duration?.us),
},
].map(({ title, description }) => (
<EuiFlexGroup
key={title}
css={{ maxWidth: 'fit-content' }}
direction="row"
alignItems="baseline"
gutterSize="xs"
responsive={false}
wrap={true}
>
<EuiText size="xs">{title}</EuiText>
{description}
</EuiFlexGroup>
))}
</EuiFlexGroup>
</EuiFlexGroup>
);
};
export const LAST_10_TEST_RUNS = i18n.translate(
'xpack.synthetics.monitorDetails.summary.lastTenTestRuns',
{

View file

@ -35,7 +35,7 @@ export const MonitorStats = ({
<EuiFlexGroup gutterSize="l">
<EuiPanel
data-test-subj="syntheticsManagementSummaryStats"
css={{ display: 'flex', flexDirection: 'column', gap: euiTheme.size.l, flexGrow: 0 }}
css={{ display: 'flex', flexDirection: 'column', gap: euiTheme.size.l, flexGrow: 1 }}
hasBorder={true}
hasShadow={false}
>
@ -56,7 +56,13 @@ export const MonitorStats = ({
</EuiPanel>
<EuiPanel
css={{ display: 'flex', flexDirection: 'column', gap: euiTheme.size.l }}
css={{
display: 'flex',
flexDirection: 'column',
gap: euiTheme.size.l,
flexGrow: 12,
minWidth: 260,
}}
hasBorder={true}
hasShadow={false}
>

View file

@ -68,7 +68,12 @@ export const OverviewGrid = memo(() => {
return (
<>
<EuiFlexGroup justifyContent="spaceBetween" alignItems="baseline" responsive={false}>
<EuiFlexGroup
justifyContent="spaceBetween"
alignItems="baseline"
responsive={false}
wrap={true}
>
<EuiFlexItem grow={true}>
<OverviewPaginationInfo
page={page}

View file

@ -62,7 +62,10 @@ export function TestResultHeader({
{isCompleted ? (
<EuiFlexGroup alignItems="center">
<EuiFlexItem grow={false}>
<EuiBadge color={summaryDoc?.summary?.down! > 0 ? 'danger' : 'success'}>
<EuiBadge
color={summaryDoc?.summary?.down! > 0 ? 'danger' : 'success'}
css={{ maxWidth: 'max-content' }}
>
{summaryDoc?.summary?.down! > 0 ? FAILED_LABEL : COMPLETED_LABEL}
</EuiBadge>
</EuiFlexItem>

View file

@ -44,17 +44,17 @@ export const StepMetaInfo = ({
const isFailed = step.synthetics.step?.status === 'failed';
return (
<EuiFlexItem grow={false}>
<EuiFlexItem grow={true}>
<EuiTitle size="xxs">
<h3>{STEP_NAME}</h3>
</EuiTitle>
<EuiText size="m">{step?.synthetics.step?.name}</EuiText>
<EuiSpacer size="s" />
<EuiFlexGroup gutterSize="m" alignItems="center">
<EuiFlexGroup gutterSize="m" alignItems="center" wrap={false}>
<EuiFlexItem grow={false}>
<StatusBadge status={parseBadgeStatus(step?.synthetics.step?.status)} />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFlexItem grow={true}>
{AFTER_LABEL}
{formatTestDuration(step?.synthetics.step?.duration.us)}
</EuiFlexItem>

View file

@ -25,8 +25,8 @@ export const StepScreenshotDetails = ({
return (
<EuiPanel hasShadow={false} hasBorder={false} color="subdued">
<EuiFlexGroup>
<EuiFlexItem css={{ alignItems: 'flex-start' }} grow={false}>
<EuiFlexGroup wrap={true}>
<EuiFlexItem css={{ margin: '0 auto' }} grow={false}>
{step ? (
<JourneyStepScreenshotContainer
key={stepIndex}

View file

@ -47,8 +47,8 @@ export const TestRunDetails = () => {
<>
<TestRunErrorInfo journeyDetails={stepsData?.details} hasNoSteps={hasNoSteps} />
{!hasNoSteps && (
<EuiFlexGroup gutterSize="m">
<EuiFlexItem grow={2} style={{ minWidth: 0 }}>
<EuiFlexGroup gutterSize="m" wrap={true}>
<EuiFlexItem css={{ flexBasis: '60%', minWidth: 260 }}>
<EuiPanel hasShadow={false} hasBorder>
<EuiFlexGroup alignItems="center">
<EuiFlexItem grow={true}>
@ -86,7 +86,7 @@ export const TestRunDetails = () => {
<EuiSpacer size="m" />
<TestRunSteps isLoading={stepsLoading} steps={stepsData?.steps ?? []} />
</EuiFlexItem>
<EuiFlexItem grow={1}>
<EuiFlexItem css={{ flexBasis: '36%', minWidth: 'min-content' }}>
<StepDurationPanel legendPosition="bottom" />
<EuiSpacer size="m" />
<MonitorDetailsPanelContainer hideEnabled hideLocations />

View file

@ -21,6 +21,7 @@ import { useInspectorContext } from '@kbn/observability-plugin/public';
import { useSyntheticsPrivileges } from './hooks/use_synthetics_priviliges';
import { ClientPluginsStart } from '../../plugin';
import { getMonitorsRoute } from './components/monitors_page/route_config';
import { SyntheticsPageTemplateComponent } from './components/common/page_template/synthetics_page_template';
import { getMonitorDetailsRoute } from './components/monitor_details/route_config';
import { getStepDetailsRoute } from './components/step_details_page/route_config';
import { getTestRunDetailsRoute } from './components/test_run_details/route_config';
@ -54,13 +55,6 @@ const baseTitle = i18n.translate('xpack.synthetics.routes.baseTitle', {
defaultMessage: 'Synthetics - Kibana',
});
export const MONITOR_MANAGEMENT_LABEL = i18n.translate(
'xpack.synthetics.monitorManagement.heading',
{
defaultMessage: 'Monitor Management',
}
);
const getRoutes = (
euiTheme: EuiThemeComputed,
history: ReturnType<typeof useHistory>,
@ -177,7 +171,7 @@ const RouteInit: React.FC<Pick<RouteProps, 'path' | 'title'>> = ({ path, title }
};
export const PageRouter: FC = () => {
const { application, observability } = useKibana<ClientPluginsStart>().services;
const { application } = useKibana<ClientPluginsStart>().services;
const { addInspectorRequest } = useInspectorContext();
const { euiTheme } = useEuiTheme();
const history = useHistory();
@ -189,7 +183,6 @@ export const PageRouter: FC = () => {
location,
application.getUrlForApp(PLUGIN.SYNTHETICS_PLUGIN_ID)
);
const PageTemplateComponent = observability.navigation.PageTemplate;
apiService.addInspectorRequest = addInspectorRequest;
@ -209,21 +202,21 @@ export const PageRouter: FC = () => {
<Route path={path} key={dataTestSubj} exact={true}>
<div className={APP_WRAPPER_CLASS} data-test-subj={dataTestSubj}>
<RouteInit title={title} path={path} />
<PageTemplateComponent
<SyntheticsPageTemplateComponent
pageHeader={isUnPrivileged ? undefined : pageHeader}
data-test-subj={'synthetics-page-template'}
isPageDataLoaded={true}
{...pageTemplateProps}
>
{isUnPrivileged || <RouteComponent />}
</PageTemplateComponent>
</SyntheticsPageTemplateComponent>
</div>
</Route>
)
)}
<Route
component={() => (
<PageTemplateComponent>
<SyntheticsPageTemplateComponent>
<NotFoundPrompt
actions={[
<EuiButtonEmpty
@ -240,7 +233,7 @@ export const PageRouter: FC = () => {
</EuiButtonEmpty>,
]}
/>
</PageTemplateComponent>
</SyntheticsPageTemplateComponent>
)}
/>
</Switch>

View file

@ -0,0 +1,20 @@
/*
* 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.
*/
export class MockResizeObserver implements ResizeObserver {
private elements: Set<Element> = new Set();
observe(target: Element) {
this.elements.add(target);
}
unobserve(target: Element) {
this.elements.delete(target);
}
disconnect() {
this.elements.clear();
}
}

View file

@ -5,4 +5,5 @@
* 2.0.
*/
export * from './mock_globals';
export * from './rtl_helpers';

View file

@ -0,0 +1,12 @@
/*
* 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 { MockResizeObserver } from './__mocks__/resize_observer.mock';
export function mockGlobals() {
global.ResizeObserver = MockResizeObserver;
}

View file

@ -35346,7 +35346,6 @@
"xpack.synthetics.monitorManagement.getAPIKeyReducedPermissions.description": "Utilisez une clé dAPI pour transmettre des moniteurs à distance à partir d'un pipeline CLI ou CD. Pour générer une clé dAPI, vous devez disposer des autorisations de gérer les clés dAPI et dun accès en écriture à Uptime. Veuillez contacter votre administrateur.",
"xpack.synthetics.monitorManagement.getProjectApiKey.label": "Générer une clé d'API de projet",
"xpack.synthetics.monitorManagement.getProjectAPIKeyLabel.generate": "Générer une clé d'API de projet",
"xpack.synthetics.monitorManagement.heading": "Gestion des moniteurs",
"xpack.synthetics.monitorManagement.hostFieldLabel": "Hôte",
"xpack.synthetics.monitorManagement.inProgress": "EN COURS",
"xpack.synthetics.monitorManagement.invalidLabel": "Non valide",

View file

@ -35325,7 +35325,6 @@
"xpack.synthetics.monitorManagement.getAPIKeyReducedPermissions.description": "APIキーを使用して、CLIまたはCDパイプラインからリモートでモニターをプッシュします。APIキーを生成するには、APIキーを管理する権限とアップタイム書き込み権限が必要です。管理者にお問い合わせください。",
"xpack.synthetics.monitorManagement.getProjectApiKey.label": "プロジェクトAPIキーを生成",
"xpack.synthetics.monitorManagement.getProjectAPIKeyLabel.generate": "プロジェクトAPIキーを生成",
"xpack.synthetics.monitorManagement.heading": "モニター管理",
"xpack.synthetics.monitorManagement.hostFieldLabel": "ホスト",
"xpack.synthetics.monitorManagement.inProgress": "進行中",
"xpack.synthetics.monitorManagement.invalidLabel": "無効",

View file

@ -35340,7 +35340,6 @@
"xpack.synthetics.monitorManagement.getAPIKeyReducedPermissions.description": "使用 API 密钥从 CLI 或 CD 管道远程推送监测。要生成 API 密钥,您必须有权管理 API 密钥并具有 Uptime 写入权限。请联系您的管理员。",
"xpack.synthetics.monitorManagement.getProjectApiKey.label": "生成项目 API 密钥",
"xpack.synthetics.monitorManagement.getProjectAPIKeyLabel.generate": "生成项目 API 密钥",
"xpack.synthetics.monitorManagement.heading": "监测管理",
"xpack.synthetics.monitorManagement.hostFieldLabel": "主机",
"xpack.synthetics.monitorManagement.inProgress": "进行中",
"xpack.synthetics.monitorManagement.invalidLabel": "无效",