mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Uptime][Monitor Management] Show form validation errors only after user interaction (#126116)(uptime/issues/456)
* Expose `onBlur` and `onFieldBlur` on fleet components. * Only pass down validators if either field is interacted with or form submit attempt has been made. uptime/issues/456
This commit is contained in:
parent
4bffe73187
commit
20dc80fc36
38 changed files with 1290 additions and 694 deletions
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { fireEvent } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { render } from '../../../lib/helper/rtl_helpers';
|
||||
|
@ -38,17 +39,21 @@ describe('<BrowserAdvancedFields />', () => {
|
|||
defaultSimpleFields = defaultBrowserSimpleFields,
|
||||
validate = defaultValidation,
|
||||
children,
|
||||
onFieldBlur,
|
||||
}: {
|
||||
defaultValues?: BrowserAdvancedFieldsType;
|
||||
defaultSimpleFields?: BrowserSimpleFields;
|
||||
validate?: Validation;
|
||||
children?: React.ReactNode;
|
||||
onFieldBlur?: (field: ConfigKey) => void;
|
||||
}) => {
|
||||
return (
|
||||
<IntlProvider locale="en">
|
||||
<BrowserSimpleFieldsContextProvider defaultValues={defaultSimpleFields}>
|
||||
<BrowserAdvancedFieldsContextProvider defaultValues={defaultValues}>
|
||||
<BrowserAdvancedFields validate={validate}>{children}</BrowserAdvancedFields>
|
||||
<BrowserAdvancedFields validate={validate} onFieldBlur={onFieldBlur}>
|
||||
{children}
|
||||
</BrowserAdvancedFields>
|
||||
</BrowserAdvancedFieldsContextProvider>
|
||||
</BrowserSimpleFieldsContextProvider>
|
||||
</IntlProvider>
|
||||
|
@ -72,6 +77,16 @@ describe('<BrowserAdvancedFields />', () => {
|
|||
userEvent.selectOptions(screenshots, ['off']);
|
||||
expect(screenshots.value).toEqual('off');
|
||||
});
|
||||
|
||||
it('calls onFieldBlur after change', () => {
|
||||
const onFieldBlur = jest.fn();
|
||||
const { getByLabelText } = render(<WrappedComponent onFieldBlur={onFieldBlur} />);
|
||||
|
||||
const screenshots = getByLabelText('Screenshot options') as HTMLInputElement;
|
||||
userEvent.selectOptions(screenshots, ['off']);
|
||||
fireEvent.blur(screenshots);
|
||||
expect(onFieldBlur).toHaveBeenCalledWith(ConfigKey.SCREENSHOTS);
|
||||
});
|
||||
});
|
||||
|
||||
it('only displayed filter options when zip url is truthy', () => {
|
||||
|
|
|
@ -29,198 +29,212 @@ interface Props {
|
|||
validate: Validation;
|
||||
children?: React.ReactNode;
|
||||
minColumnWidth?: string;
|
||||
onFieldBlur?: (field: ConfigKey) => void;
|
||||
}
|
||||
|
||||
export const BrowserAdvancedFields = memo<Props>(({ validate, children, minColumnWidth }) => {
|
||||
const { fields, setFields } = useBrowserAdvancedFieldsContext();
|
||||
const { fields: simpleFields } = useBrowserSimpleFieldsContext();
|
||||
export const BrowserAdvancedFields = memo<Props>(
|
||||
({ validate, children, minColumnWidth, onFieldBlur }) => {
|
||||
const { fields, setFields } = useBrowserAdvancedFieldsContext();
|
||||
const { fields: simpleFields } = useBrowserSimpleFieldsContext();
|
||||
|
||||
const handleInputChange = useCallback(
|
||||
({ value, configKey }: { value: unknown; configKey: ConfigKey }) => {
|
||||
setFields((prevFields) => ({ ...prevFields, [configKey]: value }));
|
||||
},
|
||||
[setFields]
|
||||
);
|
||||
const handleInputChange = useCallback(
|
||||
({ value, configKey }: { value: unknown; configKey: ConfigKey }) => {
|
||||
setFields((prevFields) => ({ ...prevFields, [configKey]: value }));
|
||||
},
|
||||
[setFields]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiAccordion
|
||||
id="syntheticsIntegrationBrowserAdvancedOptions"
|
||||
buttonContent="Advanced Browser options"
|
||||
data-test-subj="syntheticsBrowserAdvancedFieldsAccordion"
|
||||
>
|
||||
<EuiSpacer size="m" />
|
||||
{simpleFields[ConfigKey.SOURCE_ZIP_URL] && (
|
||||
return (
|
||||
<EuiAccordion
|
||||
id="syntheticsIntegrationBrowserAdvancedOptions"
|
||||
buttonContent="Advanced Browser options"
|
||||
data-test-subj="syntheticsBrowserAdvancedFieldsAccordion"
|
||||
>
|
||||
<EuiSpacer size="m" />
|
||||
{simpleFields[ConfigKey.SOURCE_ZIP_URL] && (
|
||||
<DescribedFormGroupWithWrap
|
||||
minColumnWidth={minColumnWidth}
|
||||
title={
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.browserAdvancedSettings.filtering.title"
|
||||
defaultMessage="Selective tests"
|
||||
/>
|
||||
</h4>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.browserAdvancedSettings.filtering.description"
|
||||
defaultMessage="Use these options to apply the selected monitor settings to a subset of the tests in your suite. Only the configured subset will be run by this monitor."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.browserAdvancedSettings.journeyFiltersMatch.label"
|
||||
defaultMessage="Filter match"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.browserAdvancedSettings.journeyFiltersMatch.helpText"
|
||||
defaultMessage="Run only journeys with a name that matches the provided glob with this monitor."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFieldText
|
||||
value={fields[ConfigKey.JOURNEY_FILTERS_MATCH]}
|
||||
onChange={(event) =>
|
||||
handleInputChange({
|
||||
value: event.target.value,
|
||||
configKey: ConfigKey.JOURNEY_FILTERS_MATCH,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.JOURNEY_FILTERS_MATCH)}
|
||||
data-test-subj="syntheticsBrowserJourneyFiltersMatch"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.browserAdvancedSettings.journeyFiltersTags.label"
|
||||
defaultMessage="Filter tags"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.browserAdvancedSettings.journeyFiltersTags.helpText"
|
||||
defaultMessage="Run only journeys with the given tags with this monitor."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<ComboBox
|
||||
selectedOptions={fields[ConfigKey.JOURNEY_FILTERS_TAGS]}
|
||||
onChange={(value) =>
|
||||
handleInputChange({ value, configKey: ConfigKey.JOURNEY_FILTERS_TAGS })
|
||||
}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.JOURNEY_FILTERS_TAGS)}
|
||||
data-test-subj="syntheticsBrowserJourneyFiltersTags"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</DescribedFormGroupWithWrap>
|
||||
)}
|
||||
<DescribedFormGroupWithWrap
|
||||
minColumnWidth={minColumnWidth}
|
||||
title={
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.browserAdvancedSettings.filtering.title"
|
||||
defaultMessage="Selective tests"
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.browserAdvancedSettings.title"
|
||||
defaultMessage="Synthetics agent options"
|
||||
/>
|
||||
</h4>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.browserAdvancedSettings.filtering.description"
|
||||
defaultMessage="Use these options to apply the selected monitor settings to a subset of the tests in your suite. Only the configured subset will be run by this monitor."
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.browserAdvancedSettings.description"
|
||||
defaultMessage="Provide fine-tuned configuration for the synthetics agent."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFormRow
|
||||
helpText={
|
||||
<>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.browserAdvancedSettings.ignoreHttpsErrors.helpText"
|
||||
defaultMessage="Set this option to true to disable TLS/SSL validation in the synthetics browser. This is useful for testing sites that use self-signed certs."
|
||||
/>
|
||||
</>
|
||||
}
|
||||
data-test-subj="syntheticsBrowserIgnoreHttpsErrors"
|
||||
>
|
||||
<EuiCheckbox
|
||||
id="syntheticsBrowserIgnoreHttpsErrorsCheckbox"
|
||||
checked={fields[ConfigKey.IGNORE_HTTPS_ERRORS]}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.browserAdvancedSettings.ignoreHttpsErrors.label"
|
||||
defaultMessage="Ignore HTTPS errors"
|
||||
/>
|
||||
}
|
||||
onChange={(event) =>
|
||||
handleInputChange({
|
||||
value: event.target.checked,
|
||||
configKey: ConfigKey.IGNORE_HTTPS_ERRORS,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.IGNORE_HTTPS_ERRORS)}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.browserAdvancedSettings.journeyFiltersMatch.label"
|
||||
defaultMessage="Filter match"
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.browserAdvancedSettings.screenshots.label"
|
||||
defaultMessage="Screenshot options"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.browserAdvancedSettings.journeyFiltersMatch.helpText"
|
||||
defaultMessage="Run only journeys with a name that matches the provided glob with this monitor."
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.browserAdvancedSettings.screenshots.helpText"
|
||||
defaultMessage="Set this option to manage the screenshots captured by the synthetics agent."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFieldText
|
||||
value={fields[ConfigKey.JOURNEY_FILTERS_MATCH]}
|
||||
<EuiSelect
|
||||
options={requestMethodOptions}
|
||||
value={fields[ConfigKey.SCREENSHOTS]}
|
||||
onChange={(event) =>
|
||||
handleInputChange({
|
||||
value: event.target.value,
|
||||
configKey: ConfigKey.JOURNEY_FILTERS_MATCH,
|
||||
configKey: ConfigKey.SCREENSHOTS,
|
||||
})
|
||||
}
|
||||
data-test-subj="syntheticsBrowserJourneyFiltersMatch"
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.SCREENSHOTS)}
|
||||
data-test-subj="syntheticsBrowserScreenshots"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.browserAdvancedSettings.journeyFiltersTags.label"
|
||||
defaultMessage="Filter tags"
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.browserAdvancedSettings.syntheticsArgs.label"
|
||||
defaultMessage="Synthetics args"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.browserAdvancedSettings.journeyFiltersTags.helpText"
|
||||
defaultMessage="Run only journeys with the given tags with this monitor."
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.browserAdvancedSettings.syntheticsArgs.helpText"
|
||||
defaultMessage="Extra arguments to pass to the synthetics agent package. Takes a list of strings. This is useful in rare scenarios, and should not ordinarily need to be set."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<ComboBox
|
||||
selectedOptions={fields[ConfigKey.JOURNEY_FILTERS_TAGS]}
|
||||
selectedOptions={fields[ConfigKey.SYNTHETICS_ARGS]}
|
||||
onChange={(value) =>
|
||||
handleInputChange({ value, configKey: ConfigKey.JOURNEY_FILTERS_TAGS })
|
||||
handleInputChange({ value, configKey: ConfigKey.SYNTHETICS_ARGS })
|
||||
}
|
||||
data-test-subj="syntheticsBrowserJourneyFiltersTags"
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.SYNTHETICS_ARGS)}
|
||||
data-test-subj="syntheticsBrowserSyntheticsArgs"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</DescribedFormGroupWithWrap>
|
||||
)}
|
||||
<DescribedFormGroupWithWrap
|
||||
minColumnWidth={minColumnWidth}
|
||||
title={
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.browserAdvancedSettings.title"
|
||||
defaultMessage="Synthetics agent options"
|
||||
/>
|
||||
</h4>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.browserAdvancedSettings.description"
|
||||
defaultMessage="Provide fine-tuned configuration for the synthetics agent."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFormRow
|
||||
helpText={
|
||||
<>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.browserAdvancedSettings.ignoreHttpsErrors.helpText"
|
||||
defaultMessage="Set this option to true to disable TLS/SSL validation in the synthetics browser. This is useful for testing sites that use self-signed certs."
|
||||
/>
|
||||
</>
|
||||
}
|
||||
data-test-subj="syntheticsBrowserIgnoreHttpsErrors"
|
||||
>
|
||||
<EuiCheckbox
|
||||
id="syntheticsBrowserIgnoreHttpsErrorsCheckbox"
|
||||
checked={fields[ConfigKey.IGNORE_HTTPS_ERRORS]}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.browserAdvancedSettings.ignoreHttpsErrors.label"
|
||||
defaultMessage="Ignore HTTPS errors"
|
||||
/>
|
||||
}
|
||||
onChange={(event) =>
|
||||
handleInputChange({
|
||||
value: event.target.checked,
|
||||
configKey: ConfigKey.IGNORE_HTTPS_ERRORS,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.browserAdvancedSettings.screenshots.label"
|
||||
defaultMessage="Screenshot options"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.browserAdvancedSettings.screenshots.helpText"
|
||||
defaultMessage="Set this option to manage the screenshots captured by the synthetics agent."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiSelect
|
||||
options={requestMethodOptions}
|
||||
value={fields[ConfigKey.SCREENSHOTS]}
|
||||
onChange={(event) =>
|
||||
handleInputChange({
|
||||
value: event.target.value,
|
||||
configKey: ConfigKey.SCREENSHOTS,
|
||||
})
|
||||
}
|
||||
data-test-subj="syntheticsBrowserScreenshots"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.browserAdvancedSettings.syntheticsArgs.label"
|
||||
defaultMessage="Synthetics args"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.browserAdvancedSettings.syntheticsArgs.helpText"
|
||||
defaultMessage="Extra arguments to pass to the synthetics agent package. Takes a list of strings. This is useful in rare scenarios, and should not ordinarily need to be set."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<ComboBox
|
||||
selectedOptions={fields[ConfigKey.SYNTHETICS_ARGS]}
|
||||
onChange={(value) => handleInputChange({ value, configKey: ConfigKey.SYNTHETICS_ARGS })}
|
||||
data-test-subj="syntheticsBrowserSyntheticsArgs"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</DescribedFormGroupWithWrap>
|
||||
|
||||
<ThrottlingFields validate={validate} minColumnWidth={minColumnWidth} />
|
||||
{children}
|
||||
</EuiAccordion>
|
||||
);
|
||||
});
|
||||
<ThrottlingFields
|
||||
validate={validate}
|
||||
minColumnWidth={minColumnWidth}
|
||||
onFieldBlur={onFieldBlur}
|
||||
/>
|
||||
{children}
|
||||
</EuiAccordion>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
const requestMethodOptions = Object.values(ScreenshotOption).map((option) => ({
|
||||
value: option,
|
||||
|
|
|
@ -17,9 +17,10 @@ import { SimpleFieldsWrapper } from '../common/simple_fields_wrapper';
|
|||
|
||||
interface Props {
|
||||
validate: Validation;
|
||||
onFieldBlur: (field: ConfigKey) => void; // To propagate blurred state up to parents
|
||||
}
|
||||
|
||||
export const BrowserSimpleFields = memo<Props>(({ validate }) => {
|
||||
export const BrowserSimpleFields = memo<Props>(({ validate, onFieldBlur }) => {
|
||||
const { fields, setFields, defaultValues } = useBrowserSimpleFieldsContext();
|
||||
const handleInputChange = useCallback(
|
||||
({ value, configKey }: { value: unknown; configKey: ConfigKey }) => {
|
||||
|
@ -61,7 +62,12 @@ export const BrowserSimpleFields = memo<Props>(({ validate }) => {
|
|||
);
|
||||
|
||||
return (
|
||||
<SimpleFieldsWrapper fields={fields} validate={validate} onInputChange={handleInputChange}>
|
||||
<SimpleFieldsWrapper
|
||||
fields={fields}
|
||||
validate={validate}
|
||||
onInputChange={handleInputChange}
|
||||
onFieldBlur={onFieldBlur}
|
||||
>
|
||||
<EuiFormRow
|
||||
id="syntheticsFleetScheduleField--number syntheticsFleetScheduleField--unit"
|
||||
label={
|
||||
|
@ -85,6 +91,7 @@ export const BrowserSimpleFields = memo<Props>(({ validate }) => {
|
|||
configKey: ConfigKey.SCHEDULE,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur(ConfigKey.SCHEDULE)}
|
||||
number={fields[ConfigKey.SCHEDULE].number}
|
||||
unit={fields[ConfigKey.SCHEDULE].unit}
|
||||
/>
|
||||
|
@ -99,6 +106,7 @@ export const BrowserSimpleFields = memo<Props>(({ validate }) => {
|
|||
>
|
||||
<SourceField
|
||||
onChange={onChangeSourceField}
|
||||
onFieldBlur={onFieldBlur}
|
||||
defaultConfig={useMemo(
|
||||
() => ({
|
||||
zipUrl: defaultValues[ConfigKey.SOURCE_ZIP_URL],
|
||||
|
|
|
@ -8,6 +8,7 @@ import 'jest-canvas-mock';
|
|||
|
||||
import React from 'react';
|
||||
import { fireEvent, screen, waitFor } from '@testing-library/react';
|
||||
import { ConfigKey } from '../../../../common/runtime_types';
|
||||
import { render } from '../../../lib/helper/rtl_helpers';
|
||||
import { IPolicyConfigContextProvider } from '../contexts/policy_config_context';
|
||||
import { SourceField, defaultValues } from './source_field';
|
||||
|
@ -42,6 +43,7 @@ jest.mock('../../../../../../../src/plugins/kibana_react/public', () => {
|
|||
});
|
||||
|
||||
const onChange = jest.fn();
|
||||
const onBlur = jest.fn();
|
||||
|
||||
describe('<SourceField />', () => {
|
||||
const WrappedComponent = ({
|
||||
|
@ -50,7 +52,7 @@ describe('<SourceField />', () => {
|
|||
return (
|
||||
<PolicyConfigContextProvider isZipUrlSourceEnabled={isZipUrlSourceEnabled}>
|
||||
<BrowserSimpleFieldsContextProvider>
|
||||
<SourceField onChange={onChange} />
|
||||
<SourceField onChange={onChange} onFieldBlur={onBlur} />
|
||||
</BrowserSimpleFieldsContextProvider>
|
||||
</PolicyConfigContextProvider>
|
||||
);
|
||||
|
@ -72,6 +74,16 @@ describe('<SourceField />', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('calls onBlur', () => {
|
||||
render(<WrappedComponent />);
|
||||
|
||||
const zipUrlField = screen.getByTestId('syntheticsBrowserZipUrl');
|
||||
fireEvent.click(zipUrlField);
|
||||
fireEvent.blur(zipUrlField);
|
||||
|
||||
expect(onBlur).toBeCalledWith(ConfigKey.SOURCE_ZIP_URL);
|
||||
});
|
||||
|
||||
it('shows ZipUrl source type by default', async () => {
|
||||
render(<WrappedComponent />);
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ import { OptionalLabel } from '../optional_label';
|
|||
import { CodeEditor } from '../code_editor';
|
||||
import { ScriptRecorderFields } from './script_recorder_fields';
|
||||
import { ZipUrlTLSFields } from './zip_url_tls_fields';
|
||||
import { MonacoEditorLangId } from '../types';
|
||||
import { ConfigKey, MonacoEditorLangId } from '../types';
|
||||
|
||||
enum SourceType {
|
||||
INLINE = 'syntheticsBrowserInlineConfig',
|
||||
|
@ -46,6 +46,7 @@ interface SourceConfig {
|
|||
|
||||
interface Props {
|
||||
onChange: (sourceConfig: SourceConfig) => void;
|
||||
onFieldBlur: (field: ConfigKey) => void;
|
||||
defaultConfig?: SourceConfig;
|
||||
}
|
||||
|
||||
|
@ -71,7 +72,7 @@ const getDefaultTab = (defaultConfig: SourceConfig, isZipUrlSourceEnabled = true
|
|||
return isZipUrlSourceEnabled ? SourceType.ZIP : SourceType.INLINE;
|
||||
};
|
||||
|
||||
export const SourceField = ({ onChange, defaultConfig = defaultValues }: Props) => {
|
||||
export const SourceField = ({ onChange, onFieldBlur, defaultConfig = defaultValues }: Props) => {
|
||||
const { isZipUrlSourceEnabled } = usePolicyConfigContext();
|
||||
const [sourceType, setSourceType] = useState<SourceType>(
|
||||
getDefaultTab(defaultConfig, isZipUrlSourceEnabled)
|
||||
|
@ -118,6 +119,7 @@ export const SourceField = ({ onChange, defaultConfig = defaultValues }: Props)
|
|||
onChange={({ target: { value } }) =>
|
||||
setConfig((prevConfig) => ({ ...prevConfig, zipUrl: value }))
|
||||
}
|
||||
onBlur={() => onFieldBlur(ConfigKey.SOURCE_ZIP_URL)}
|
||||
value={config.zipUrl}
|
||||
data-test-subj="syntheticsBrowserZipUrl"
|
||||
/>
|
||||
|
@ -142,6 +144,7 @@ export const SourceField = ({ onChange, defaultConfig = defaultValues }: Props)
|
|||
onChange={({ target: { value } }) =>
|
||||
setConfig((prevConfig) => ({ ...prevConfig, proxyUrl: value }))
|
||||
}
|
||||
onBlur={() => onFieldBlur(ConfigKey.SOURCE_ZIP_PROXY_URL)}
|
||||
value={config.proxyUrl}
|
||||
data-test-subj="syntheticsBrowserZipUrlProxy"
|
||||
/>
|
||||
|
@ -165,6 +168,7 @@ export const SourceField = ({ onChange, defaultConfig = defaultValues }: Props)
|
|||
onChange={({ target: { value } }) =>
|
||||
setConfig((prevConfig) => ({ ...prevConfig, folder: value }))
|
||||
}
|
||||
onBlur={() => onFieldBlur(ConfigKey.SOURCE_ZIP_FOLDER)}
|
||||
value={config.folder}
|
||||
data-test-subj="syntheticsBrowserZipUrlFolder"
|
||||
/>
|
||||
|
@ -193,7 +197,10 @@ export const SourceField = ({ onChange, defaultConfig = defaultValues }: Props)
|
|||
)}
|
||||
id="jsonParamsEditor"
|
||||
languageId={MonacoEditorLangId.JSON}
|
||||
onChange={(code) => setConfig((prevConfig) => ({ ...prevConfig, params: code }))}
|
||||
onChange={(code) => {
|
||||
setConfig((prevConfig) => ({ ...prevConfig, params: code }));
|
||||
onFieldBlur(ConfigKey.PARAMS);
|
||||
}}
|
||||
value={config.params}
|
||||
data-test-subj="syntheticsBrowserZipUrlParams"
|
||||
/>
|
||||
|
@ -217,6 +224,7 @@ export const SourceField = ({ onChange, defaultConfig = defaultValues }: Props)
|
|||
onChange={({ target: { value } }) =>
|
||||
setConfig((prevConfig) => ({ ...prevConfig, username: value }))
|
||||
}
|
||||
onBlur={() => onFieldBlur(ConfigKey.SOURCE_ZIP_USERNAME)}
|
||||
value={config.username}
|
||||
data-test-subj="syntheticsBrowserZipUrlUsername"
|
||||
/>
|
||||
|
@ -240,6 +248,7 @@ export const SourceField = ({ onChange, defaultConfig = defaultValues }: Props)
|
|||
onChange={({ target: { value } }) =>
|
||||
setConfig((prevConfig) => ({ ...prevConfig, password: value }))
|
||||
}
|
||||
onBlur={() => onFieldBlur(ConfigKey.SOURCE_ZIP_PASSWORD)}
|
||||
value={config.password}
|
||||
data-test-subj="syntheticsBrowserZipUrlPassword"
|
||||
/>
|
||||
|
@ -281,7 +290,10 @@ export const SourceField = ({ onChange, defaultConfig = defaultValues }: Props)
|
|||
)}
|
||||
id="javascript"
|
||||
languageId={MonacoEditorLangId.JAVASCRIPT}
|
||||
onChange={(code) => setConfig((prevConfig) => ({ ...prevConfig, inlineScript: code }))}
|
||||
onChange={(code) => {
|
||||
setConfig((prevConfig) => ({ ...prevConfig, inlineScript: code }));
|
||||
onFieldBlur(ConfigKey.SOURCE_INLINE);
|
||||
}}
|
||||
value={config.inlineScript}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
|
|
@ -5,11 +5,18 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { fireEvent } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { render } from '../../../lib/helper/rtl_helpers';
|
||||
import { ThrottlingFields } from './throttling_fields';
|
||||
import { DataStream, BrowserAdvancedFields, BrowserSimpleFields, Validation } from '../types';
|
||||
import {
|
||||
DataStream,
|
||||
BrowserAdvancedFields,
|
||||
BrowserSimpleFields,
|
||||
Validation,
|
||||
ConfigKey,
|
||||
} from '../types';
|
||||
import {
|
||||
BrowserAdvancedFieldsContextProvider,
|
||||
BrowserSimpleFieldsContextProvider,
|
||||
|
@ -31,16 +38,18 @@ describe('<ThrottlingFields />', () => {
|
|||
defaultValues = defaultConfig,
|
||||
defaultSimpleFields = defaultBrowserSimpleFields,
|
||||
validate = defaultValidation,
|
||||
onFieldBlur,
|
||||
}: {
|
||||
defaultValues?: BrowserAdvancedFields;
|
||||
defaultSimpleFields?: BrowserSimpleFields;
|
||||
validate?: Validation;
|
||||
onFieldBlur?: (field: ConfigKey) => void;
|
||||
}) => {
|
||||
return (
|
||||
<IntlProvider locale="en">
|
||||
<BrowserSimpleFieldsContextProvider defaultValues={defaultSimpleFields}>
|
||||
<BrowserAdvancedFieldsContextProvider defaultValues={defaultValues}>
|
||||
<ThrottlingFields validate={validate} />
|
||||
<ThrottlingFields validate={validate} onFieldBlur={onFieldBlur} />
|
||||
</BrowserAdvancedFieldsContextProvider>
|
||||
</BrowserSimpleFieldsContextProvider>
|
||||
</IntlProvider>
|
||||
|
@ -97,6 +106,39 @@ describe('<ThrottlingFields />', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('calls onBlur on fields', () => {
|
||||
const onFieldBlur = jest.fn();
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('for the enable switch', () => {
|
||||
const { getByTestId } = render(<WrappedComponent onFieldBlur={onFieldBlur} />);
|
||||
|
||||
const enableSwitch = getByTestId('syntheticsBrowserIsThrottlingEnabled');
|
||||
fireEvent.focus(enableSwitch);
|
||||
fireEvent.blur(enableSwitch);
|
||||
expect(onFieldBlur).toHaveBeenCalledWith(ConfigKey.IS_THROTTLING_ENABLED);
|
||||
});
|
||||
|
||||
it('for throttling inputs', () => {
|
||||
const { getByLabelText } = render(<WrappedComponent onFieldBlur={onFieldBlur} />);
|
||||
|
||||
const downloadSpeed = getByLabelText('Download Speed') as HTMLInputElement;
|
||||
const uploadSpeed = getByLabelText('Upload Speed') as HTMLInputElement;
|
||||
const latency = getByLabelText('Latency') as HTMLInputElement;
|
||||
|
||||
fireEvent.blur(downloadSpeed);
|
||||
fireEvent.blur(uploadSpeed);
|
||||
fireEvent.blur(latency);
|
||||
|
||||
expect(onFieldBlur).toHaveBeenCalledWith(ConfigKey.DOWNLOAD_SPEED);
|
||||
expect(onFieldBlur).toHaveBeenCalledWith(ConfigKey.UPLOAD_SPEED);
|
||||
expect(onFieldBlur).toHaveBeenCalledWith(ConfigKey.LATENCY);
|
||||
});
|
||||
});
|
||||
|
||||
describe('validates changing fields', () => {
|
||||
it('disallows negative/zero download speeds', () => {
|
||||
const { getByLabelText, queryByText } = render(<WrappedComponent />);
|
||||
|
|
|
@ -17,6 +17,7 @@ import { Validation, ConfigKey } from '../types';
|
|||
interface Props {
|
||||
validate: Validation;
|
||||
minColumnWidth?: string;
|
||||
onFieldBlur?: (field: ConfigKey) => void;
|
||||
}
|
||||
|
||||
type ThrottlingConfigs =
|
||||
|
@ -25,7 +26,7 @@ type ThrottlingConfigs =
|
|||
| ConfigKey.UPLOAD_SPEED
|
||||
| ConfigKey.LATENCY;
|
||||
|
||||
export const ThrottlingFields = memo<Props>(({ validate, minColumnWidth }) => {
|
||||
export const ThrottlingFields = memo<Props>(({ validate, minColumnWidth, onFieldBlur }) => {
|
||||
const { fields, setFields } = useBrowserAdvancedFieldsContext();
|
||||
|
||||
const handleInputChange = useCallback(
|
||||
|
@ -64,6 +65,7 @@ export const ThrottlingFields = memo<Props>(({ validate, minColumnWidth }) => {
|
|||
configKey: ConfigKey.DOWNLOAD_SPEED,
|
||||
});
|
||||
}}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.DOWNLOAD_SPEED)}
|
||||
data-test-subj="syntheticsBrowserDownloadSpeed"
|
||||
append={
|
||||
<EuiText size="xs">
|
||||
|
@ -98,6 +100,7 @@ export const ThrottlingFields = memo<Props>(({ validate, minColumnWidth }) => {
|
|||
configKey: ConfigKey.UPLOAD_SPEED,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.UPLOAD_SPEED)}
|
||||
data-test-subj="syntheticsBrowserUploadSpeed"
|
||||
append={
|
||||
<EuiText size="xs">
|
||||
|
@ -131,6 +134,7 @@ export const ThrottlingFields = memo<Props>(({ validate, minColumnWidth }) => {
|
|||
configKey: ConfigKey.LATENCY,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.LATENCY)}
|
||||
data-test-subj="syntheticsBrowserLatency"
|
||||
append={
|
||||
<EuiText size="xs">
|
||||
|
@ -177,6 +181,7 @@ export const ThrottlingFields = memo<Props>(({ validate, minColumnWidth }) => {
|
|||
configKey: ConfigKey.IS_THROTTLING_ENABLED,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.IS_THROTTLING_ENABLED)}
|
||||
/>
|
||||
{throttlingInputs}
|
||||
</DescribedFormGroupWithWrap>
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { fireEvent } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { render } from '../../lib/helper/rtl_helpers';
|
||||
import { ComboBox } from './combo_box';
|
||||
|
@ -20,4 +21,17 @@ describe('<ComboBox />', () => {
|
|||
|
||||
expect(getByTestId('syntheticsFleetComboBox')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls onBlur', () => {
|
||||
const onBlur = jest.fn();
|
||||
const { getByTestId } = render(
|
||||
<ComboBox selectedOptions={selectedOptions} onChange={onChange} onBlur={onBlur} />
|
||||
);
|
||||
|
||||
const combobox = getByTestId('syntheticsFleetComboBox');
|
||||
fireEvent.focus(combobox);
|
||||
fireEvent.blur(combobox);
|
||||
|
||||
expect(onBlur).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -10,10 +10,11 @@ import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui';
|
|||
|
||||
export interface Props {
|
||||
onChange: (value: string[]) => void;
|
||||
onBlur?: () => void;
|
||||
selectedOptions: string[];
|
||||
}
|
||||
|
||||
export const ComboBox = ({ onChange, selectedOptions, ...props }: Props) => {
|
||||
export const ComboBox = ({ onChange, onBlur, selectedOptions, ...props }: Props) => {
|
||||
const [formattedSelectedOptions, setSelectedOptions] = useState<
|
||||
Array<EuiComboBoxOptionOption<string>>
|
||||
>(selectedOptions.map((option) => ({ label: option, key: option })));
|
||||
|
@ -64,6 +65,7 @@ export const ComboBox = ({ onChange, selectedOptions, ...props }: Props) => {
|
|||
selectedOptions={formattedSelectedOptions}
|
||||
onCreateOption={onCreateOption}
|
||||
onChange={onOptionsChange}
|
||||
onBlur={() => onBlur?.()}
|
||||
onSearchChange={onSearchChange}
|
||||
isInvalid={isInvalid}
|
||||
{...props}
|
||||
|
|
|
@ -5,14 +5,13 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
import { EuiFieldNumber, EuiFieldText, EuiFormRow } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiFormRow, EuiFieldText, EuiFieldNumber } from '@elastic/eui';
|
||||
import { Validation, DataStream } from '../types';
|
||||
import { ConfigKey, CommonFields as CommonFieldsType } from '../types';
|
||||
import React, { useEffect } from 'react';
|
||||
import { ComboBox } from '../combo_box';
|
||||
import { OptionalLabel } from '../optional_label';
|
||||
import { usePolicyConfigContext } from '../contexts';
|
||||
import { OptionalLabel } from '../optional_label';
|
||||
import { CommonFields as CommonFieldsType, ConfigKey, DataStream, Validation } from '../types';
|
||||
|
||||
interface Props {
|
||||
validate: Validation;
|
||||
|
@ -24,9 +23,10 @@ interface Props {
|
|||
value: string | string[] | null;
|
||||
configKey: ConfigKey;
|
||||
}) => void;
|
||||
onFieldBlur?: (field: ConfigKey) => void;
|
||||
}
|
||||
|
||||
export function CommonFields({ fields, onChange, validate }: Props) {
|
||||
export function CommonFields({ fields, onChange, onFieldBlur, validate }: Props) {
|
||||
const { monitorType } = usePolicyConfigContext();
|
||||
|
||||
const isBrowser = monitorType === DataStream.BROWSER;
|
||||
|
@ -65,6 +65,7 @@ export function CommonFields({ fields, onChange, validate }: Props) {
|
|||
configKey: ConfigKey.APM_SERVICE_NAME,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.APM_SERVICE_NAME)}
|
||||
data-test-subj="syntheticsAPMServiceName"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
@ -106,6 +107,7 @@ export function CommonFields({ fields, onChange, validate }: Props) {
|
|||
configKey: ConfigKey.TIMEOUT,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.TIMEOUT)}
|
||||
step={'any'}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
@ -128,6 +130,7 @@ export function CommonFields({ fields, onChange, validate }: Props) {
|
|||
<ComboBox
|
||||
selectedOptions={fields[ConfigKey.TAGS]}
|
||||
onChange={(value) => onChange({ value, configKey: ConfigKey.TAGS })}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.TAGS)}
|
||||
data-test-subj="syntheticsTags"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
|
|
@ -13,9 +13,10 @@ import { ConfigKey, CommonFields } from '../types';
|
|||
interface Props {
|
||||
fields: CommonFields;
|
||||
onChange: ({ value, configKey }: { value: boolean; configKey: ConfigKey }) => void;
|
||||
onBlur?: () => void;
|
||||
}
|
||||
|
||||
export function Enabled({ fields, onChange }: Props) {
|
||||
export function Enabled({ fields, onChange, onBlur }: Props) {
|
||||
return (
|
||||
<>
|
||||
<EuiFormRow
|
||||
|
@ -41,6 +42,7 @@ export function Enabled({ fields, onChange }: Props) {
|
|||
configKey: ConfigKey.ENABLED,
|
||||
})
|
||||
}
|
||||
onBlur={() => onBlur?.()}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</>
|
||||
|
|
|
@ -6,23 +6,39 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { ConfigKey, Validation, CommonFields as CommonFieldsType } from '../types';
|
||||
import { CommonFields } from '../common/common_fields';
|
||||
import { Enabled } from '../common/enabled';
|
||||
import { CommonFields } from './common_fields';
|
||||
import { Enabled } from './enabled';
|
||||
import { CommonFields as CommonFieldsType, ConfigKey, Validation } from '../types';
|
||||
|
||||
interface Props {
|
||||
validate: Validation;
|
||||
onInputChange: ({ value, configKey }: { value: unknown; configKey: ConfigKey }) => void;
|
||||
onFieldBlur?: (field: ConfigKey) => void;
|
||||
children: React.ReactNode;
|
||||
fields: CommonFieldsType;
|
||||
}
|
||||
|
||||
export const SimpleFieldsWrapper = ({ validate, onInputChange, children, fields }: Props) => {
|
||||
export const SimpleFieldsWrapper = ({
|
||||
validate,
|
||||
onInputChange,
|
||||
onFieldBlur,
|
||||
children,
|
||||
fields,
|
||||
}: Props) => {
|
||||
return (
|
||||
<>
|
||||
<Enabled fields={fields} onChange={onInputChange} />
|
||||
<Enabled
|
||||
fields={fields}
|
||||
onChange={onInputChange}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.ENABLED)}
|
||||
/>
|
||||
{children}
|
||||
<CommonFields fields={fields} onChange={onInputChange} validate={validate} />
|
||||
<CommonFields
|
||||
fields={fields}
|
||||
validate={validate}
|
||||
onChange={onInputChange}
|
||||
onFieldBlur={onFieldBlur}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -57,10 +57,13 @@ const defaultHTTPConfig = defaultConfig[DataStream.HTTP];
|
|||
const defaultTCPConfig = defaultConfig[DataStream.TCP];
|
||||
|
||||
describe('<CustomFields />', () => {
|
||||
let onFieldBlurMock: jest.Mock | undefined;
|
||||
|
||||
const WrappedComponent = ({
|
||||
validate = defaultValidation,
|
||||
isEditable = false,
|
||||
dataStreams = [DataStream.HTTP, DataStream.TCP, DataStream.ICMP, DataStream.BROWSER],
|
||||
onFieldBlur = onFieldBlurMock,
|
||||
}) => {
|
||||
return (
|
||||
<HTTPContextProvider>
|
||||
|
@ -69,7 +72,11 @@ describe('<CustomFields />', () => {
|
|||
<BrowserContextProvider>
|
||||
<ICMPSimpleFieldsContextProvider>
|
||||
<TLSFieldsContextProvider>
|
||||
<CustomFields validate={validate} dataStreams={dataStreams} />
|
||||
<CustomFields
|
||||
validate={validate}
|
||||
dataStreams={dataStreams}
|
||||
onFieldBlur={onFieldBlur}
|
||||
/>
|
||||
</TLSFieldsContextProvider>
|
||||
</ICMPSimpleFieldsContextProvider>
|
||||
</BrowserContextProvider>
|
||||
|
@ -79,8 +86,15 @@ describe('<CustomFields />', () => {
|
|||
);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
onFieldBlurMock = undefined;
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('renders CustomFields', async () => {
|
||||
const { getByText, getByLabelText, queryByLabelText } = render(<WrappedComponent />);
|
||||
const { getByText, getByLabelText, queryByLabelText } = render(
|
||||
<WrappedComponent onFieldBlur={undefined} />
|
||||
);
|
||||
const monitorType = getByLabelText('Monitor Type') as HTMLInputElement;
|
||||
const url = getByLabelText('URL') as HTMLInputElement;
|
||||
const proxyUrl = getByLabelText('Proxy URL') as HTMLInputElement;
|
||||
|
@ -366,4 +380,19 @@ describe('<CustomFields />', () => {
|
|||
expect(enabled).not.toBeChecked();
|
||||
});
|
||||
});
|
||||
|
||||
it('calls onFieldBlur on fields', () => {
|
||||
onFieldBlurMock = jest.fn();
|
||||
const { queryByLabelText } = render(
|
||||
<WrappedComponent
|
||||
dataStreams={[DataStream.HTTP, DataStream.TCP, DataStream.ICMP]}
|
||||
onFieldBlur={onFieldBlurMock}
|
||||
/>
|
||||
);
|
||||
|
||||
const monitorTypeSelect = queryByLabelText('Monitor Type') as HTMLInputElement;
|
||||
fireEvent.click(monitorTypeSelect);
|
||||
fireEvent.blur(monitorTypeSelect);
|
||||
expect(onFieldBlurMock).toHaveBeenCalledWith(ConfigKey.MONITOR_TYPE);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -37,6 +37,7 @@ interface Props {
|
|||
children?: React.ReactNode;
|
||||
appendAdvancedFields?: React.ReactNode;
|
||||
minColumnWidth?: string;
|
||||
onFieldBlur?: (field: ConfigKey) => void;
|
||||
}
|
||||
|
||||
const dataStreamToString = [
|
||||
|
@ -55,7 +56,7 @@ const dataStreamToString = [
|
|||
];
|
||||
|
||||
export const CustomFields = memo<Props>(
|
||||
({ validate, dataStreams = [], children, appendAdvancedFields, minColumnWidth }) => {
|
||||
({ validate, dataStreams = [], children, appendAdvancedFields, minColumnWidth, onFieldBlur }) => {
|
||||
const { monitorType, setMonitorType, isTLSEnabled, setIsTLSEnabled, isEditable } =
|
||||
usePolicyConfigContext();
|
||||
|
||||
|
@ -71,13 +72,24 @@ export const CustomFields = memo<Props>(
|
|||
const renderSimpleFields = (type: DataStream) => {
|
||||
switch (type) {
|
||||
case DataStream.HTTP:
|
||||
return <HTTPSimpleFields validate={validate} />;
|
||||
return (
|
||||
<HTTPSimpleFields validate={validate} onFieldBlur={(field) => onFieldBlur?.(field)} />
|
||||
);
|
||||
case DataStream.ICMP:
|
||||
return <ICMPSimpleFields validate={validate} />;
|
||||
return (
|
||||
<ICMPSimpleFields validate={validate} onFieldBlur={(field) => onFieldBlur?.(field)} />
|
||||
);
|
||||
case DataStream.TCP:
|
||||
return <TCPSimpleFields validate={validate} />;
|
||||
return (
|
||||
<TCPSimpleFields validate={validate} onFieldBlur={(field) => onFieldBlur?.(field)} />
|
||||
);
|
||||
case DataStream.BROWSER:
|
||||
return <BrowserSimpleFields validate={validate} />;
|
||||
return (
|
||||
<BrowserSimpleFields
|
||||
validate={validate}
|
||||
onFieldBlur={(field) => onFieldBlur?.(field)}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
@ -132,6 +144,7 @@ export const CustomFields = memo<Props>(
|
|||
options={dataStreamOptions}
|
||||
value={monitorType}
|
||||
onChange={(event) => setMonitorType(event.target.value as DataStream)}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.MONITOR_TYPE)}
|
||||
data-test-subj="syntheticsMonitorTypeField"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
@ -204,17 +217,25 @@ export const CustomFields = memo<Props>(
|
|||
)}
|
||||
<EuiSpacer size="m" />
|
||||
{isHTTP && (
|
||||
<HTTPAdvancedFields validate={validate} minColumnWidth={minColumnWidth}>
|
||||
<HTTPAdvancedFields
|
||||
validate={validate}
|
||||
minColumnWidth={minColumnWidth}
|
||||
onFieldBlur={onFieldBlur}
|
||||
>
|
||||
{appendAdvancedFields}
|
||||
</HTTPAdvancedFields>
|
||||
)}
|
||||
{isTCP && (
|
||||
<TCPAdvancedFields minColumnWidth={minColumnWidth}>
|
||||
<TCPAdvancedFields minColumnWidth={minColumnWidth} onFieldBlur={onFieldBlur}>
|
||||
{appendAdvancedFields}
|
||||
</TCPAdvancedFields>
|
||||
)}
|
||||
{isBrowser && (
|
||||
<BrowserAdvancedFields validate={validate} minColumnWidth={minColumnWidth}>
|
||||
<BrowserAdvancedFields
|
||||
validate={validate}
|
||||
minColumnWidth={minColumnWidth}
|
||||
onFieldBlur={onFieldBlur}
|
||||
>
|
||||
{appendAdvancedFields}
|
||||
</BrowserAdvancedFields>
|
||||
)}
|
||||
|
|
|
@ -13,8 +13,13 @@ import { Mode } from './types';
|
|||
|
||||
describe('<HeaderField />', () => {
|
||||
const onChange = jest.fn();
|
||||
const onBlur = jest.fn();
|
||||
const defaultValue = {};
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('renders HeaderField', () => {
|
||||
const { getByText, getByTestId } = render(
|
||||
<HeaderField defaultValue={{ sample: 'header' }} onChange={onChange} />
|
||||
|
@ -28,6 +33,20 @@ describe('<HeaderField />', () => {
|
|||
expect(value.value).toEqual('header');
|
||||
});
|
||||
|
||||
it('calls onBlur', () => {
|
||||
const { getByTestId } = render(
|
||||
<HeaderField defaultValue={{ sample: 'header' }} onChange={onChange} onBlur={onBlur} />
|
||||
);
|
||||
|
||||
const key = getByTestId('keyValuePairsKey0') as HTMLInputElement;
|
||||
const value = getByTestId('keyValuePairsValue0') as HTMLInputElement;
|
||||
|
||||
fireEvent.blur(key);
|
||||
fireEvent.blur(value);
|
||||
|
||||
expect(onBlur).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('formats headers and handles onChange', async () => {
|
||||
const { getByTestId, getByText } = render(
|
||||
<HeaderField defaultValue={defaultValue} onChange={onChange} />
|
||||
|
|
|
@ -15,6 +15,7 @@ interface Props {
|
|||
contentMode?: Mode;
|
||||
defaultValue: Record<string, string>;
|
||||
onChange: (value: Record<string, string>) => void;
|
||||
onBlur?: () => void;
|
||||
'data-test-subj'?: string;
|
||||
}
|
||||
|
||||
|
@ -22,6 +23,7 @@ export const HeaderField = ({
|
|||
contentMode,
|
||||
defaultValue,
|
||||
onChange,
|
||||
onBlur,
|
||||
'data-test-subj': dataTestSubj,
|
||||
}: Props) => {
|
||||
const defaultValueKeys = Object.keys(defaultValue).filter((key) => key !== 'Content-Type'); // Content-Type is a secret header we hide from the user
|
||||
|
@ -61,6 +63,7 @@ export const HeaderField = ({
|
|||
}
|
||||
defaultPairs={headers}
|
||||
onChange={setHeaders}
|
||||
onBlur={() => onBlur?.()}
|
||||
data-test-subj={dataTestSubj}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -5,22 +5,22 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { fireEvent } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { render } from '../../../lib/helper/rtl_helpers';
|
||||
import { HTTPAdvancedFields } from './advanced_fields';
|
||||
import {
|
||||
defaultHTTPAdvancedFields as defaultConfig,
|
||||
HTTPAdvancedFieldsContextProvider,
|
||||
} from '../contexts';
|
||||
import {
|
||||
ConfigKey,
|
||||
DataStream,
|
||||
HTTPMethod,
|
||||
HTTPAdvancedFields as HTTPAdvancedFieldsType,
|
||||
HTTPMethod,
|
||||
Validation,
|
||||
} from '../types';
|
||||
import {
|
||||
HTTPAdvancedFieldsContextProvider,
|
||||
defaultHTTPAdvancedFields as defaultConfig,
|
||||
} from '../contexts';
|
||||
import { validate as centralValidation } from '../validation';
|
||||
import { HTTPAdvancedFields } from './advanced_fields';
|
||||
|
||||
jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({
|
||||
htmlIdGenerator: () => () => `id-${Math.random()}`,
|
||||
|
@ -46,6 +46,8 @@ jest.mock('../../../../../../../src/plugins/kibana_react/public', () => {
|
|||
const defaultValidation = centralValidation[DataStream.HTTP];
|
||||
|
||||
describe('<HTTPAdvancedFields />', () => {
|
||||
const onFieldBlur = jest.fn();
|
||||
|
||||
const WrappedComponent = ({
|
||||
defaultValues,
|
||||
validate = defaultValidation,
|
||||
|
@ -57,7 +59,9 @@ describe('<HTTPAdvancedFields />', () => {
|
|||
}) => {
|
||||
return (
|
||||
<HTTPAdvancedFieldsContextProvider defaultValues={defaultValues}>
|
||||
<HTTPAdvancedFields validate={validate}>{children}</HTTPAdvancedFields>
|
||||
<HTTPAdvancedFields validate={validate} onFieldBlur={onFieldBlur}>
|
||||
{children}
|
||||
</HTTPAdvancedFields>
|
||||
</HTTPAdvancedFieldsContextProvider>
|
||||
);
|
||||
};
|
||||
|
@ -129,6 +133,20 @@ describe('<HTTPAdvancedFields />', () => {
|
|||
expect(indexResponseHeaders.checked).toBe(false);
|
||||
});
|
||||
|
||||
it('calls onBlur', () => {
|
||||
const { getByLabelText } = render(<WrappedComponent />);
|
||||
|
||||
const username = getByLabelText('Username') as HTMLInputElement;
|
||||
const requestMethod = getByLabelText('Request method') as HTMLInputElement;
|
||||
const indexResponseBody = getByLabelText('Index response body') as HTMLInputElement;
|
||||
|
||||
[username, requestMethod, indexResponseBody].forEach((field) => fireEvent.blur(field));
|
||||
|
||||
expect(onFieldBlur).toHaveBeenCalledWith(ConfigKey.USERNAME);
|
||||
expect(onFieldBlur).toHaveBeenCalledWith(ConfigKey.REQUEST_METHOD_CHECK);
|
||||
expect(onFieldBlur).toHaveBeenCalledWith(ConfigKey.RESPONSE_BODY_INDEX);
|
||||
});
|
||||
|
||||
it('renders upstream fields', () => {
|
||||
const upstreamFieldsText = 'Monitor Advanced field section';
|
||||
const { getByText } = render(<WrappedComponent>{upstreamFieldsText}</WrappedComponent>);
|
||||
|
|
|
@ -34,442 +34,457 @@ interface Props {
|
|||
validate: Validation;
|
||||
children?: React.ReactNode;
|
||||
minColumnWidth?: string;
|
||||
onFieldBlur?: (field: ConfigKey) => void;
|
||||
}
|
||||
|
||||
export const HTTPAdvancedFields = memo<Props>(({ validate, children, minColumnWidth }) => {
|
||||
const { fields, setFields } = useHTTPAdvancedFieldsContext();
|
||||
const handleInputChange = useCallback(
|
||||
({ value, configKey }: { value: unknown; configKey: ConfigKey }) => {
|
||||
setFields((prevFields) => ({ ...prevFields, [configKey]: value }));
|
||||
},
|
||||
[setFields]
|
||||
);
|
||||
export const HTTPAdvancedFields = memo<Props>(
|
||||
({ validate, children, minColumnWidth, onFieldBlur }) => {
|
||||
const { fields, setFields } = useHTTPAdvancedFieldsContext();
|
||||
const handleInputChange = useCallback(
|
||||
({ value, configKey }: { value: unknown; configKey: ConfigKey }) => {
|
||||
setFields((prevFields) => ({ ...prevFields, [configKey]: value }));
|
||||
},
|
||||
[setFields]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiAccordion
|
||||
id="uptimeFleetHttpAdvancedOptions"
|
||||
buttonContent={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions"
|
||||
defaultMessage="Advanced HTTP options"
|
||||
/>
|
||||
}
|
||||
data-test-subj="syntheticsHTTPAdvancedFieldsAccordion"
|
||||
>
|
||||
<EuiSpacer size="xl" />
|
||||
<DescribedFormGroupWithWrap
|
||||
minColumnWidth={minColumnWidth}
|
||||
title={
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestConfiguration.title"
|
||||
defaultMessage="Request configuration"
|
||||
/>
|
||||
</h4>
|
||||
}
|
||||
description={
|
||||
return (
|
||||
<EuiAccordion
|
||||
id="uptimeFleetHttpAdvancedOptions"
|
||||
buttonContent={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestConfiguration.description"
|
||||
defaultMessage="Configure an optional request to send to the remote host including method, body, and headers."
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions"
|
||||
defaultMessage="Advanced HTTP options"
|
||||
/>
|
||||
}
|
||||
data-test-subj="httpAdvancedFieldsSection"
|
||||
data-test-subj="syntheticsHTTPAdvancedFieldsAccordion"
|
||||
>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.username.label"
|
||||
defaultMessage="Username"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.username.helpText"
|
||||
defaultMessage="Username for authenticating with the server."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFieldText
|
||||
value={fields[ConfigKey.USERNAME]}
|
||||
onChange={(event) =>
|
||||
handleInputChange({
|
||||
value: event.target.value,
|
||||
configKey: ConfigKey.USERNAME,
|
||||
})
|
||||
}
|
||||
data-test-subj="syntheticsUsername"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.password.label"
|
||||
defaultMessage="Password"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.password.helpText"
|
||||
defaultMessage="Password for authenticating with the server."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFieldPassword
|
||||
value={fields[ConfigKey.PASSWORD]}
|
||||
onChange={(event) =>
|
||||
handleInputChange({
|
||||
value: event.target.value,
|
||||
configKey: ConfigKey.PASSWORD,
|
||||
})
|
||||
}
|
||||
data-test-subj="syntheticsPassword"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.proxyURL.http.label"
|
||||
defaultMessage="Proxy URL"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.proxyUrl.http.helpText"
|
||||
defaultMessage="HTTP proxy URL."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFieldText
|
||||
value={fields[ConfigKey.PROXY_URL]}
|
||||
onChange={(event) =>
|
||||
handleInputChange({
|
||||
value: event.target.value,
|
||||
configKey: ConfigKey.PROXY_URL,
|
||||
})
|
||||
}
|
||||
data-test-subj="syntheticsProxyUrl"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestConfiguration.requestMethod.label"
|
||||
defaultMessage="Request method"
|
||||
/>
|
||||
}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestMethod.helpText"
|
||||
defaultMessage="The HTTP method to use."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiSelect
|
||||
options={requestMethodOptions}
|
||||
value={fields[ConfigKey.REQUEST_METHOD_CHECK]}
|
||||
onChange={(event) =>
|
||||
handleInputChange({
|
||||
value: event.target.value,
|
||||
configKey: ConfigKey.REQUEST_METHOD_CHECK,
|
||||
})
|
||||
}
|
||||
data-test-subj="syntheticsRequestMethod"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestConfiguration.requestHeaders"
|
||||
defaultMessage="Request headers"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
isInvalid={!!validate[ConfigKey.REQUEST_HEADERS_CHECK]?.(fields)}
|
||||
error={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestHeadersField.error"
|
||||
defaultMessage="Header key must be a valid HTTP token."
|
||||
/>
|
||||
}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestHeadersField.helpText"
|
||||
defaultMessage="A dictionary of additional HTTP headers to send. By default the client will set the User-Agent header to identify itself."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<HeaderField
|
||||
contentMode={
|
||||
fields[ConfigKey.REQUEST_BODY_CHECK].value
|
||||
? fields[ConfigKey.REQUEST_BODY_CHECK].type
|
||||
: undefined
|
||||
} // only pass contentMode if the request body is truthy
|
||||
defaultValue={fields[ConfigKey.REQUEST_HEADERS_CHECK]}
|
||||
onChange={useCallback(
|
||||
(value) =>
|
||||
handleInputChange({
|
||||
value,
|
||||
configKey: ConfigKey.REQUEST_HEADERS_CHECK,
|
||||
}),
|
||||
[handleInputChange]
|
||||
)}
|
||||
data-test-subj="syntheticsRequestHeaders"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestConfiguration.requestBody"
|
||||
defaultMessage="Request body"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestBody.helpText"
|
||||
defaultMessage="Request body content."
|
||||
/>
|
||||
}
|
||||
fullWidth
|
||||
>
|
||||
<RequestBodyField
|
||||
value={fields[ConfigKey.REQUEST_BODY_CHECK].value}
|
||||
type={fields[ConfigKey.REQUEST_BODY_CHECK].type}
|
||||
onChange={useCallback(
|
||||
(value) =>
|
||||
handleInputChange({
|
||||
value,
|
||||
configKey: ConfigKey.REQUEST_BODY_CHECK,
|
||||
}),
|
||||
[handleInputChange]
|
||||
)}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</DescribedFormGroupWithWrap>
|
||||
<EuiSpacer size="xl" />
|
||||
<DescribedFormGroupWithWrap
|
||||
minColumnWidth={minColumnWidth}
|
||||
title={
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseConfiguration.title"
|
||||
defaultMessage="Response configuration"
|
||||
/>
|
||||
</h4>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseConfiguration.description"
|
||||
defaultMessage="Control the indexing of the HTTP response contents."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFormRow
|
||||
helpText={
|
||||
<>
|
||||
<EuiSpacer size="xl" />
|
||||
<DescribedFormGroupWithWrap
|
||||
minColumnWidth={minColumnWidth}
|
||||
title={
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.indexResponseHeaders.helpText"
|
||||
defaultMessage="Controls the indexing of the HTTP response headers to "
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestConfiguration.title"
|
||||
defaultMessage="Request configuration"
|
||||
/>
|
||||
<EuiCode>http.response.body.headers</EuiCode>
|
||||
</>
|
||||
</h4>
|
||||
}
|
||||
data-test-subj="syntheticsIndexResponseHeaders"
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestConfiguration.description"
|
||||
defaultMessage="Configure an optional request to send to the remote host including method, body, and headers."
|
||||
/>
|
||||
}
|
||||
data-test-subj="httpAdvancedFieldsSection"
|
||||
>
|
||||
<EuiCheckbox
|
||||
id={'uptimeFleetIndexResponseHeaders'}
|
||||
checked={fields[ConfigKey.RESPONSE_HEADERS_INDEX]}
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseConfig.indexResponseHeaders"
|
||||
defaultMessage="Index response headers"
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.username.label"
|
||||
defaultMessage="Username"
|
||||
/>
|
||||
}
|
||||
onChange={(event) =>
|
||||
handleInputChange({
|
||||
value: event.target.checked,
|
||||
configKey: ConfigKey.RESPONSE_HEADERS_INDEX,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
helpText={
|
||||
<>
|
||||
labelAppend={<OptionalLabel />}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.indexResponseBody.helpText"
|
||||
defaultMessage="Controls the indexing of the HTTP response body contents to "
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.username.helpText"
|
||||
defaultMessage="Username for authenticating with the server."
|
||||
/>
|
||||
<EuiCode>http.response.body.contents</EuiCode>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<EuiFieldText
|
||||
value={fields[ConfigKey.USERNAME]}
|
||||
onChange={(event) =>
|
||||
handleInputChange({
|
||||
value: event.target.value,
|
||||
configKey: ConfigKey.USERNAME,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.USERNAME)}
|
||||
data-test-subj="syntheticsUsername"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.password.label"
|
||||
defaultMessage="Password"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.password.helpText"
|
||||
defaultMessage="Password for authenticating with the server."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFieldPassword
|
||||
value={fields[ConfigKey.PASSWORD]}
|
||||
onChange={(event) =>
|
||||
handleInputChange({
|
||||
value: event.target.value,
|
||||
configKey: ConfigKey.PASSWORD,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.PASSWORD)}
|
||||
data-test-subj="syntheticsPassword"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.proxyURL.http.label"
|
||||
defaultMessage="Proxy URL"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.proxyUrl.http.helpText"
|
||||
defaultMessage="HTTP proxy URL."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFieldText
|
||||
value={fields[ConfigKey.PROXY_URL]}
|
||||
onChange={(event) =>
|
||||
handleInputChange({
|
||||
value: event.target.value,
|
||||
configKey: ConfigKey.PROXY_URL,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.PROXY_URL)}
|
||||
data-test-subj="syntheticsProxyUrl"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestConfiguration.requestMethod.label"
|
||||
defaultMessage="Request method"
|
||||
/>
|
||||
}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestMethod.helpText"
|
||||
defaultMessage="The HTTP method to use."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiSelect
|
||||
options={requestMethodOptions}
|
||||
value={fields[ConfigKey.REQUEST_METHOD_CHECK]}
|
||||
onChange={(event) =>
|
||||
handleInputChange({
|
||||
value: event.target.value,
|
||||
configKey: ConfigKey.REQUEST_METHOD_CHECK,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.REQUEST_METHOD_CHECK)}
|
||||
data-test-subj="syntheticsRequestMethod"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestConfiguration.requestHeaders"
|
||||
defaultMessage="Request headers"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
isInvalid={!!validate[ConfigKey.REQUEST_HEADERS_CHECK]?.(fields)}
|
||||
error={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestHeadersField.error"
|
||||
defaultMessage="Header key must be a valid HTTP token."
|
||||
/>
|
||||
}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestHeadersField.helpText"
|
||||
defaultMessage="A dictionary of additional HTTP headers to send. By default the client will set the User-Agent header to identify itself."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<HeaderField
|
||||
contentMode={
|
||||
fields[ConfigKey.REQUEST_BODY_CHECK].value
|
||||
? fields[ConfigKey.REQUEST_BODY_CHECK].type
|
||||
: undefined
|
||||
} // only pass contentMode if the request body is truthy
|
||||
defaultValue={fields[ConfigKey.REQUEST_HEADERS_CHECK]}
|
||||
onChange={useCallback(
|
||||
(value) =>
|
||||
handleInputChange({
|
||||
value,
|
||||
configKey: ConfigKey.REQUEST_HEADERS_CHECK,
|
||||
}),
|
||||
[handleInputChange]
|
||||
)}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.REQUEST_HEADERS_CHECK)}
|
||||
data-test-subj="syntheticsRequestHeaders"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestConfiguration.requestBody"
|
||||
defaultMessage="Request body"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.requestBody.helpText"
|
||||
defaultMessage="Request body content."
|
||||
/>
|
||||
}
|
||||
fullWidth
|
||||
>
|
||||
<RequestBodyField
|
||||
value={fields[ConfigKey.REQUEST_BODY_CHECK].value}
|
||||
type={fields[ConfigKey.REQUEST_BODY_CHECK].type}
|
||||
onChange={useCallback(
|
||||
(value) =>
|
||||
handleInputChange({
|
||||
value,
|
||||
configKey: ConfigKey.REQUEST_BODY_CHECK,
|
||||
}),
|
||||
[handleInputChange]
|
||||
)}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.REQUEST_BODY_CHECK)}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</DescribedFormGroupWithWrap>
|
||||
<EuiSpacer size="xl" />
|
||||
<DescribedFormGroupWithWrap
|
||||
minColumnWidth={minColumnWidth}
|
||||
title={
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseConfiguration.title"
|
||||
defaultMessage="Response configuration"
|
||||
/>
|
||||
</h4>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseConfiguration.description"
|
||||
defaultMessage="Control the indexing of the HTTP response contents."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<ResponseBodyIndexField
|
||||
defaultValue={fields[ConfigKey.RESPONSE_BODY_INDEX]}
|
||||
onChange={useCallback(
|
||||
(policy) =>
|
||||
handleInputChange({ value: policy, configKey: ConfigKey.RESPONSE_BODY_INDEX }),
|
||||
[handleInputChange]
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFormRow
|
||||
helpText={
|
||||
<>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.indexResponseHeaders.helpText"
|
||||
defaultMessage="Controls the indexing of the HTTP response headers to "
|
||||
/>
|
||||
<EuiCode>http.response.body.headers</EuiCode>
|
||||
</>
|
||||
}
|
||||
data-test-subj="syntheticsIndexResponseHeaders"
|
||||
>
|
||||
<EuiCheckbox
|
||||
id={'uptimeFleetIndexResponseHeaders'}
|
||||
checked={fields[ConfigKey.RESPONSE_HEADERS_INDEX]}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseConfig.indexResponseHeaders"
|
||||
defaultMessage="Index response headers"
|
||||
/>
|
||||
}
|
||||
onChange={(event) =>
|
||||
handleInputChange({
|
||||
value: event.target.checked,
|
||||
configKey: ConfigKey.RESPONSE_HEADERS_INDEX,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.RESPONSE_HEADERS_INDEX)}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
helpText={
|
||||
<>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.indexResponseBody.helpText"
|
||||
defaultMessage="Controls the indexing of the HTTP response body contents to "
|
||||
/>
|
||||
<EuiCode>http.response.body.contents</EuiCode>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<ResponseBodyIndexField
|
||||
defaultValue={fields[ConfigKey.RESPONSE_BODY_INDEX]}
|
||||
onChange={useCallback(
|
||||
(policy) =>
|
||||
handleInputChange({ value: policy, configKey: ConfigKey.RESPONSE_BODY_INDEX }),
|
||||
[handleInputChange]
|
||||
)}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.RESPONSE_BODY_INDEX)}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</DescribedFormGroupWithWrap>
|
||||
<DescribedFormGroupWithWrap
|
||||
minColumnWidth={minColumnWidth}
|
||||
title={
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseChecks.title"
|
||||
defaultMessage="Response checks"
|
||||
/>
|
||||
</h4>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseChecks.description"
|
||||
defaultMessage="Configure the expected HTTP response."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseChecks.responseStatusCheck.label"
|
||||
defaultMessage="Check response status equals"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
isInvalid={!!validate[ConfigKey.RESPONSE_STATUS_CHECK]?.(fields)}
|
||||
error={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseChecks.responseStatusCheck.error"
|
||||
defaultMessage="Status code must contain digits only."
|
||||
/>
|
||||
}
|
||||
helpText={i18n.translate(
|
||||
'xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseChecks.responseStatusCheck.helpText',
|
||||
{
|
||||
defaultMessage:
|
||||
'A list of expected status codes. Press enter to add a new code. 4xx and 5xx codes are considered down by default. Other codes are considered up.',
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</DescribedFormGroupWithWrap>
|
||||
<DescribedFormGroupWithWrap
|
||||
minColumnWidth={minColumnWidth}
|
||||
title={
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseChecks.title"
|
||||
defaultMessage="Response checks"
|
||||
/>
|
||||
</h4>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseChecks.description"
|
||||
defaultMessage="Configure the expected HTTP response."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseChecks.responseStatusCheck.label"
|
||||
defaultMessage="Check response status equals"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
isInvalid={!!validate[ConfigKey.RESPONSE_STATUS_CHECK]?.(fields)}
|
||||
error={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseChecks.responseStatusCheck.error"
|
||||
defaultMessage="Status code must contain digits only."
|
||||
/>
|
||||
}
|
||||
helpText={i18n.translate(
|
||||
'xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseChecks.responseStatusCheck.helpText',
|
||||
{
|
||||
defaultMessage:
|
||||
'A list of expected status codes. Press enter to add a new code. 4xx and 5xx codes are considered down by default. Other codes are considered up.',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<ComboBox
|
||||
selectedOptions={fields[ConfigKey.RESPONSE_STATUS_CHECK]}
|
||||
onChange={(value) =>
|
||||
handleInputChange({
|
||||
value,
|
||||
configKey: ConfigKey.RESPONSE_STATUS_CHECK,
|
||||
})
|
||||
}
|
||||
data-test-subj="syntheticsResponseStatusCheck"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseChecks.checkResponseHeadersContain"
|
||||
defaultMessage="Check response headers contain"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
isInvalid={!!validate[ConfigKey.RESPONSE_HEADERS_CHECK]?.(fields)}
|
||||
error={[
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseHeadersField.error"
|
||||
defaultMessage="Header key must be a valid HTTP token."
|
||||
/>,
|
||||
]}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseHeadersField.helpText"
|
||||
defaultMessage="A list of expected response headers."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<HeaderField
|
||||
defaultValue={fields[ConfigKey.RESPONSE_HEADERS_CHECK]}
|
||||
onChange={useCallback(
|
||||
(value) =>
|
||||
>
|
||||
<ComboBox
|
||||
selectedOptions={fields[ConfigKey.RESPONSE_STATUS_CHECK]}
|
||||
onChange={(value) =>
|
||||
handleInputChange({
|
||||
value,
|
||||
configKey: ConfigKey.RESPONSE_HEADERS_CHECK,
|
||||
}),
|
||||
[handleInputChange]
|
||||
)}
|
||||
data-test-subj="syntheticsResponseHeaders"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseCheckPositive.label"
|
||||
defaultMessage="Check response body contains"
|
||||
configKey: ConfigKey.RESPONSE_STATUS_CHECK,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.RESPONSE_STATUS_CHECK)}
|
||||
data-test-subj="syntheticsResponseStatusCheck"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
helpText={i18n.translate(
|
||||
'xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseBodyCheckPositive.helpText',
|
||||
{
|
||||
defaultMessage:
|
||||
'A list of regular expressions to match the body output. Press enter to add a new expression. Only a single expression needs to match.',
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseChecks.checkResponseHeadersContain"
|
||||
defaultMessage="Check response headers contain"
|
||||
/>
|
||||
}
|
||||
)}
|
||||
>
|
||||
<ComboBox
|
||||
selectedOptions={fields[ConfigKey.RESPONSE_BODY_CHECK_POSITIVE]}
|
||||
onChange={useCallback(
|
||||
(value) =>
|
||||
handleInputChange({
|
||||
value,
|
||||
configKey: ConfigKey.RESPONSE_BODY_CHECK_POSITIVE,
|
||||
}),
|
||||
[handleInputChange]
|
||||
)}
|
||||
data-test-subj="syntheticsResponseBodyCheckPositive"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseCheckNegative.label"
|
||||
defaultMessage="Check response body does not contain"
|
||||
labelAppend={<OptionalLabel />}
|
||||
isInvalid={!!validate[ConfigKey.RESPONSE_HEADERS_CHECK]?.(fields)}
|
||||
error={[
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseHeadersField.error"
|
||||
defaultMessage="Header key must be a valid HTTP token."
|
||||
/>,
|
||||
]}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseHeadersField.helpText"
|
||||
defaultMessage="A list of expected response headers."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<HeaderField
|
||||
defaultValue={fields[ConfigKey.RESPONSE_HEADERS_CHECK]}
|
||||
onChange={useCallback(
|
||||
(value) =>
|
||||
handleInputChange({
|
||||
value,
|
||||
configKey: ConfigKey.RESPONSE_HEADERS_CHECK,
|
||||
}),
|
||||
[handleInputChange]
|
||||
)}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.RESPONSE_HEADERS_CHECK)}
|
||||
data-test-subj="syntheticsResponseHeaders"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
helpText={i18n.translate(
|
||||
'xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseBodyCheckNegative.helpText',
|
||||
{
|
||||
defaultMessage:
|
||||
'A list of regular expressions to match the the body output negatively. Press enter to add a new expression. Return match failed if single expression matches.',
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseCheckPositive.label"
|
||||
defaultMessage="Check response body contains"
|
||||
/>
|
||||
}
|
||||
)}
|
||||
>
|
||||
<ComboBox
|
||||
selectedOptions={fields[ConfigKey.RESPONSE_BODY_CHECK_NEGATIVE]}
|
||||
onChange={useCallback(
|
||||
(value) =>
|
||||
handleInputChange({
|
||||
value,
|
||||
configKey: ConfigKey.RESPONSE_BODY_CHECK_NEGATIVE,
|
||||
}),
|
||||
[handleInputChange]
|
||||
labelAppend={<OptionalLabel />}
|
||||
helpText={i18n.translate(
|
||||
'xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseBodyCheckPositive.helpText',
|
||||
{
|
||||
defaultMessage:
|
||||
'A list of regular expressions to match the body output. Press enter to add a new expression. Only a single expression needs to match.',
|
||||
}
|
||||
)}
|
||||
data-test-subj="syntheticsResponseBodyCheckNegative"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</DescribedFormGroupWithWrap>
|
||||
{children}
|
||||
</EuiAccordion>
|
||||
);
|
||||
});
|
||||
>
|
||||
<ComboBox
|
||||
selectedOptions={fields[ConfigKey.RESPONSE_BODY_CHECK_POSITIVE]}
|
||||
onChange={useCallback(
|
||||
(value) =>
|
||||
handleInputChange({
|
||||
value,
|
||||
configKey: ConfigKey.RESPONSE_BODY_CHECK_POSITIVE,
|
||||
}),
|
||||
[handleInputChange]
|
||||
)}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.RESPONSE_BODY_CHECK_POSITIVE)}
|
||||
data-test-subj="syntheticsResponseBodyCheckPositive"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseCheckNegative.label"
|
||||
defaultMessage="Check response body does not contain"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
helpText={i18n.translate(
|
||||
'xpack.uptime.createPackagePolicy.stepConfigure.httpAdvancedOptions.responseBodyCheckNegative.helpText',
|
||||
{
|
||||
defaultMessage:
|
||||
'A list of regular expressions to match the the body output negatively. Press enter to add a new expression. Return match failed if single expression matches.',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<ComboBox
|
||||
selectedOptions={fields[ConfigKey.RESPONSE_BODY_CHECK_NEGATIVE]}
|
||||
onChange={useCallback(
|
||||
(value) =>
|
||||
handleInputChange({
|
||||
value,
|
||||
configKey: ConfigKey.RESPONSE_BODY_CHECK_NEGATIVE,
|
||||
}),
|
||||
[handleInputChange]
|
||||
)}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.RESPONSE_BODY_CHECK_NEGATIVE)}
|
||||
data-test-subj="syntheticsResponseBodyCheckNegative"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</DescribedFormGroupWithWrap>
|
||||
{children}
|
||||
</EuiAccordion>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
const requestMethodOptions = Object.values(HTTPMethod).map((method) => ({
|
||||
value: method,
|
||||
|
|
|
@ -5,20 +5,21 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { memo, useCallback } from 'react';
|
||||
import { EuiFieldNumber, EuiFieldText, EuiFormRow } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiFormRow, EuiFieldText, EuiFieldNumber } from '@elastic/eui';
|
||||
import { ConfigKey, Validation } from '../types';
|
||||
import React, { memo, useCallback } from 'react';
|
||||
import { SimpleFieldsWrapper } from '../common/simple_fields_wrapper';
|
||||
import { useHTTPSimpleFieldsContext } from '../contexts';
|
||||
import { OptionalLabel } from '../optional_label';
|
||||
import { ScheduleField } from '../schedule_field';
|
||||
import { SimpleFieldsWrapper } from '../common/simple_fields_wrapper';
|
||||
import { ConfigKey, Validation } from '../types';
|
||||
|
||||
interface Props {
|
||||
validate: Validation;
|
||||
onFieldBlur: (field: ConfigKey) => void; // To propagate blurred state up to parents
|
||||
}
|
||||
|
||||
export const HTTPSimpleFields = memo<Props>(({ validate }) => {
|
||||
export const HTTPSimpleFields = memo<Props>(({ validate, onFieldBlur }) => {
|
||||
const { fields, setFields } = useHTTPSimpleFieldsContext();
|
||||
const handleInputChange = useCallback(
|
||||
({ value, configKey }: { value: unknown; configKey: ConfigKey }) => {
|
||||
|
@ -28,7 +29,12 @@ export const HTTPSimpleFields = memo<Props>(({ validate }) => {
|
|||
);
|
||||
|
||||
return (
|
||||
<SimpleFieldsWrapper fields={fields} validate={validate} onInputChange={handleInputChange}>
|
||||
<SimpleFieldsWrapper
|
||||
fields={fields}
|
||||
validate={validate}
|
||||
onInputChange={handleInputChange}
|
||||
onFieldBlur={onFieldBlur}
|
||||
>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
|
@ -49,6 +55,7 @@ export const HTTPSimpleFields = memo<Props>(({ validate }) => {
|
|||
onChange={(event) =>
|
||||
handleInputChange({ value: event.target.value, configKey: ConfigKey.URLS })
|
||||
}
|
||||
onBlur={() => onFieldBlur(ConfigKey.URLS)}
|
||||
data-test-subj="syntheticsUrlField"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
@ -75,6 +82,7 @@ export const HTTPSimpleFields = memo<Props>(({ validate }) => {
|
|||
configKey: ConfigKey.SCHEDULE,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur(ConfigKey.SCHEDULE)}
|
||||
number={fields[ConfigKey.SCHEDULE].number}
|
||||
unit={fields[ConfigKey.SCHEDULE].unit}
|
||||
/>
|
||||
|
@ -110,6 +118,7 @@ export const HTTPSimpleFields = memo<Props>(({ validate }) => {
|
|||
configKey: ConfigKey.MAX_REDIRECTS,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur(ConfigKey.MAX_REDIRECTS)}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</SimpleFieldsWrapper>
|
||||
|
|
|
@ -16,9 +16,10 @@ import { SimpleFieldsWrapper } from '../common/simple_fields_wrapper';
|
|||
|
||||
interface Props {
|
||||
validate: Validation;
|
||||
onFieldBlur: (field: ConfigKey) => void; // To propagate blurred state up to parents
|
||||
}
|
||||
|
||||
export const ICMPSimpleFields = memo<Props>(({ validate }) => {
|
||||
export const ICMPSimpleFields = memo<Props>(({ validate, onFieldBlur }) => {
|
||||
const { fields, setFields } = useICMPSimpleFieldsContext();
|
||||
const handleInputChange = useCallback(
|
||||
({ value, configKey }: { value: unknown; configKey: ConfigKey }) => {
|
||||
|
@ -28,7 +29,12 @@ export const ICMPSimpleFields = memo<Props>(({ validate }) => {
|
|||
);
|
||||
|
||||
return (
|
||||
<SimpleFieldsWrapper fields={fields} validate={validate} onInputChange={handleInputChange}>
|
||||
<SimpleFieldsWrapper
|
||||
fields={fields}
|
||||
validate={validate}
|
||||
onInputChange={handleInputChange}
|
||||
onFieldBlur={onFieldBlur}
|
||||
>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
|
@ -52,6 +58,7 @@ export const ICMPSimpleFields = memo<Props>(({ validate }) => {
|
|||
configKey: ConfigKey.HOSTS,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur(ConfigKey.HOSTS)}
|
||||
data-test-subj="syntheticsICMPHostField"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
@ -78,6 +85,7 @@ export const ICMPSimpleFields = memo<Props>(({ validate }) => {
|
|||
configKey: ConfigKey.SCHEDULE,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur(ConfigKey.SCHEDULE)}
|
||||
number={fields[ConfigKey.SCHEDULE].number}
|
||||
unit={fields[ConfigKey.SCHEDULE].unit}
|
||||
/>
|
||||
|
@ -113,6 +121,7 @@ export const ICMPSimpleFields = memo<Props>(({ validate }) => {
|
|||
configKey: ConfigKey.WAIT,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur(ConfigKey.WAIT)}
|
||||
step={'any'}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
|
|
@ -14,10 +14,17 @@ import { ResponseBodyIndexPolicy } from './types';
|
|||
describe('<ResponseBodyIndexField/>', () => {
|
||||
const defaultDefaultValue = ResponseBodyIndexPolicy.ON_ERROR;
|
||||
const onChange = jest.fn();
|
||||
const onBlur = jest.fn();
|
||||
const WrappedComponent = ({ defaultValue = defaultDefaultValue }) => {
|
||||
return <ResponseBodyIndexField defaultValue={defaultValue} onChange={onChange} />;
|
||||
return (
|
||||
<ResponseBodyIndexField defaultValue={defaultValue} onChange={onChange} onBlur={onBlur} />
|
||||
);
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('renders ResponseBodyIndexField', () => {
|
||||
const { getByText, getByTestId } = render(<WrappedComponent />);
|
||||
const select = getByTestId('indexResponseBodyFieldSelect') as HTMLInputElement;
|
||||
|
@ -41,6 +48,17 @@ describe('<ResponseBodyIndexField/>', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('calls onBlur', async () => {
|
||||
const { getByTestId } = render(<WrappedComponent />);
|
||||
const select = getByTestId('indexResponseBodyFieldSelect') as HTMLInputElement;
|
||||
const newPolicy = ResponseBodyIndexPolicy.ALWAYS;
|
||||
|
||||
fireEvent.change(select, { target: { value: newPolicy } });
|
||||
fireEvent.blur(select);
|
||||
|
||||
expect(onBlur).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('handles checkbox change', async () => {
|
||||
const { getByTestId, getByLabelText } = render(<WrappedComponent />);
|
||||
const checkbox = getByLabelText('Index response body') as HTMLInputElement;
|
||||
|
|
|
@ -15,9 +15,10 @@ import { ResponseBodyIndexPolicy } from './types';
|
|||
interface Props {
|
||||
defaultValue: ResponseBodyIndexPolicy;
|
||||
onChange: (responseBodyIndexPolicy: ResponseBodyIndexPolicy) => void;
|
||||
onBlur?: () => void;
|
||||
}
|
||||
|
||||
export const ResponseBodyIndexField = ({ defaultValue, onChange }: Props) => {
|
||||
export const ResponseBodyIndexField = ({ defaultValue, onChange, onBlur }: Props) => {
|
||||
const [policy, setPolicy] = useState<ResponseBodyIndexPolicy>(
|
||||
defaultValue !== ResponseBodyIndexPolicy.NEVER ? defaultValue : ResponseBodyIndexPolicy.ON_ERROR
|
||||
);
|
||||
|
@ -52,6 +53,7 @@ export const ResponseBodyIndexField = ({ defaultValue, onChange }: Props) => {
|
|||
const checkedEvent = event.target.checked;
|
||||
setChecked(checkedEvent);
|
||||
}}
|
||||
onBlur={() => onBlur?.()}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
{checked && (
|
||||
|
@ -69,6 +71,7 @@ export const ResponseBodyIndexField = ({ defaultValue, onChange }: Props) => {
|
|||
onChange={(event) => {
|
||||
setPolicy(event.target.value as ResponseBodyIndexPolicy);
|
||||
}}
|
||||
onBlur={() => onBlur?.()}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import { fireEvent, waitFor } from '@testing-library/react';
|
||||
import { render } from '../../lib/helper/rtl_helpers';
|
||||
|
@ -12,6 +13,7 @@ import { KeyValuePairsField, Pair } from './key_value_field';
|
|||
|
||||
describe('<KeyValuePairsField />', () => {
|
||||
const onChange = jest.fn();
|
||||
const onBlur = jest.fn();
|
||||
const defaultDefaultValue = [['', '']] as Pair[];
|
||||
const WrappedComponent = ({
|
||||
defaultValue = defaultDefaultValue,
|
||||
|
@ -21,11 +23,16 @@ describe('<KeyValuePairsField />', () => {
|
|||
<KeyValuePairsField
|
||||
defaultPairs={defaultValue}
|
||||
onChange={onChange}
|
||||
onBlur={onBlur}
|
||||
addPairControlLabel={addPairControlLabel}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('renders KeyValuePairsField', () => {
|
||||
const { getByText } = render(<WrappedComponent />);
|
||||
expect(getByText('Key')).toBeInTheDocument();
|
||||
|
@ -34,6 +41,21 @@ describe('<KeyValuePairsField />', () => {
|
|||
expect(getByText('Add pair')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls onBlur', () => {
|
||||
const { getByText, getByTestId } = render(<WrappedComponent />);
|
||||
const addPair = getByText('Add pair');
|
||||
fireEvent.click(addPair);
|
||||
|
||||
const keyInput = getByTestId('keyValuePairsKey0') as HTMLInputElement;
|
||||
const valueInput = getByTestId('keyValuePairsValue0') as HTMLInputElement;
|
||||
|
||||
userEvent.type(keyInput, 'some-key');
|
||||
userEvent.type(valueInput, 'some-value');
|
||||
fireEvent.blur(valueInput);
|
||||
|
||||
expect(onBlur).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('handles adding and editing a new row', async () => {
|
||||
const { getByTestId, queryByTestId, getByText } = render(
|
||||
<WrappedComponent defaultValue={[]} />
|
||||
|
|
|
@ -50,6 +50,7 @@ interface Props {
|
|||
addPairControlLabel: string | React.ReactElement;
|
||||
defaultPairs: Pair[];
|
||||
onChange: (pairs: Pair[]) => void;
|
||||
onBlur?: () => void;
|
||||
'data-test-subj'?: string;
|
||||
}
|
||||
|
||||
|
@ -57,6 +58,7 @@ export const KeyValuePairsField = ({
|
|||
addPairControlLabel,
|
||||
defaultPairs,
|
||||
onChange,
|
||||
onBlur,
|
||||
'data-test-subj': dataTestSubj,
|
||||
}: Props) => {
|
||||
const [pairs, setPairs] = useState<Pair[]>(defaultPairs);
|
||||
|
@ -167,6 +169,7 @@ export const KeyValuePairsField = ({
|
|||
data-test-subj={`keyValuePairsKey${index}`}
|
||||
value={key}
|
||||
onChange={(event) => handleOnChange(event, index, true)}
|
||||
onBlur={() => onBlur?.()}
|
||||
/>
|
||||
}
|
||||
endControl={
|
||||
|
@ -177,6 +180,7 @@ export const KeyValuePairsField = ({
|
|||
data-test-subj={`keyValuePairsValue${index}`}
|
||||
value={value}
|
||||
onChange={(event) => handleOnChange(event, index, false)}
|
||||
onBlur={() => onBlur?.()}
|
||||
/>
|
||||
}
|
||||
delimiter=":"
|
||||
|
|
|
@ -15,6 +15,7 @@ import { CodeEditor } from './code_editor';
|
|||
|
||||
interface Props {
|
||||
onChange: (requestBody: { type: Mode; value: string }) => void;
|
||||
onBlur?: () => void;
|
||||
type: Mode;
|
||||
value: string;
|
||||
}
|
||||
|
@ -25,7 +26,7 @@ enum ResponseBodyType {
|
|||
}
|
||||
|
||||
// TO DO: Look into whether or not code editor reports errors, in order to prevent form submission on an error
|
||||
export const RequestBodyField = ({ onChange, type, value }: Props) => {
|
||||
export const RequestBodyField = ({ onChange, onBlur, type, value }: Props) => {
|
||||
const [values, setValues] = useState<Record<ResponseBodyType, string>>({
|
||||
[ResponseBodyType.FORM]: type === Mode.FORM ? value : '',
|
||||
[ResponseBodyType.CODE]: type !== Mode.FORM ? value : '',
|
||||
|
@ -93,9 +94,10 @@ export const RequestBodyField = ({ onChange, type, value }: Props) => {
|
|||
)}
|
||||
id={Mode.PLAINTEXT}
|
||||
languageId={MonacoEditorLangId.PLAINTEXT}
|
||||
onChange={(code) =>
|
||||
setValues((prevValues) => ({ ...prevValues, [ResponseBodyType.CODE]: code }))
|
||||
}
|
||||
onChange={(code) => {
|
||||
setValues((prevValues) => ({ ...prevValues, [ResponseBodyType.CODE]: code }));
|
||||
onBlur?.();
|
||||
}}
|
||||
value={values[ResponseBodyType.CODE]}
|
||||
/>
|
||||
),
|
||||
|
@ -114,9 +116,10 @@ export const RequestBodyField = ({ onChange, type, value }: Props) => {
|
|||
)}
|
||||
id={Mode.JSON}
|
||||
languageId={MonacoEditorLangId.JSON}
|
||||
onChange={(code) =>
|
||||
setValues((prevValues) => ({ ...prevValues, [ResponseBodyType.CODE]: code }))
|
||||
}
|
||||
onChange={(code) => {
|
||||
setValues((prevValues) => ({ ...prevValues, [ResponseBodyType.CODE]: code }));
|
||||
onBlur?.();
|
||||
}}
|
||||
value={values[ResponseBodyType.CODE]}
|
||||
/>
|
||||
),
|
||||
|
@ -135,9 +138,10 @@ export const RequestBodyField = ({ onChange, type, value }: Props) => {
|
|||
)}
|
||||
id={Mode.XML}
|
||||
languageId={MonacoEditorLangId.XML}
|
||||
onChange={(code) =>
|
||||
setValues((prevValues) => ({ ...prevValues, [ResponseBodyType.CODE]: code }))
|
||||
}
|
||||
onChange={(code) => {
|
||||
setValues((prevValues) => ({ ...prevValues, [ResponseBodyType.CODE]: code }));
|
||||
onBlur?.();
|
||||
}}
|
||||
value={values[ResponseBodyType.CODE]}
|
||||
/>
|
||||
),
|
||||
|
@ -156,6 +160,7 @@ export const RequestBodyField = ({ onChange, type, value }: Props) => {
|
|||
}
|
||||
defaultPairs={defaultFormPairs}
|
||||
onChange={onChangeFormFields}
|
||||
onBlur={() => onBlur?.()}
|
||||
/>
|
||||
),
|
||||
},
|
||||
|
|
|
@ -17,6 +17,7 @@ import { ScheduleUnit } from './types';
|
|||
describe('<ScheduleField/>', () => {
|
||||
const number = '1';
|
||||
const unit = ScheduleUnit.MINUTES;
|
||||
const onBlur = jest.fn();
|
||||
const WrappedComponent = ({
|
||||
allowedScheduleUnits,
|
||||
}: Omit<IPolicyConfigContextProvider, 'children'>) => {
|
||||
|
@ -31,11 +32,16 @@ describe('<ScheduleField/>', () => {
|
|||
number={config.number}
|
||||
unit={config.unit}
|
||||
onChange={(value) => setConfig(value)}
|
||||
onBlur={onBlur}
|
||||
/>
|
||||
</PolicyConfigContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('shows all options by default (allowedScheduleUnits is not provided)', () => {
|
||||
const { getByText } = render(<WrappedComponent />);
|
||||
expect(getByText('Minutes')).toBeInTheDocument();
|
||||
|
@ -110,4 +116,21 @@ describe('<ScheduleField/>', () => {
|
|||
expect(getByText('Seconds')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('calls onBlur when changed', () => {
|
||||
const { getByTestId } = render(
|
||||
<WrappedComponent allowedScheduleUnits={[ScheduleUnit.SECONDS, ScheduleUnit.MINUTES]} />
|
||||
);
|
||||
const input = getByTestId('scheduleFieldInput') as HTMLInputElement;
|
||||
const select = getByTestId('scheduleFieldSelect') as HTMLInputElement;
|
||||
|
||||
userEvent.clear(input);
|
||||
userEvent.type(input, '2');
|
||||
|
||||
userEvent.selectOptions(select, ScheduleUnit.MINUTES);
|
||||
|
||||
userEvent.click(input);
|
||||
|
||||
expect(onBlur).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -14,10 +14,11 @@ import { ConfigKey, MonitorFields, ScheduleUnit } from './types';
|
|||
interface Props {
|
||||
number: string;
|
||||
onChange: (schedule: MonitorFields[ConfigKey.SCHEDULE]) => void;
|
||||
onBlur: () => void;
|
||||
unit: ScheduleUnit;
|
||||
}
|
||||
|
||||
export const ScheduleField = ({ number, onChange, unit }: Props) => {
|
||||
export const ScheduleField = ({ number, onChange, onBlur, unit }: Props) => {
|
||||
const { allowedScheduleUnits } = usePolicyConfigContext();
|
||||
const options = !allowedScheduleUnits?.length
|
||||
? allOptions
|
||||
|
@ -51,6 +52,8 @@ export const ScheduleField = ({ number, onChange, unit }: Props) => {
|
|||
const updatedNumber = `${Math.ceil(+event.target.value)}`;
|
||||
onChange({ number: updatedNumber, unit });
|
||||
}
|
||||
|
||||
onBlur();
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
@ -70,6 +73,7 @@ export const ScheduleField = ({ number, onChange, unit }: Props) => {
|
|||
const updatedUnit = event.target.value;
|
||||
onChange({ number, unit: updatedUnit as ScheduleUnit });
|
||||
}}
|
||||
onBlur={() => onBlur()}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -24,13 +24,15 @@ describe('<TCPAdvancedFields />', () => {
|
|||
const WrappedComponent = ({
|
||||
defaultValues = defaultConfig,
|
||||
children,
|
||||
onFieldBlur,
|
||||
}: {
|
||||
defaultValues?: TCPAdvancedFieldsType;
|
||||
children?: React.ReactNode;
|
||||
onFieldBlur?: (field: ConfigKey) => void;
|
||||
}) => {
|
||||
return (
|
||||
<TCPAdvancedFieldsContextProvider defaultValues={defaultValues}>
|
||||
<TCPAdvancedFields>{children}</TCPAdvancedFields>
|
||||
<TCPAdvancedFields onFieldBlur={onFieldBlur}>{children}</TCPAdvancedFields>
|
||||
</TCPAdvancedFieldsContextProvider>
|
||||
);
|
||||
};
|
||||
|
@ -59,6 +61,17 @@ describe('<TCPAdvancedFields />', () => {
|
|||
expect(requestPayload.value).toEqual('success');
|
||||
});
|
||||
|
||||
it('calls onBlur on fields', () => {
|
||||
const onFieldBlur = jest.fn();
|
||||
const { getByLabelText } = render(<WrappedComponent onFieldBlur={onFieldBlur} />);
|
||||
|
||||
const requestPayload = getByLabelText('Request payload') as HTMLInputElement;
|
||||
|
||||
fireEvent.change(requestPayload, { target: { value: 'success' } });
|
||||
fireEvent.blur(requestPayload);
|
||||
expect(onFieldBlur).toHaveBeenCalledWith(ConfigKey.REQUEST_SEND_CHECK);
|
||||
});
|
||||
|
||||
it('shows resolve hostnames locally field when proxy url is filled for tcp monitors', () => {
|
||||
const { getByLabelText, queryByLabelText } = render(<WrappedComponent />);
|
||||
|
||||
|
|
|
@ -19,9 +19,10 @@ import { OptionalLabel } from '../optional_label';
|
|||
interface Props {
|
||||
children?: React.ReactNode;
|
||||
minColumnWidth?: string;
|
||||
onFieldBlur?: (field: ConfigKey) => void;
|
||||
}
|
||||
|
||||
export const TCPAdvancedFields = memo<Props>(({ children, minColumnWidth }) => {
|
||||
export const TCPAdvancedFields = memo<Props>(({ children, minColumnWidth, onFieldBlur }) => {
|
||||
const { fields, setFields } = useTCPAdvancedFieldsContext();
|
||||
|
||||
const handleInputChange = useCallback(
|
||||
|
@ -79,6 +80,7 @@ export const TCPAdvancedFields = memo<Props>(({ children, minColumnWidth }) => {
|
|||
configKey: ConfigKey.PROXY_URL,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.PROXY_URL)}
|
||||
data-test-subj="syntheticsProxyUrl"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
@ -127,6 +129,7 @@ export const TCPAdvancedFields = memo<Props>(({ children, minColumnWidth }) => {
|
|||
}),
|
||||
[handleInputChange]
|
||||
)}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.REQUEST_SEND_CHECK)}
|
||||
data-test-subj="syntheticsTCPRequestSendCheck"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
@ -173,6 +176,7 @@ export const TCPAdvancedFields = memo<Props>(({ children, minColumnWidth }) => {
|
|||
}),
|
||||
[handleInputChange]
|
||||
)}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.RESPONSE_RECEIVE_CHECK)}
|
||||
data-test-subj="syntheticsTCPResponseReceiveCheck"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
|
|
@ -15,9 +15,10 @@ import { SimpleFieldsWrapper } from '../common/simple_fields_wrapper';
|
|||
|
||||
interface Props {
|
||||
validate: Validation;
|
||||
onFieldBlur: (field: ConfigKey) => void; // To propagate blurred state up to parents
|
||||
}
|
||||
|
||||
export const TCPSimpleFields = memo<Props>(({ validate }) => {
|
||||
export const TCPSimpleFields = memo<Props>(({ validate, onFieldBlur }) => {
|
||||
const { fields, setFields } = useTCPSimpleFieldsContext();
|
||||
const handleInputChange = useCallback(
|
||||
({ value, configKey }: { value: unknown; configKey: ConfigKey }) => {
|
||||
|
@ -27,7 +28,12 @@ export const TCPSimpleFields = memo<Props>(({ validate }) => {
|
|||
);
|
||||
|
||||
return (
|
||||
<SimpleFieldsWrapper fields={fields} validate={validate} onInputChange={handleInputChange}>
|
||||
<SimpleFieldsWrapper
|
||||
fields={fields}
|
||||
validate={validate}
|
||||
onInputChange={handleInputChange}
|
||||
onFieldBlur={onFieldBlur}
|
||||
>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
|
@ -51,6 +57,7 @@ export const TCPSimpleFields = memo<Props>(({ validate }) => {
|
|||
configKey: ConfigKey.HOSTS,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur(ConfigKey.HOSTS)}
|
||||
data-test-subj="syntheticsTCPHostField"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
@ -78,6 +85,7 @@ export const TCPSimpleFields = memo<Props>(({ validate }) => {
|
|||
configKey: ConfigKey.SCHEDULE,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur(ConfigKey.SCHEDULE)}
|
||||
number={fields[ConfigKey.SCHEDULE].number}
|
||||
unit={fields[ConfigKey.SCHEDULE].unit}
|
||||
/>
|
||||
|
|
|
@ -6,11 +6,11 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { screen } from '@testing-library/react';
|
||||
import { fireEvent, screen } from '@testing-library/react';
|
||||
import { render } from '../../../lib/helper/rtl_helpers';
|
||||
import { ServiceLocations } from './locations';
|
||||
|
||||
describe('<ActionBar />', () => {
|
||||
describe('<ServiceLocations />', () => {
|
||||
const setLocations = jest.fn();
|
||||
const location = {
|
||||
label: 'US Central',
|
||||
|
@ -21,6 +21,7 @@ describe('<ActionBar />', () => {
|
|||
},
|
||||
url: 'url',
|
||||
};
|
||||
const locationTestSubId = `syntheticsServiceLocation--${location.id}`;
|
||||
const state = {
|
||||
monitorManagementList: {
|
||||
locations: [location],
|
||||
|
@ -62,4 +63,35 @@ describe('<ActionBar />', () => {
|
|||
|
||||
expect(screen.getByText('At least one service location must be specified')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('checks unchecks location', () => {
|
||||
const { getByTestId } = render(
|
||||
<ServiceLocations selectedLocations={[]} setLocations={setLocations} isInvalid={true} />,
|
||||
{ state }
|
||||
);
|
||||
|
||||
const checkbox = getByTestId(locationTestSubId) as HTMLInputElement;
|
||||
expect(checkbox.checked).toEqual(false);
|
||||
fireEvent.click(checkbox);
|
||||
|
||||
expect(setLocations).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls onBlur', () => {
|
||||
const onBlur = jest.fn();
|
||||
const { getByTestId } = render(
|
||||
<ServiceLocations
|
||||
selectedLocations={[]}
|
||||
setLocations={setLocations}
|
||||
isInvalid={true}
|
||||
onBlur={onBlur}
|
||||
/>,
|
||||
{ state }
|
||||
);
|
||||
|
||||
const checkbox = getByTestId(locationTestSubId) as HTMLInputElement;
|
||||
fireEvent.click(checkbox);
|
||||
fireEvent.blur(checkbox);
|
||||
expect(onBlur).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -16,9 +16,10 @@ interface Props {
|
|||
selectedLocations: ServiceLocation[];
|
||||
setLocations: React.Dispatch<React.SetStateAction<ServiceLocation[]>>;
|
||||
isInvalid: boolean;
|
||||
onBlur?: () => void;
|
||||
}
|
||||
|
||||
export const ServiceLocations = ({ selectedLocations, setLocations, isInvalid }: Props) => {
|
||||
export const ServiceLocations = ({ selectedLocations, setLocations, isInvalid, onBlur }: Props) => {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [checkboxIdToSelectedMap, setCheckboxIdToSelectedMap] = useState<Record<string, boolean>>(
|
||||
{}
|
||||
|
@ -58,6 +59,7 @@ export const ServiceLocations = ({ selectedLocations, setLocations, isInvalid }:
|
|||
}))}
|
||||
idToSelectedMap={checkboxIdToSelectedMap}
|
||||
onChange={(id) => onLocationChange(id)}
|
||||
onBlur={() => onBlur?.()}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
|
|
|
@ -4,88 +4,92 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import React, { memo } from 'react';
|
||||
import { EuiFieldText, EuiFormRow, EuiLink, EuiSpacer } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiFormRow, EuiSpacer, EuiLink, EuiFieldText } from '@elastic/eui';
|
||||
import type { Validation } from '../../../../common/types';
|
||||
import React, { memo } from 'react';
|
||||
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
|
||||
import { ConfigKey } from '../../../../common/runtime_types';
|
||||
import type { Validation } from '../../../../common/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;
|
||||
onFieldBlur?: (field: ConfigKey) => void;
|
||||
}
|
||||
|
||||
export const MonitorManagementAdvancedFields = memo<Props>(({ validate, minColumnWidth }) => {
|
||||
const { namespace, setNamespace } = usePolicyConfigContext();
|
||||
export const MonitorManagementAdvancedFields = memo<Props>(
|
||||
({ validate, minColumnWidth, onFieldBlur }) => {
|
||||
const { namespace, setNamespace } = usePolicyConfigContext();
|
||||
|
||||
const namespaceErrorMsg = validate[ConfigKey.NAMESPACE]?.({
|
||||
[ConfigKey.NAMESPACE]: namespace,
|
||||
});
|
||||
const isNamespaceInvalid = !!namespaceErrorMsg;
|
||||
const { services } = useKibana();
|
||||
const namespaceErrorMsg = validate[ConfigKey.NAMESPACE]?.({
|
||||
[ConfigKey.NAMESPACE]: namespace,
|
||||
});
|
||||
const isNamespaceInvalid = !!namespaceErrorMsg;
|
||||
const { services } = useKibana();
|
||||
|
||||
return (
|
||||
<DescribedFormGroupWithWrap
|
||||
minColumnWidth={minColumnWidth}
|
||||
title={
|
||||
<h4>
|
||||
return (
|
||||
<DescribedFormGroupWithWrap
|
||||
minColumnWidth={minColumnWidth}
|
||||
title={
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.monitorManagement.monitorAdvancedOptions.dataStreamConfiguration.title"
|
||||
defaultMessage="Data stream settings"
|
||||
/>
|
||||
</h4>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.monitorManagement.monitorAdvancedOptions.dataStreamConfiguration.title"
|
||||
defaultMessage="Data stream settings"
|
||||
/>
|
||||
</h4>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.monitorManagement.monitorAdvancedOptions.dataStreamConfiguration.description"
|
||||
defaultMessage="Configure additional Data Stream options."
|
||||
/>
|
||||
}
|
||||
data-test-subj="monitorAdvancedFieldsSection"
|
||||
>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFormRow
|
||||
isInvalid={isNamespaceInvalid}
|
||||
error={namespaceErrorMsg}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.monitorManagement.monitorAdvancedOptions.monitorNamespaceFieldLabel"
|
||||
defaultMessage="Namespace"
|
||||
/>
|
||||
}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.monitorManagement.monitorAdvancedOptions.namespaceHelpLabel"
|
||||
defaultMessage="Change the default namespace. This setting changes the name of the monitor's data stream. {learnMore}."
|
||||
values={{
|
||||
learnMore: (
|
||||
<EuiLink
|
||||
target="_blank"
|
||||
href={services.docLinks?.links?.fleet?.datastreamsNamingScheme}
|
||||
external
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.monitorManagement.monitorAdvancedOptions.namespaceHelpLearnMoreLabel"
|
||||
defaultMessage="Learn More"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
id="xpack.uptime.monitorManagement.monitorAdvancedOptions.dataStreamConfiguration.description"
|
||||
defaultMessage="Configure additional Data Stream options."
|
||||
/>
|
||||
}
|
||||
data-test-subj="monitorAdvancedFieldsSection"
|
||||
>
|
||||
<EuiFieldText
|
||||
defaultValue={namespace}
|
||||
onChange={(event) => setNamespace(event.target.value)}
|
||||
required={true}
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFormRow
|
||||
isInvalid={isNamespaceInvalid}
|
||||
fullWidth={true}
|
||||
name="namespace"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</DescribedFormGroupWithWrap>
|
||||
);
|
||||
});
|
||||
error={namespaceErrorMsg}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.monitorManagement.monitorAdvancedOptions.monitorNamespaceFieldLabel"
|
||||
defaultMessage="Namespace"
|
||||
/>
|
||||
}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.monitorManagement.monitorAdvancedOptions.namespaceHelpLabel"
|
||||
defaultMessage="Change the default namespace. This setting changes the name of the monitor's data stream. {learnMore}."
|
||||
values={{
|
||||
learnMore: (
|
||||
<EuiLink
|
||||
target="_blank"
|
||||
href={services.docLinks?.links?.fleet?.datastreamsNamingScheme}
|
||||
external
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.uptime.monitorManagement.monitorAdvancedOptions.namespaceHelpLearnMoreLabel"
|
||||
defaultMessage="Learn More"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFieldText
|
||||
defaultValue={namespace}
|
||||
onChange={(event) => setNamespace(event.target.value)}
|
||||
required={true}
|
||||
isInvalid={isNamespaceInvalid}
|
||||
fullWidth={true}
|
||||
name="namespace"
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.NAMESPACE)}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</DescribedFormGroupWithWrap>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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 { fireEvent } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import React from 'react';
|
||||
import { render } from '../../../lib/helper/rtl_helpers';
|
||||
import {
|
||||
BrowserContextProvider,
|
||||
HTTPContextProvider,
|
||||
ICMPSimpleFieldsContextProvider,
|
||||
PolicyConfigContextProvider,
|
||||
TCPContextProvider,
|
||||
TLSFieldsContextProvider,
|
||||
} from '../../fleet_package/contexts';
|
||||
import { MonitorConfig } from './monitor_config';
|
||||
|
||||
describe('<MonitorConfig />', () => {
|
||||
const WrappedComponent = ({ isEditable = true, isEdit = false }) => {
|
||||
return (
|
||||
<HTTPContextProvider>
|
||||
<PolicyConfigContextProvider isEditable={isEditable}>
|
||||
<TCPContextProvider>
|
||||
<BrowserContextProvider>
|
||||
<ICMPSimpleFieldsContextProvider>
|
||||
<TLSFieldsContextProvider>
|
||||
<MonitorConfig isEdit={isEdit} />
|
||||
</TLSFieldsContextProvider>
|
||||
</ICMPSimpleFieldsContextProvider>
|
||||
</BrowserContextProvider>
|
||||
</TCPContextProvider>
|
||||
</PolicyConfigContextProvider>
|
||||
</HTTPContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('renders MonitorConfig', async () => {
|
||||
const { getByLabelText } = render(<WrappedComponent />);
|
||||
const monitorName = getByLabelText('Monitor name') as HTMLInputElement;
|
||||
expect(monitorName).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('only shows validation errors when field is interacted with', async () => {
|
||||
const { getByLabelText, queryByText } = render(<WrappedComponent />);
|
||||
const monitorName = getByLabelText('Monitor name') as HTMLInputElement;
|
||||
expect(monitorName).toBeInTheDocument();
|
||||
|
||||
userEvent.clear(monitorName);
|
||||
expect(queryByText('Monitor name is required')).toBeNull();
|
||||
fireEvent.blur(monitorName);
|
||||
expect(queryByText('Monitor name is required')).not.toBeNull();
|
||||
});
|
||||
});
|
|
@ -44,10 +44,15 @@ export const MonitorConfig = ({ isEdit = false }: { isEdit: boolean }) => {
|
|||
defaultConfig: defaultConfig[monitorType],
|
||||
});
|
||||
|
||||
const [hasBeenSubmitted, setHasBeenSubmitted] = useState(false);
|
||||
const [testRun, setTestRun] = useState<TestRun>();
|
||||
const [isTestRunInProgress, setIsTestRunInProgress] = useState<boolean>(false);
|
||||
const [isFlyoutOpen, setIsFlyoutOpen] = useState<boolean>(false);
|
||||
|
||||
const handleFormSubmit = () => {
|
||||
setHasBeenSubmitted(true);
|
||||
};
|
||||
|
||||
const handleTestNow = () => {
|
||||
if (config) {
|
||||
setTestRun({ id: uuidv4(), monitor: config as MonitorFieldsType });
|
||||
|
@ -90,7 +95,7 @@ export const MonitorConfig = ({ isEdit = false }: { isEdit: boolean }) => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<MonitorFields />
|
||||
<MonitorFields isFormSubmitted={hasBeenSubmitted} />
|
||||
|
||||
{flyout}
|
||||
|
||||
|
@ -100,6 +105,7 @@ export const MonitorConfig = ({ isEdit = false }: { isEdit: boolean }) => {
|
|||
onTestNow={handleTestNow}
|
||||
testRun={testRun}
|
||||
isTestRunInProgress={isTestRunInProgress}
|
||||
onSave={handleFormSubmit}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* 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 { fireEvent } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import React from 'react';
|
||||
import { ConfigKey, DataStream, HTTPFields } from '../../../../common/runtime_types';
|
||||
import { render } from '../../../lib/helper/rtl_helpers';
|
||||
import {
|
||||
BrowserContextProvider,
|
||||
HTTPContextProvider,
|
||||
ICMPSimpleFieldsContextProvider,
|
||||
PolicyConfigContextProvider,
|
||||
TCPContextProvider,
|
||||
TLSFieldsContextProvider,
|
||||
} from '../../fleet_package/contexts';
|
||||
import { defaultConfig } from '../../fleet_package/synthetics_policy_create_extension';
|
||||
import { MonitorFields } from './monitor_fields';
|
||||
|
||||
const defaultHTTPConfig = defaultConfig[DataStream.HTTP] as HTTPFields;
|
||||
|
||||
describe('<MonitorFields />', () => {
|
||||
const WrappedComponent = ({
|
||||
isEditable = true,
|
||||
isFormSubmitted = false,
|
||||
defaultSimpleHttpFields = defaultHTTPConfig,
|
||||
}: {
|
||||
isEditable?: boolean;
|
||||
isFormSubmitted?: boolean;
|
||||
defaultSimpleHttpFields?: HTTPFields;
|
||||
}) => {
|
||||
return (
|
||||
<HTTPContextProvider defaultValues={defaultSimpleHttpFields}>
|
||||
<PolicyConfigContextProvider isEditable={isEditable}>
|
||||
<TCPContextProvider>
|
||||
<BrowserContextProvider>
|
||||
<ICMPSimpleFieldsContextProvider>
|
||||
<TLSFieldsContextProvider>
|
||||
<MonitorFields isFormSubmitted={isFormSubmitted} />
|
||||
</TLSFieldsContextProvider>
|
||||
</ICMPSimpleFieldsContextProvider>
|
||||
</BrowserContextProvider>
|
||||
</TCPContextProvider>
|
||||
</PolicyConfigContextProvider>
|
||||
</HTTPContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('renders MonitorFields', async () => {
|
||||
const { getByLabelText } = render(<WrappedComponent />);
|
||||
const monitorName = getByLabelText('URL') as HTMLInputElement;
|
||||
expect(monitorName).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('only shows validation errors when field has been interacted with', async () => {
|
||||
const { getByLabelText, queryByText } = render(<WrappedComponent />);
|
||||
const monitorName = getByLabelText('Monitor name') as HTMLInputElement;
|
||||
expect(monitorName).toBeInTheDocument();
|
||||
|
||||
userEvent.clear(monitorName);
|
||||
expect(queryByText('Monitor name is required')).toBeNull();
|
||||
fireEvent.blur(monitorName);
|
||||
expect(queryByText('Monitor name is required')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('shows all validations errors when form is submitted', async () => {
|
||||
const httpInvalidValues = { ...defaultHTTPConfig, [ConfigKey.NAME]: '', [ConfigKey.URLS]: '' };
|
||||
const { queryByText } = render(
|
||||
<WrappedComponent isFormSubmitted={true} defaultSimpleHttpFields={httpInvalidValues} />
|
||||
);
|
||||
|
||||
expect(queryByText('Monitor name is required')).not.toBeNull();
|
||||
expect(queryByText('URL is required')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('does not show validation errors initially', async () => {
|
||||
const httpInvalidValues = { ...defaultHTTPConfig, [ConfigKey.NAME]: '', [ConfigKey.URLS]: '' };
|
||||
const { queryByText } = render(
|
||||
<WrappedComponent isFormSubmitted={false} defaultSimpleHttpFields={httpInvalidValues} />
|
||||
);
|
||||
|
||||
expect(queryByText('Monitor name is required')).toBeNull();
|
||||
expect(queryByText('URL is required')).toBeNull();
|
||||
});
|
||||
});
|
|
@ -5,9 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { EuiForm } from '@elastic/eui';
|
||||
import { DataStream } from '../../../../common/runtime_types';
|
||||
import { ConfigKey, DataStream } from '../../../../common/runtime_types';
|
||||
import { usePolicyConfigContext } from '../../fleet_package/contexts';
|
||||
|
||||
import { CustomFields } from '../../fleet_package/custom_fields';
|
||||
|
@ -17,22 +17,44 @@ import { MonitorManagementAdvancedFields } from './monitor_advanced_fields';
|
|||
|
||||
const MIN_COLUMN_WRAP_WIDTH = '360px';
|
||||
|
||||
export const MonitorFields = () => {
|
||||
export const MonitorFields = ({ isFormSubmitted = false }: { isFormSubmitted?: boolean }) => {
|
||||
const { monitorType } = usePolicyConfigContext();
|
||||
|
||||
const [touchedFieldsHash, setTouchedFieldsHash] = useState<Record<string, boolean>>({});
|
||||
|
||||
const fieldValidation = useMemo(() => {
|
||||
const validatorsHash = { ...validate[monitorType] };
|
||||
if (!isFormSubmitted) {
|
||||
Object.keys(validatorsHash).map((key) => {
|
||||
if (!touchedFieldsHash[key]) {
|
||||
validatorsHash[key as ConfigKey] = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return validatorsHash;
|
||||
}, [isFormSubmitted, monitorType, touchedFieldsHash]);
|
||||
|
||||
const handleFieldBlur = (field: ConfigKey) => {
|
||||
setTouchedFieldsHash((hash) => ({ ...hash, [field]: true }));
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiForm id="syntheticsServiceCreateMonitorForm" component="form">
|
||||
<CustomFields
|
||||
minColumnWidth={MIN_COLUMN_WRAP_WIDTH}
|
||||
validate={validate[monitorType]}
|
||||
validate={fieldValidation}
|
||||
dataStreams={[DataStream.HTTP, DataStream.TCP, DataStream.ICMP, DataStream.BROWSER]}
|
||||
appendAdvancedFields={
|
||||
<MonitorManagementAdvancedFields
|
||||
validate={validate[monitorType]}
|
||||
validate={fieldValidation}
|
||||
minColumnWidth={MIN_COLUMN_WRAP_WIDTH}
|
||||
onFieldBlur={handleFieldBlur}
|
||||
/>
|
||||
}
|
||||
onFieldBlur={handleFieldBlur}
|
||||
>
|
||||
<MonitorNameAndLocation validate={validate[monitorType]} />
|
||||
<MonitorNameAndLocation validate={fieldValidation} onFieldBlur={handleFieldBlur} />
|
||||
</CustomFields>
|
||||
</EuiForm>
|
||||
);
|
||||
|
|
|
@ -17,9 +17,10 @@ import { useMonitorName } from './use_monitor_name';
|
|||
|
||||
interface Props {
|
||||
validate: Validation;
|
||||
onFieldBlur?: (field: ConfigKey) => void;
|
||||
}
|
||||
|
||||
export const MonitorNameAndLocation = ({ validate }: Props) => {
|
||||
export const MonitorNameAndLocation = ({ validate, onFieldBlur }: Props) => {
|
||||
const { name, setName, locations = [], setLocations } = usePolicyConfigContext();
|
||||
const isNameInvalid = !!validate[ConfigKey.NAME]?.({ [ConfigKey.NAME]: name });
|
||||
const isLocationsInvalid = !!validate[ConfigKey.LOCATIONS]?.({
|
||||
|
@ -64,6 +65,7 @@ export const MonitorNameAndLocation = ({ validate }: Props) => {
|
|||
fullWidth={true}
|
||||
name="name"
|
||||
onChange={(event) => setLocalName(event.target.value)}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.NAME)}
|
||||
data-test-subj="monitorManagementMonitorName"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
@ -71,6 +73,7 @@ export const MonitorNameAndLocation = ({ validate }: Props) => {
|
|||
setLocations={setLocations}
|
||||
selectedLocations={locations}
|
||||
isInvalid={isLocationsInvalid}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.LOCATIONS)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue