diff --git a/x-pack/plugins/uptime/public/components/fleet_package/browser/advanced_fields.tsx b/x-pack/plugins/uptime/public/components/fleet_package/browser/advanced_fields.tsx index cf72c7562d39..f838474f5219 100644 --- a/x-pack/plugins/uptime/public/components/fleet_package/browser/advanced_fields.tsx +++ b/x-pack/plugins/uptime/public/components/fleet_package/browser/advanced_fields.tsx @@ -13,10 +13,10 @@ import { EuiFieldText, EuiCheckbox, EuiFormRow, - EuiDescribedFormGroup, EuiSpacer, } from '@elastic/eui'; import { ComboBox } from '../combo_box'; +import { DescribedFormGroupWithWrap } from '../common/described_form_group_with_wrap'; import { useBrowserAdvancedFieldsContext, useBrowserSimpleFieldsContext } from '../contexts'; @@ -28,9 +28,10 @@ import { ThrottlingFields } from './throttling_fields'; interface Props { validate: Validation; children?: React.ReactNode; + minColumnWidth?: string; } -export const BrowserAdvancedFields = memo(({ validate, children }) => { +export const BrowserAdvancedFields = memo(({ validate, children, minColumnWidth }) => { const { fields, setFields } = useBrowserAdvancedFieldsContext(); const { fields: simpleFields } = useBrowserSimpleFieldsContext(); @@ -49,7 +50,8 @@ export const BrowserAdvancedFields = memo(({ validate, children }) => { > {simpleFields[ConfigKey.SOURCE_ZIP_URL] && ( - (({ validate, children }) => { data-test-subj="syntheticsBrowserJourneyFiltersTags" /> - + )} - (({ validate, children }) => { data-test-subj="syntheticsBrowserSyntheticsArgs" /> - + - + {children} ); diff --git a/x-pack/plugins/uptime/public/components/fleet_package/browser/throttling_fields.tsx b/x-pack/plugins/uptime/public/components/fleet_package/browser/throttling_fields.tsx index 6d52ef755d0a..d5ec96ebb5a6 100644 --- a/x-pack/plugins/uptime/public/components/fleet_package/browser/throttling_fields.tsx +++ b/x-pack/plugins/uptime/public/components/fleet_package/browser/throttling_fields.tsx @@ -7,14 +7,8 @@ import React, { memo, useCallback } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; -import { - EuiDescribedFormGroup, - EuiSwitch, - EuiSpacer, - EuiFormRow, - EuiFieldNumber, - EuiText, -} from '@elastic/eui'; +import { EuiSwitch, EuiSpacer, EuiFormRow, EuiFieldNumber, EuiText } from '@elastic/eui'; +import { DescribedFormGroupWithWrap } from '../common/described_form_group_with_wrap'; import { OptionalLabel } from '../optional_label'; import { useBrowserAdvancedFieldsContext } from '../contexts'; @@ -22,6 +16,7 @@ import { Validation, ConfigKey } from '../types'; interface Props { validate: Validation; + minColumnWidth?: string; } type ThrottlingConfigs = @@ -30,7 +25,7 @@ type ThrottlingConfigs = | ConfigKey.UPLOAD_SPEED | ConfigKey.LATENCY; -export const ThrottlingFields = memo(({ validate }) => { +export const ThrottlingFields = memo(({ validate, minColumnWidth }) => { const { fields, setFields } = useBrowserAdvancedFieldsContext(); const handleInputChange = useCallback( @@ -148,7 +143,8 @@ export const ThrottlingFields = memo(({ validate }) => { ) : null; return ( - (({ validate }) => { } /> {throttlingInputs} - + ); }); diff --git a/x-pack/plugins/uptime/public/components/fleet_package/code_editor.tsx b/x-pack/plugins/uptime/public/components/fleet_package/code_editor.tsx index 3f80b5f9f365..ee3ede16582f 100644 --- a/x-pack/plugins/uptime/public/components/fleet_package/code_editor.tsx +++ b/x-pack/plugins/uptime/public/components/fleet_package/code_editor.tsx @@ -9,6 +9,7 @@ import React from 'react'; import styled from 'styled-components'; import { EuiPanel } from '@elastic/eui'; +import { euiStyled } from '../../../../../../src/plugins/kibana_react/common'; import { CodeEditor as MonacoCodeEditor } from '../../../../../../src/plugins/kibana_react/public'; import { MonacoEditorLangId } from './types'; @@ -28,7 +29,11 @@ interface Props { export const CodeEditor = ({ ariaLabel, id, languageId, onChange, value }: Props) => { return ( -
+ -
+
); }; + +const MonacoCodeContainer = euiStyled.div` + & > .kibanaCodeEditor { + z-index: 0; + } +`; diff --git a/x-pack/plugins/uptime/public/components/fleet_package/common/described_form_group_with_wrap.tsx b/x-pack/plugins/uptime/public/components/fleet_package/common/described_form_group_with_wrap.tsx new file mode 100644 index 000000000000..5668b6f1121c --- /dev/null +++ b/x-pack/plugins/uptime/public/components/fleet_package/common/described_form_group_with_wrap.tsx @@ -0,0 +1,23 @@ +/* + * 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 { EuiDescribedFormGroup } from '@elastic/eui'; +import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common'; + +/** + * EuiForm group doesn't expose props to control the flex wrapping on flex groups defining form rows. + * This override allows to define a minimum column width to which the Described Form's flex rows should wrap. + */ +export const DescribedFormGroupWithWrap = euiStyled(EuiDescribedFormGroup)<{ + minColumnWidth?: string; +}>` + > .euiFlexGroup { + ${({ minColumnWidth }) => (minColumnWidth ? `flex-wrap: wrap;` : '')} + > .euiFlexItem { + ${({ minColumnWidth }) => (minColumnWidth ? `min-width: ${minColumnWidth};` : '')} + } + } +`; diff --git a/x-pack/plugins/uptime/public/components/fleet_package/custom_fields.tsx b/x-pack/plugins/uptime/public/components/fleet_package/custom_fields.tsx index 10aa01ba9361..638f91fb32d2 100644 --- a/x-pack/plugins/uptime/public/components/fleet_package/custom_fields.tsx +++ b/x-pack/plugins/uptime/public/components/fleet_package/custom_fields.tsx @@ -14,11 +14,11 @@ import { EuiFormRow, EuiSelect, EuiSpacer, - EuiDescribedFormGroup, EuiSwitch, EuiCallOut, EuiLink, } from '@elastic/eui'; +import { DescribedFormGroupWithWrap } from './common/described_form_group_with_wrap'; import { ConfigKey, DataStream, Validation } from './types'; import { usePolicyConfigContext } from './contexts'; import { TLSFields } from './tls_fields'; @@ -36,6 +36,7 @@ interface Props { dataStreams?: DataStream[]; children?: React.ReactNode; appendAdvancedFields?: React.ReactNode; + minColumnWidth?: string; } const dataStreamToString = [ @@ -54,7 +55,7 @@ const dataStreamToString = [ ]; export const CustomFields = memo( - ({ validate, dataStreams = [], children, appendAdvancedFields }) => { + ({ validate, dataStreams = [], children, appendAdvancedFields, minColumnWidth }) => { const { monitorType, setMonitorType, isTLSEnabled, setIsTLSEnabled, isEditable } = usePolicyConfigContext(); @@ -86,7 +87,8 @@ export const CustomFields = memo( return ( - ( {renderSimpleFields(monitorType)} - + {(isHTTP || isTCP) && ( - ( onChange={(event) => setIsTLSEnabled(event.target.checked)} /> - + )} {isHTTP && ( - {appendAdvancedFields} + + {appendAdvancedFields} + + )} + {isTCP && ( + + {appendAdvancedFields} + )} - {isTCP && {appendAdvancedFields}} {isBrowser && ( - {appendAdvancedFields} + + {appendAdvancedFields} + )} {isICMP && {appendAdvancedFields}} diff --git a/x-pack/plugins/uptime/public/components/fleet_package/http/advanced_fields.tsx b/x-pack/plugins/uptime/public/components/fleet_package/http/advanced_fields.tsx index 35c6eb6ffa9e..e4dd68f50f52 100644 --- a/x-pack/plugins/uptime/public/components/fleet_package/http/advanced_fields.tsx +++ b/x-pack/plugins/uptime/public/components/fleet_package/http/advanced_fields.tsx @@ -14,11 +14,11 @@ import { EuiFieldText, EuiFormRow, EuiSelect, - EuiDescribedFormGroup, EuiCheckbox, EuiSpacer, EuiFieldPassword, } from '@elastic/eui'; +import { DescribedFormGroupWithWrap } from '../common/described_form_group_with_wrap'; import { useHTTPAdvancedFieldsContext } from '../contexts'; @@ -33,9 +33,10 @@ import { ComboBox } from '../combo_box'; interface Props { validate: Validation; children?: React.ReactNode; + minColumnWidth?: string; } -export const HTTPAdvancedFields = memo(({ validate, children }) => { +export const HTTPAdvancedFields = memo(({ validate, children, minColumnWidth }) => { const { fields, setFields } = useHTTPAdvancedFieldsContext(); const handleInputChange = useCallback( ({ value, configKey }: { value: unknown; configKey: ConfigKey }) => { @@ -56,7 +57,8 @@ export const HTTPAdvancedFields = memo(({ validate, children }) => { data-test-subj="syntheticsHTTPAdvancedFieldsAccordion" > - (({ validate, children }) => { )} /> - + - (({ validate, children }) => { )} /> - - + (({ validate, children }) => { data-test-subj="syntheticsResponseBodyCheckNegative" /> - + {children} ); diff --git a/x-pack/plugins/uptime/public/components/fleet_package/tcp/advanced_fields.tsx b/x-pack/plugins/uptime/public/components/fleet_package/tcp/advanced_fields.tsx index 46e1a739c57c..ab185b34085b 100644 --- a/x-pack/plugins/uptime/public/components/fleet_package/tcp/advanced_fields.tsx +++ b/x-pack/plugins/uptime/public/components/fleet_package/tcp/advanced_fields.tsx @@ -7,14 +7,8 @@ import React, { memo, useCallback } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; -import { - EuiAccordion, - EuiCheckbox, - EuiFormRow, - EuiDescribedFormGroup, - EuiFieldText, - EuiSpacer, -} from '@elastic/eui'; +import { EuiAccordion, EuiCheckbox, EuiFormRow, EuiFieldText, EuiSpacer } from '@elastic/eui'; +import { DescribedFormGroupWithWrap } from '../common/described_form_group_with_wrap'; import { useTCPAdvancedFieldsContext } from '../contexts'; @@ -24,9 +18,10 @@ import { OptionalLabel } from '../optional_label'; interface Props { children?: React.ReactNode; + minColumnWidth?: string; } -export const TCPAdvancedFields = memo(({ children }) => { +export const TCPAdvancedFields = memo(({ children, minColumnWidth }) => { const { fields, setFields } = useTCPAdvancedFieldsContext(); const handleInputChange = useCallback( @@ -43,7 +38,8 @@ export const TCPAdvancedFields = memo(({ children }) => { data-test-subj="syntheticsTCPAdvancedFieldsAccordion" > - (({ children }) => { data-test-subj="syntheticsTCPRequestSendCheck" /> - - + (({ children }) => { data-test-subj="syntheticsTCPResponseReceiveCheck" /> - + {children} ); diff --git a/x-pack/plugins/uptime/public/components/monitor_management/action_bar/action_bar.test.tsx b/x-pack/plugins/uptime/public/components/monitor_management/action_bar/action_bar.test.tsx index adc2a0a8ed34..64b7984b00b4 100644 --- a/x-pack/plugins/uptime/public/components/monitor_management/action_bar/action_bar.test.tsx +++ b/x-pack/plugins/uptime/public/components/monitor_management/action_bar/action_bar.test.tsx @@ -35,7 +35,7 @@ describe('', () => { }); it('only calls setMonitor when valid and after submission', () => { - render(); + render(); act(() => { userEvent.click(screen.getByText('Save monitor')); @@ -45,7 +45,7 @@ describe('', () => { }); it('does not call setMonitor until submission', () => { - render(); + render(); expect(setMonitor).not.toBeCalled(); @@ -57,7 +57,7 @@ describe('', () => { }); it('does not call setMonitor if invalid', () => { - render(); + render(); expect(setMonitor).not.toBeCalled(); @@ -69,7 +69,7 @@ describe('', () => { }); it('disables button and displays help text when form is invalid after first submission', async () => { - render(); + render(); expect( screen.queryByText('Your monitor has errors. Please fix them before saving.') @@ -90,7 +90,9 @@ describe('', () => { it('calls option onSave when saving monitor', () => { const onSave = jest.fn(); - render(); + render( + + ); act(() => { userEvent.click(screen.getByText('Save monitor')); diff --git a/x-pack/plugins/uptime/public/components/monitor_management/action_bar/action_bar.tsx b/x-pack/plugins/uptime/public/components/monitor_management/action_bar/action_bar.tsx index 4d0d20d54867..f54031766be8 100644 --- a/x-pack/plugins/uptime/public/components/monitor_management/action_bar/action_bar.tsx +++ b/x-pack/plugins/uptime/public/components/monitor_management/action_bar/action_bar.tsx @@ -13,7 +13,7 @@ import { EuiButton, EuiButtonEmpty, EuiText, - EuiToolTip, + EuiPopover, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -37,11 +37,19 @@ export interface ActionBarProps { monitor: SyntheticsMonitor; isValid: boolean; testRun?: TestRun; + isTestRunInProgress: boolean; onSave?: () => void; onTestNow?: () => void; } -export const ActionBar = ({ monitor, isValid, onSave, onTestNow, testRun }: ActionBarProps) => { +export const ActionBar = ({ + monitor, + isValid, + onSave, + onTestNow, + testRun, + isTestRunInProgress, +}: ActionBarProps) => { const { monitorId } = useParams<{ monitorId: string }>(); const { basePath } = useContext(UptimeSettingsContext); const { locations } = useSelector(monitorManagementListSelector); @@ -49,6 +57,7 @@ export const ActionBar = ({ monitor, isValid, onSave, onTestNow, testRun }: Acti const [hasBeenSubmitted, setHasBeenSubmitted] = useState(false); const [isSaving, setIsSaving] = useState(false); const [isSuccessful, setIsSuccessful] = useState(false); + const [isPopoverOpen, setIsPopoverOpen] = useState(undefined); const { data, status } = useFetcher(() => { if (!isSaving || !isValid) { @@ -94,7 +103,7 @@ export const ActionBar = ({ monitor, isValid, onSave, onTestNow, testRun }: Acti }); setIsSuccessful(true); } else if (hasErrors && !loading) { - Object.values(data).forEach((location) => { + Object.values(data!).forEach((location) => { const { status: responseStatus, reason } = location.error || {}; kibanaService.toasts.addWarning({ title: i18n.translate('xpack.uptime.monitorManagement.service.error.title', { @@ -144,35 +153,51 @@ export const ActionBar = ({ monitor, isValid, onSave, onTestNow, testRun }: Acti - {onTestNow && ( - - - onTestNow()} - disabled={!isValid} - data-test-subj={'monitorTestNowRunBtn'} - > - {testRun ? RE_RUN_TEST_LABEL : RUN_TEST_LABEL} - - - - )} - {DISCARD_LABEL} + {onTestNow && ( + + {/* Popover is used instead of EuiTooltip until the resolution of https://github.com/elastic/eui/issues/5604 */} + onTestNow()} + onMouseEnter={() => { + setIsPopoverOpen(true); + }} + onMouseLeave={() => { + setIsPopoverOpen(false); + }} + > + {testRun ? RE_RUN_TEST_LABEL : RUN_TEST_LABEL} + + } + isOpen={isPopoverOpen} + > + +

{TEST_NOW_DESCRIPTION}

+
+
+
+ )} + Service Errors', () => { status: FETCH_STATUS.SUCCESS, refetch: () => {}, }); - render(, { state: mockLocationsState }); + render(, { + state: mockLocationsState, + }); userEvent.click(screen.getByText('Save monitor')); await waitFor(() => { diff --git a/x-pack/plugins/uptime/public/components/monitor_management/edit_monitor_config.tsx b/x-pack/plugins/uptime/public/components/monitor_management/edit_monitor_config.tsx index 015d9c2f9dfd..2f2014f405bd 100644 --- a/x-pack/plugins/uptime/public/components/monitor_management/edit_monitor_config.tsx +++ b/x-pack/plugins/uptime/public/components/monitor_management/edit_monitor_config.tsx @@ -88,7 +88,7 @@ export const EditMonitorConfig = ({ monitor }: Props) => { browserDefaultValues={fullDefaultConfig[DataStream.BROWSER]} tlsDefaultValues={defaultTLSConfig} > - + ); }; diff --git a/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/monitor_advanced_fields.tsx b/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/monitor_advanced_fields.tsx index 5cecdf9a385b..21ef7d12dcd5 100644 --- a/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/monitor_advanced_fields.tsx +++ b/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/monitor_advanced_fields.tsx @@ -6,17 +6,19 @@ */ import React, { memo } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiFormRow, EuiSpacer, EuiDescribedFormGroup, EuiLink, EuiFieldText } from '@elastic/eui'; -import type { Validation } from '../../../../common/types/index'; -import { ConfigKey } from '../../../../common/runtime_types/monitor_management'; +import { EuiFormRow, EuiSpacer, EuiLink, EuiFieldText } from '@elastic/eui'; +import type { Validation } from '../../../../common/types'; +import { ConfigKey } from '../../../../common/runtime_types'; +import { DescribedFormGroupWithWrap } from '../../fleet_package/common/described_form_group_with_wrap'; import { usePolicyConfigContext } from '../../fleet_package/contexts'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; interface Props { validate: Validation; + minColumnWidth?: string; } -export const MonitorManagementAdvancedFields = memo(({ validate }) => { +export const MonitorManagementAdvancedFields = memo(({ validate, minColumnWidth }) => { const { namespace, setNamespace } = usePolicyConfigContext(); const namespaceErrorMsg = validate[ConfigKey.NAMESPACE]?.({ @@ -26,7 +28,8 @@ export const MonitorManagementAdvancedFields = memo(({ validate }) => { const { services } = useKibana(); return ( - (({ validate }) => { name="namespace" /> - + ); }); diff --git a/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/monitor_config.tsx b/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/monitor_config.tsx index bcade3692980..c12e3a3f4993 100644 --- a/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/monitor_config.tsx +++ b/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/monitor_config.tsx @@ -5,9 +5,17 @@ * 2.0. */ -import React, { useState } from 'react'; +import React, { useCallback, useState } from 'react'; -import { EuiResizableContainer } from '@elastic/eui'; +import { + EuiFlyoutBody, + EuiFlyoutHeader, + EuiFlyout, + EuiSpacer, + EuiFlyoutFooter, + EuiButtonEmpty, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { v4 as uuidv4 } from 'uuid'; import { defaultConfig, usePolicyConfigContext } from '../../fleet_package/contexts'; @@ -19,7 +27,7 @@ import { MonitorFields } from './monitor_fields'; import { TestNowMode, TestRun } from '../test_now_mode/test_now_mode'; import { MonitorFields as MonitorFieldsType } from '../../../../common/runtime_types'; -export const MonitorConfig = () => { +export const MonitorConfig = ({ isEdit = false }: { isEdit: boolean }) => { const { monitorType } = usePolicyConfigContext(); /* raw policy config compatible with the UI. Save this to saved objects */ @@ -37,46 +45,70 @@ export const MonitorConfig = () => { }); const [testRun, setTestRun] = useState(); + const [isTestRunInProgress, setIsTestRunInProgress] = useState(false); + const [isFlyoutOpen, setIsFlyoutOpen] = useState(false); - const onTestNow = () => { + const handleTestNow = () => { if (config) { setTestRun({ id: uuidv4(), monitor: config as MonitorFieldsType }); + setIsTestRunInProgress(true); + setIsFlyoutOpen(true); } }; + const handleTestDone = useCallback(() => { + setIsTestRunInProgress(false); + }, [setIsTestRunInProgress]); + + const handleFlyoutClose = useCallback(() => { + handleTestDone(); + setIsFlyoutOpen(false); + }, [handleTestDone, setIsFlyoutOpen]); + + const flyout = isFlyoutOpen && config && ( + + + + + + + + + + {CLOSE_LABEL} + + + + ); + return ( <> - - {(EuiResizablePanel, EuiResizableButton) => ( - <> - - - + - - - - {config && } - - - )} - + {flyout} ); }; + +const TEST_RESULT = i18n.translate('xpack.uptime.monitorManagement.testResult', { + defaultMessage: 'Test result', +}); + +const CLOSE_LABEL = i18n.translate('xpack.uptime.monitorManagement.closeButtonLabel', { + defaultMessage: 'Close', +}); diff --git a/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/monitor_fields.tsx b/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/monitor_fields.tsx index 9e72a810f821..32783460aed0 100644 --- a/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/monitor_fields.tsx +++ b/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/monitor_fields.tsx @@ -15,14 +15,22 @@ import { validate } from '../validation'; import { MonitorNameAndLocation } from './monitor_name_location'; import { MonitorManagementAdvancedFields } from './monitor_advanced_fields'; +const MIN_COLUMN_WRAP_WIDTH = '360px'; + export const MonitorFields = () => { const { monitorType } = usePolicyConfigContext(); return ( } + appendAdvancedFields={ + + } > diff --git a/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/monitor_name_location.tsx b/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/monitor_name_location.tsx index b5a4c7c4f7b0..7ba80f411c6f 100644 --- a/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/monitor_name_location.tsx +++ b/x-pack/plugins/uptime/public/components/monitor_management/monitor_config/monitor_name_location.tsx @@ -43,7 +43,7 @@ export const MonitorNameAndLocation = ({ validate }: Props) => { defaultMessage="Monitor name" /> } - fullWidth={true} + fullWidth={false} isInvalid={isNameInvalid || nameAlreadyExists} error={ nameAlreadyExists ? ( diff --git a/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/browser/browser_test_results.test.tsx b/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/browser/browser_test_results.test.tsx index 727dfa4b9ec3..d164e1970583 100644 --- a/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/browser/browser_test_results.test.tsx +++ b/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/browser/browser_test_results.test.tsx @@ -14,8 +14,16 @@ import { BrowserTestRunResult } from './browser_test_results'; import { fireEvent } from '@testing-library/dom'; describe('BrowserTestRunResult', function () { + const onDone = jest.fn(); + let testId: string; + + beforeEach(() => { + testId = 'test-id'; + jest.resetAllMocks(); + }); + it('should render properly', async function () { - render(); + render(); expect(await screen.findByText('Test result')).toBeInTheDocument(); expect(await screen.findByText('0 steps completed')).toBeInTheDocument(); const dataApi = (kibanaService.core as any).data.search; @@ -28,7 +36,7 @@ describe('BrowserTestRunResult', function () { query: { bool: { filter: [ - { term: { config_id: 'test-id' } }, + { term: { config_id: testId } }, { terms: { 'synthetics.type': ['heartbeat/summary', 'journey/start'], @@ -52,12 +60,13 @@ describe('BrowserTestRunResult', function () { data, stepListData: { steps: [stepEndDoc._source] } as any, loading: false, + stepsLoading: false, journeyStarted: true, summaryDoc: summaryDoc._source, stepEnds: [stepEndDoc._source], }); - render(); + render(); expect(await screen.findByText('Test result')).toBeInTheDocument(); @@ -69,6 +78,9 @@ describe('BrowserTestRunResult', function () { expect(await screen.findByText('Go to https://www.elastic.co/')).toBeInTheDocument(); expect(await screen.findByText('21.8 seconds')).toBeInTheDocument(); + + // Calls onDone on completion + expect(onDone).toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/browser/browser_test_results.tsx b/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/browser/browser_test_results.tsx index 5dc893356b21..c6074626bad1 100644 --- a/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/browser/browser_test_results.tsx +++ b/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/browser/browser_test_results.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import { useEffect } from 'react'; import * as React from 'react'; import { EuiAccordion, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -16,13 +17,21 @@ import { TestResultHeader } from '../test_result_header'; interface Props { monitorId: string; + isMonitorSaved: boolean; + onDone: () => void; } -export const BrowserTestRunResult = ({ monitorId }: Props) => { - const { data, loading, stepEnds, journeyStarted, summaryDoc, stepListData } = +export const BrowserTestRunResult = ({ monitorId, isMonitorSaved, onDone }: Props) => { + const { data, loading, stepsLoading, stepEnds, journeyStarted, summaryDoc, stepListData } = useBrowserRunOnceMonitors({ configId: monitorId, }); + useEffect(() => { + if (Boolean(summaryDoc)) { + onDone(); + } + }, [summaryDoc, onDone]); + const hits = data?.hits.hits; const doc = hits?.[0]?._source as JourneyStep; @@ -50,6 +59,10 @@ export const BrowserTestRunResult = ({ monitorId }: Props) => { ); + const isStepsLoading = + journeyStarted && stepEnds.length === 0 && (!summaryDoc || (summaryDoc && stepsLoading)); + const isStepsLoadingFailed = summaryDoc && stepEnds.length === 0 && !isStepsLoading; + return ( { buttonContent={buttonContent} paddingSize="s" data-test-subj="expandResults" + initialIsOpen={true} > - {summaryDoc && stepEnds.length === 0 && {FAILED_TO_RUN}} - {!summaryDoc && journeyStarted && stepEnds.length === 0 && {LOADING_STEPS}} + {isStepsLoading && {LOADING_STEPS}} + {isStepsLoadingFailed && {FAILED_TO_RUN}} + {stepEnds.length > 0 && stepListData?.steps && ( diff --git a/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/browser/use_browser_run_once_monitors.test.tsx b/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/browser/use_browser_run_once_monitors.test.tsx index f467bb642a13..285a4a4140c2 100644 --- a/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/browser/use_browser_run_once_monitors.test.tsx +++ b/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/browser/use_browser_run_once_monitors.test.tsx @@ -34,6 +34,7 @@ describe('useBrowserRunOnceMonitors', function () { data: undefined, journeyStarted: false, loading: true, + stepsLoading: true, stepEnds: [], stepListData: undefined, summaryDoc: undefined, diff --git a/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/browser/use_browser_run_once_monitors.ts b/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/browser/use_browser_run_once_monitors.ts index d051eaebe392..04605373f369 100644 --- a/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/browser/use_browser_run_once_monitors.ts +++ b/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/browser/use_browser_run_once_monitors.ts @@ -86,7 +86,7 @@ export const useBrowserRunOnceMonitors = ({ const { data, loading } = useBrowserEsResults({ configId, testRunId, lastRefresh }); - const { data: stepListData } = useFetcher(() => { + const { data: stepListData, loading: stepsLoading } = useFetcher(() => { if (checkGroupId && !skipDetails) { return fetchJourneySteps({ checkGroup: checkGroupId, @@ -122,6 +122,7 @@ export const useBrowserRunOnceMonitors = ({ data, stepEnds, loading, + stepsLoading, stepListData, summaryDoc: summary, journeyStarted: Boolean(checkGroupId), diff --git a/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/simple/simple_test_results.test.tsx b/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/simple/simple_test_results.test.tsx index 99ed9ac43db1..1d5dfef8a67e 100644 --- a/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/simple/simple_test_results.test.tsx +++ b/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/simple/simple_test_results.test.tsx @@ -14,8 +14,16 @@ import * as runOnceHooks from './use_simple_run_once_monitors'; import { Ping } from '../../../../../common/runtime_types'; describe('SimpleTestResults', function () { + const onDone = jest.fn(); + let testId: string; + + beforeEach(() => { + testId = 'test-id'; + jest.resetAllMocks(); + }); + it('should render properly', async function () { - render(); + render(); expect(await screen.findByText('Test result')).toBeInTheDocument(); const dataApi = (kibanaService.core as any).data.search; @@ -26,7 +34,7 @@ describe('SimpleTestResults', function () { body: { query: { bool: { - filter: [{ term: { config_id: 'test-id' } }, { exists: { field: 'summary' } }], + filter: [{ term: { config_id: testId } }, { exists: { field: 'summary' } }], }, }, sort: [{ '@timestamp': 'desc' }], @@ -51,7 +59,7 @@ describe('SimpleTestResults', function () { loading: false, }); - render(); + render(); expect(await screen.findByText('Test result')).toBeInTheDocument(); @@ -61,6 +69,9 @@ describe('SimpleTestResults', function () { expect(await screen.findByText('Checked Jan 12, 2022 11:54:27 AM')).toBeInTheDocument(); expect(await screen.findByText('Took 191 ms')).toBeInTheDocument(); + // Calls onDone on completion + expect(onDone).toHaveBeenCalled(); + screen.debug(); }); }); diff --git a/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/simple/simple_test_results.tsx b/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/simple/simple_test_results.tsx index 507082c7fefb..4fb27fb83d56 100644 --- a/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/simple/simple_test_results.tsx +++ b/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/simple/simple_test_results.tsx @@ -12,16 +12,18 @@ import { TestResultHeader } from '../test_result_header'; interface Props { monitorId: string; + onDone: () => void; } -export function SimpleTestResults({ monitorId }: Props) { +export function SimpleTestResults({ monitorId, onDone }: Props) { const [summaryDocs, setSummaryDocs] = useState([]); const { summaryDoc, loading } = useSimpleRunOnceMonitors({ configId: monitorId }); useEffect(() => { if (summaryDoc) { setSummaryDocs((prevState) => [summaryDoc, ...prevState]); + onDone(); } - }, [summaryDoc]); + }, [summaryDoc, onDone]); return ( <> diff --git a/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/test_now_mode.test.tsx b/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/test_now_mode.test.tsx index 849f1215614d..4a3f155a1881 100644 --- a/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/test_now_mode.test.tsx +++ b/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/test_now_mode.test.tsx @@ -13,9 +13,19 @@ import { kibanaService } from '../../../state/kibana_service'; import { MonitorFields } from '../../../../common/runtime_types'; describe('TestNowMode', function () { + const onDone = jest.fn(); + + afterEach(() => { + jest.resetAllMocks(); + }); + it('should render properly', async function () { render( - + ); expect(await screen.findByText('Test result')).toBeInTheDocument(); expect(await screen.findByText('PENDING')).toBeInTheDocument(); diff --git a/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/test_now_mode.tsx b/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/test_now_mode.tsx index 43d4e0e6e9d2..a4f04e04ddc1 100644 --- a/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/test_now_mode.tsx +++ b/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/test_now_mode.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiCallOut, @@ -26,13 +26,25 @@ export interface TestRun { monitor: MonitorFields; } -export function TestNowMode({ testRun }: { testRun?: TestRun }) { +export function TestNowMode({ + testRun, + isMonitorSaved, + onDone, +}: { + testRun?: TestRun; + isMonitorSaved: boolean; + onDone: () => void; +}) { + const [serviceError, setServiceError] = useState(null); + const { data, loading: isPushing } = useFetcher(() => { if (testRun) { return runOnceMonitor({ monitor: testRun.monitor, id: testRun.id, - }); + }) + .then(() => setServiceError(null)) + .catch((error) => setServiceError(error)); } return new Promise((resolve) => resolve(null)); }, [testRun]); @@ -49,7 +61,13 @@ export function TestNowMode({ testRun }: { testRun?: TestRun }) { const errors = (data as { errors?: Array<{ error: Error }> })?.errors; - const hasErrors = errors && errors?.length > 0; + const hasErrors = serviceError || (errors && errors?.length > 0); + + useEffect(() => { + if (!isPushing && (!testRun || hasErrors)) { + onDone(); + } + }, [testRun, hasErrors, isPushing, onDone]); if (!testRun) { return null; @@ -68,7 +86,12 @@ export function TestNowMode({ testRun }: { testRun?: TestRun }) { {testRun && !hasErrors && !isPushing && ( - + )} diff --git a/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/test_run_results.tsx b/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/test_run_results.tsx index 4b261815e994..27c9eb8426a3 100644 --- a/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/test_run_results.tsx +++ b/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/test_run_results.tsx @@ -13,11 +13,13 @@ import { SimpleTestResults } from './simple/simple_test_results'; interface Props { monitorId: string; monitor: SyntheticsMonitor; + isMonitorSaved: boolean; + onDone: () => void; } -export const TestRunResult = ({ monitorId, monitor }: Props) => { +export const TestRunResult = ({ monitorId, monitor, isMonitorSaved, onDone }: Props) => { return monitor.type === 'browser' ? ( - + ) : ( - + ); }; diff --git a/x-pack/plugins/uptime/public/components/synthetics/check_steps/step_duration.tsx b/x-pack/plugins/uptime/public/components/synthetics/check_steps/step_duration.tsx index a9697a8969f6..d9c8eee59ecc 100644 --- a/x-pack/plugins/uptime/public/components/synthetics/check_steps/step_duration.tsx +++ b/x-pack/plugins/uptime/public/components/synthetics/check_steps/step_duration.tsx @@ -8,7 +8,7 @@ import type { MouseEvent } from 'react'; import * as React from 'react'; -import { EuiButtonEmpty, EuiPopover } from '@elastic/eui'; +import { EuiButtonEmpty, EuiPopover, EuiText } from '@elastic/eui'; import { useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { JourneyStep } from '../../../../common/runtime_types'; @@ -16,6 +16,7 @@ import { StepFieldTrend } from './step_field_trend'; import { microToSec } from '../../../lib/formatting'; interface Props { + showStepDurationTrend?: boolean; compactView?: boolean; step: JourneyStep; durationPopoverOpenIndex: number | null; @@ -26,8 +27,20 @@ export const StepDuration = ({ step, durationPopoverOpenIndex, setDurationPopoverOpenIndex, + showStepDurationTrend = true, compactView = false, }: Props) => { + const stepDurationText = useMemo( + () => + i18n.translate('xpack.uptime.synthetics.step.duration', { + defaultMessage: '{value} seconds', + values: { + value: microToSec(step.synthetics.step?.duration.us!, 1), + }, + }), + [step.synthetics.step?.duration.us] + ); + const component = useMemo( () => ( --; } + if (!showStepDurationTrend) { + return {stepDurationText}; + } + const button = ( setDurationPopoverOpenIndex(step.synthetics.step?.index ?? null)} iconType={compactView ? undefined : 'visArea'} > - {i18n.translate('xpack.uptime.synthetics.step.duration', { - defaultMessage: '{value} seconds', - values: { - value: microToSec(step.synthetics.step?.duration.us!, 1), - }, - })} + {stepDurationText} ); diff --git a/x-pack/plugins/uptime/public/components/synthetics/check_steps/steps_list.tsx b/x-pack/plugins/uptime/public/components/synthetics/check_steps/steps_list.tsx index d635d76fc3f8..40362df3df5f 100644 --- a/x-pack/plugins/uptime/public/components/synthetics/check_steps/steps_list.tsx +++ b/x-pack/plugins/uptime/public/components/synthetics/check_steps/steps_list.tsx @@ -37,6 +37,7 @@ interface Props { error?: Error; loading: boolean; compactView?: boolean; + showStepDurationTrend?: boolean; } interface StepStatusCount { @@ -85,7 +86,13 @@ function reduceStepStatus(prev: StepStatusCount, cur: JourneyStep): StepStatusCo return prev; } -export const StepsList = ({ data, error, loading, compactView = false }: Props) => { +export const StepsList = ({ + data, + error, + loading, + showStepDurationTrend = true, + compactView = false, +}: Props) => { const steps: JourneyStep[] = data.filter(isStepEnd); const { expandedRows, toggleExpand } = useExpandedRow({ steps, allSteps: data, loading }); @@ -140,6 +147,7 @@ export const StepsList = ({ data, error, loading, compactView = false }: Props) step={item} durationPopoverOpenIndex={durationPopoverOpenIndex} setDurationPopoverOpenIndex={setDurationPopoverOpenIndex} + showStepDurationTrend={showStepDurationTrend} compactView={compactView} /> ); diff --git a/x-pack/plugins/uptime/public/pages/monitor_management/add_monitor.tsx b/x-pack/plugins/uptime/public/pages/monitor_management/add_monitor.tsx index bc8737ccd4b3..dbf1c1214abd 100644 --- a/x-pack/plugins/uptime/public/pages/monitor_management/add_monitor.tsx +++ b/x-pack/plugins/uptime/public/pages/monitor_management/add_monitor.tsx @@ -37,7 +37,7 @@ export const AddMonitorPage: React.FC = () => { allowedScheduleUnits: [ScheduleUnit.MINUTES], }} > - + );