[Synthetics] Enable throttling in synthetics app (#154378)

## Summary

Enable throttling config in synthetics app !!

<img width="1773" alt="image"
src="https://user-images.githubusercontent.com/3505601/229875799-d87b0ad6-c318-4a17-9012-c56666841b5c.png">

---------

Co-authored-by: Dominique Clarke <dominique.clarke@elastic.co>
This commit is contained in:
Shahzad 2023-04-19 18:14:11 +02:00 committed by GitHub
parent 54f9f11339
commit 2fbfb33bed
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
47 changed files with 1481 additions and 274 deletions

View file

@ -138,7 +138,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
"slo": "aefffabdb35d15a6c388634af2cee1fa59ede83c",
"space": "7fc578a1f9f7708cb07479f03953d664ad9f1dae",
"spaces-usage-stats": "084bd0f080f94fb5735d7f3cf12f13ec92f36bad",
"synthetics-monitor": "7136a2669a65323c56da849f26c369cdeeb3b381",
"synthetics-monitor": "fa988678e5d6791c75515fa1acdbbb3b2d1f916d",
"synthetics-param": "9776c9b571d35f0d0397e8915e035ea1dc026db7",
"synthetics-privates-locations": "7d032fc788905e32152029ae7ab3d6038c48ae44",
"tag": "87f21f07df9cc37001b15a26e413c18f50d1fbfe",

View file

@ -4,6 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { i18n } from '@kbn/i18n';
import {
BrowserAdvancedFields,
BrowserSimpleFields,
@ -22,6 +23,7 @@ import {
SourceType,
TCPAdvancedFields,
TCPSimpleFields,
ThrottlingConfig,
TLSFields,
TLSVersion,
VerificationMode,
@ -30,6 +32,86 @@ import { ConfigKey } from './monitor_management';
export const DEFAULT_NAMESPACE_STRING = 'default';
export enum PROFILE_VALUES_ENUM {
DEFAULT = 'default',
CABLE = 'cable',
DSL = 'dsl',
THREE_G = '3g',
FOUR_G = '4g',
LTE = 'lte',
FIBRE = 'fibre',
NO_THROTTLING = 'no-throttling',
CUSTOM = 'custom',
}
export const CUSTOM_LABEL = i18n.translate('xpack.synthetics.connectionProfile.custom', {
defaultMessage: 'Custom',
});
export const PROFILE_VALUES: ThrottlingConfig[] = [
{
value: { download: '5', upload: '3', latency: '20' },
id: PROFILE_VALUES_ENUM.DEFAULT,
label: i18n.translate('xpack.synthetics.connectionProfile.default', {
defaultMessage: 'Default',
}),
},
{
value: { download: '5', upload: '1', latency: '28' },
id: PROFILE_VALUES_ENUM.CABLE,
label: i18n.translate('xpack.synthetics.connectionProfile.cable', {
defaultMessage: 'Cable',
}),
},
{
value: { download: '1.5', upload: '0.384', latency: '50' },
id: PROFILE_VALUES_ENUM.DSL,
label: i18n.translate('xpack.synthetics.connectionProfile.dsl', {
defaultMessage: 'DSL',
}),
},
{
value: { download: '1.6', upload: '0.768', latency: '300' },
id: PROFILE_VALUES_ENUM.THREE_G,
label: i18n.translate('xpack.synthetics.connectionProfile.threeG', {
defaultMessage: '3G',
}),
},
{
value: { download: '9', upload: '0.75', latency: '170' },
id: PROFILE_VALUES_ENUM.FOUR_G,
label: i18n.translate('xpack.synthetics.connectionProfile.fourG', {
defaultMessage: '4G',
}),
},
{
value: { download: '12', upload: '0.75', latency: '70' },
id: PROFILE_VALUES_ENUM.LTE,
label: i18n.translate('xpack.synthetics.connectionProfile.lte', {
defaultMessage: 'LTE',
}),
},
{
value: { download: '20', upload: '5', latency: '4' },
id: PROFILE_VALUES_ENUM.FIBRE,
label: i18n.translate('xpack.synthetics.connectionProfile.fibre', {
defaultMessage: 'Fibre',
}),
},
{
value: null,
id: PROFILE_VALUES_ENUM.NO_THROTTLING,
label: i18n.translate('xpack.synthetics.connectionProfile.noThrottling', {
defaultMessage: 'No throttling',
}),
},
];
export const PROFILES_MAP = PROFILE_VALUES.reduce((acc, profile) => {
acc[profile.id] = profile;
return acc;
}, {} as { [key: string]: ThrottlingConfig });
export const ALLOWED_SCHEDULES_IN_MINUTES = [
'1',
'3',
@ -71,11 +153,7 @@ export const DEFAULT_BROWSER_ADVANCED_FIELDS: BrowserAdvancedFields = {
[ConfigKey.JOURNEY_FILTERS_MATCH]: '',
[ConfigKey.JOURNEY_FILTERS_TAGS]: [],
[ConfigKey.IGNORE_HTTPS_ERRORS]: false,
[ConfigKey.IS_THROTTLING_ENABLED]: true,
[ConfigKey.DOWNLOAD_SPEED]: '5',
[ConfigKey.UPLOAD_SPEED]: '3',
[ConfigKey.LATENCY]: '20',
[ConfigKey.THROTTLING_CONFIG]: '5d/3u/20l',
[ConfigKey.THROTTLING_CONFIG]: PROFILES_MAP[PROFILE_VALUES_ENUM.DEFAULT],
};
export const DEFAULT_BROWSER_SIMPLE_FIELDS: BrowserSimpleFields = {

View file

@ -65,11 +65,7 @@ export enum ConfigKey {
TLS_VERSION = 'ssl.supported_protocols',
TAGS = 'tags',
TIMEOUT = 'timeout',
THROTTLING_CONFIG = 'throttling.config',
IS_THROTTLING_ENABLED = 'throttling.is_enabled',
DOWNLOAD_SPEED = 'throttling.download_speed',
UPLOAD_SPEED = 'throttling.upload_speed',
LATENCY = 'throttling.latency',
THROTTLING_CONFIG = 'throttling',
URLS = 'urls',
USERNAME = 'username',
WAIT = 'wait',
@ -106,4 +102,10 @@ export enum LegacyConfigKey {
ZIP_URL_TLS_KEY_PASSPHRASE = 'source.zip_url.ssl.key_passphrase',
ZIP_URL_TLS_VERIFICATION_MODE = 'source.zip_url.ssl.verification_mode',
ZIP_URL_TLS_VERSION = 'source.zip_url.ssl.supported_protocols',
THROTTLING_CONFIG = 'throttling.config',
IS_THROTTLING_ENABLED = 'throttling.is_enabled',
DOWNLOAD_SPEED = 'throttling.download_speed',
UPLOAD_SPEED = 'throttling.upload_speed',
LATENCY = 'throttling.latency',
}

View file

@ -17,29 +17,20 @@ import { tlsFormatters } from '../tls/formatters';
export type BrowserFormatMap = Record<keyof BrowserFields, Formatter>;
const throttlingFormatter: Formatter = (fields) => {
if (!fields[ConfigKey.IS_THROTTLING_ENABLED]) return 'false';
export const throttlingFormatter: Formatter = (fields) => {
const throttling = fields[ConfigKey.THROTTLING_CONFIG];
const getThrottlingValue = (v: string | undefined, suffix: 'd' | 'u' | 'l') =>
v !== '' && v !== undefined ? `${v}${suffix}` : null;
if (!throttling || throttling?.id === 'no-throttling' || !throttling?.value) {
return 'false';
}
return [
getThrottlingValue(fields[ConfigKey.DOWNLOAD_SPEED], 'd'),
getThrottlingValue(fields[ConfigKey.UPLOAD_SPEED], 'u'),
getThrottlingValue(fields[ConfigKey.LATENCY], 'l'),
]
.filter((v) => v !== null)
.join('/');
return `${throttling.value.download}d/${throttling.value.upload}u/${throttling.value.latency}l`;
};
export const browserFormatters: BrowserFormatMap = {
[ConfigKey.SOURCE_PROJECT_CONTENT]: null,
[ConfigKey.PARAMS]: null,
[ConfigKey.SCREENSHOTS]: null,
[ConfigKey.IS_THROTTLING_ENABLED]: null,
[ConfigKey.DOWNLOAD_SPEED]: null,
[ConfigKey.UPLOAD_SPEED]: null,
[ConfigKey.LATENCY]: null,
[ConfigKey.IGNORE_HTTPS_ERRORS]: null,
[ConfigKey.PLAYWRIGHT_OPTIONS]: null,
[ConfigKey.TEXT_ASSERTION]: null,

View file

@ -6,6 +6,7 @@
*/
import { ConfigKey, DataStream } from '../runtime_types';
import { formatSyntheticsPolicy } from './format_synthetics_policy';
import { PROFILE_VALUES_ENUM, PROFILES_MAP } from '../constants/monitor_defaults';
const gParams = { proxyUrl: 'https://proxy.com' };
describe('formatSyntheticsPolicy', () => {
@ -1145,11 +1146,7 @@ const browserConfig: any = {
'filter_journeys.match': '',
'filter_journeys.tags': [],
ignore_https_errors: false,
'throttling.is_enabled': true,
'throttling.download_speed': '5',
'throttling.upload_speed': '3',
'throttling.latency': '20',
'throttling.config': '5d/3u/20l',
throttling: PROFILES_MAP[PROFILE_VALUES_ENUM.DEFAULT],
'ssl.certificate_authorities': '',
'ssl.certificate': '',
'ssl.key': '',

View file

@ -7,6 +7,8 @@
import { NewPackagePolicy } from '@kbn/fleet-plugin/common';
import { cloneDeep } from 'lodash';
import { throttlingFormatter } from './browser/formatters';
import { LegacyConfigKey } from '../constants/monitor_management';
import { replaceStringWithParams } from './formatting_utils';
import { syntheticsPolicyFormatters } from './formatters';
import { ConfigKey, DataStream, MonitorFields } from '../runtime_types';
@ -69,5 +71,11 @@ export const formatSyntheticsPolicy = (
}
});
// TODO: remove this once we remove legacy support
const throttling = dataStream?.vars?.[LegacyConfigKey.THROTTLING_CONFIG];
if (throttling) {
throttling.value = throttlingFormatter?.(config, ConfigKey.THROTTLING_CONFIG);
}
return { formattedPolicy, hasDataStream: Boolean(dataStream), hasInput: Boolean(currentInput) };
};

View file

@ -111,15 +111,6 @@ export enum ScreenshotOption {
export const ScreenshotOptionCodec = tEnum<ScreenshotOption>('ScreenshotOption', ScreenshotOption);
export type ScreenshotOptionType = t.TypeOf<typeof ScreenshotOptionCodec>;
export enum ThrottlingSuffix {
DOWNLOAD = 'd',
UPLOAD = 'u',
LATENCY = 'l',
}
export const ThrottlingSuffixCodec = tEnum<ThrottlingSuffix>('ThrottlingSuffix', ThrottlingSuffix);
export type ThrottlingSuffixType = t.TypeOf<typeof ThrottlingSuffixCodec>;
export enum SourceType {
UI = 'ui',
PROJECT = 'project',

View file

@ -192,15 +192,6 @@ export const HTTPFieldsCodec = t.intersection([
export type HTTPFields = t.TypeOf<typeof HTTPFieldsCodec>;
// Browser Fields
export const ThrottlingConfigKeyCodec = t.union([
t.literal(ConfigKey.DOWNLOAD_SPEED),
t.literal(ConfigKey.UPLOAD_SPEED),
t.literal(ConfigKey.LATENCY),
]);
export type ThrottlingConfigKey = t.TypeOf<typeof ThrottlingConfigKeyCodec>;
export const EncryptedBrowserSimpleFieldsCodec = t.intersection([
t.intersection([
t.interface({
@ -225,16 +216,28 @@ export const BrowserSensitiveSimpleFieldsCodec = t.intersection([
CommonFieldsCodec,
]);
export const ThrottlingConfigValueCodec = t.interface({
download: t.string,
upload: t.string,
latency: t.string,
});
export type ThrottlingConfigValue = t.TypeOf<typeof ThrottlingConfigValueCodec>;
export const ThrottlingConfigCodec = t.interface({
value: t.union([ThrottlingConfigValueCodec, t.null]),
label: t.string,
id: t.string,
});
export type ThrottlingConfig = t.TypeOf<typeof ThrottlingConfigCodec>;
export const EncryptedBrowserAdvancedFieldsCodec = t.interface({
[ConfigKey.SCREENSHOTS]: t.string,
[ConfigKey.JOURNEY_FILTERS_MATCH]: t.string,
[ConfigKey.JOURNEY_FILTERS_TAGS]: t.array(t.string),
[ConfigKey.IGNORE_HTTPS_ERRORS]: t.boolean,
[ConfigKey.IS_THROTTLING_ENABLED]: t.boolean,
[ConfigKey.DOWNLOAD_SPEED]: t.string,
[ConfigKey.UPLOAD_SPEED]: t.string,
[ConfigKey.LATENCY]: t.string,
[ConfigKey.THROTTLING_CONFIG]: t.string,
[ConfigKey.THROTTLING_CONFIG]: ThrottlingConfigCodec,
});
export const BrowserSimpleFieldsCodec = t.intersection([

View file

@ -5,10 +5,12 @@
* 2.0.
*/
import { ConfigKey, MonitorFields } from '../runtime_types';
import { ConfigKey, MonitorFields, ThrottlingConfig } from '../runtime_types';
export type Validator = (config: Partial<MonitorFields>) => boolean;
export type NamespaceValidator = (config: Partial<MonitorFields>) => false | string;
export type Validator = (config: Partial<MonitorFields & ThrottlingConfig>) => boolean;
export type NamespaceValidator = (
config: Partial<MonitorFields & ThrottlingConfig>
) => false | string;
export type ConfigValidation = Omit<Record<ConfigKey, Validator>, ConfigKey.NAMESPACE> &
Record<ConfigKey.NAMESPACE, NamespaceValidator>;

View file

@ -0,0 +1,81 @@
/*
* 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 { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import React from 'react';
import { PROFILE_VALUES_ENUM } from '../../../../../../../common/constants/monitor_defaults';
import { ThrottlingConfig } from '../../../../../../../common/runtime_types';
export const ConnectionProfile = ({
throttling,
id,
}: {
throttling?: ThrottlingConfig;
id: string;
}) => {
if (id === PROFILE_VALUES_ENUM.NO_THROTTLING) {
return (
<EuiFlexGroup alignItems="baseline" gutterSize="xs">
<EuiFlexItem grow={false}>
<EuiText>
<FormattedMessage
id="xpack.synthetics.monitorAddEdit.throttling.connectionProfile.disabled.label"
defaultMessage="No throttling"
/>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
);
}
if (throttling && throttling.value) {
const { label, value } = throttling;
return (
<EuiFlexGroup alignItems="baseline" gutterSize="xs">
<EuiFlexItem grow={false}>
<EuiText>{label}</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="xs" color="subdued">
<FormattedMessage
id="xpack.synthetics.monitorAddEdit.throttling.connectionProfile"
defaultMessage="({download} Mbps, {upload} Mbps, {latency} ms)"
values={{
download: value.download,
upload: value.upload,
latency: value.latency,
}}
/>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
);
} else {
return (
<EuiFlexGroup alignItems="baseline" gutterSize="xs">
<EuiFlexItem grow={false}>
<EuiText>
<FormattedMessage
id="xpack.synthetics.monitorAddEdit.throttling.connectionProfile.custom.label"
defaultMessage="Custom"
/>
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="xs" color="subdued">
<FormattedMessage
id="xpack.synthetics.monitorAddEdit.throttling.connectionProfile.custom"
defaultMessage="(X Mbps, Y Mbps, Z ms)"
/>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
);
}
};

View file

@ -0,0 +1,81 @@
/*
* 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 React from 'react';
import { fireEvent, screen } from '@testing-library/react';
import { PROFILE_OPTIONS, ThrottlingConfigField } from './throttling_config_field';
import { render } from '../../../../utils/testing';
import { PROFILES_MAP } from '../../../../../../../common/constants/monitor_defaults';
describe('ThrottlingConfigField', () => {
it('renders', async () => {
render(
<ThrottlingConfigField
ariaLabel={'ariaLabel'}
initialValue={PROFILES_MAP.default}
value={PROFILES_MAP.default}
id={'id'}
options={PROFILE_OPTIONS}
onChange={() => {}}
/>
);
expect(await screen.findByText('(5 Mbps, 3 Mbps, 20 ms)')).toBeInTheDocument();
});
it('selects custom values', async () => {
const onChange = jest.fn();
render(
<ThrottlingConfigField
ariaLabel={'ariaLabel'}
initialValue={PROFILES_MAP.default}
value={PROFILES_MAP.default}
id={'id'}
options={PROFILE_OPTIONS}
onChange={onChange}
/>
);
expect(await screen.findByText('(5 Mbps, 3 Mbps, 20 ms)')).toBeInTheDocument();
fireEvent.click(screen.getByTestId('syntheticsThrottlingSelect'));
fireEvent.click(await screen.findByTestId('syntheticsThrottlingSelectCustom'));
const customValue = {
id: 'custom',
label: 'Custom',
value: { download: '5', latency: '20', upload: '3' },
};
expect(onChange).toHaveBeenCalledWith(customValue);
});
it('changes custom values', async () => {
const onChange = jest.fn();
const customValue = {
id: 'custom',
label: 'Custom',
value: { download: '5', latency: '20', upload: '3' },
};
render(
<ThrottlingConfigField
ariaLabel={'ariaLabel'}
initialValue={customValue}
value={customValue}
id={'id'}
options={PROFILE_OPTIONS}
onChange={onChange}
/>
);
fireEvent.input(screen.getByTestId('syntheticsBrowserUploadSpeed'), {
target: { value: '10' },
});
expect(onChange).toHaveBeenCalledWith({
...customValue,
value: { ...customValue.value, upload: '10' },
});
});
});

View file

@ -0,0 +1,79 @@
/*
* 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 React from 'react';
import { EuiSuperSelect } from '@elastic/eui';
import { useConnectionProfiles } from './use_connection_profiles';
import { ThrottlingDisabledCallout } from './throttling_disabled_callout';
import { ThrottlingConfig } from '../../../../../../../common/runtime_types';
import { ThrottlingFields } from './throttling_fields';
import { PROFILE_VALUES_ENUM, PROFILE_VALUES, PROFILES_MAP, CUSTOM_LABEL } from '../../constants';
import { ConnectionProfile } from './connection_profile';
export interface ThrottlingConfigFieldProps {
ariaLabel: string;
id: string;
onChange: (value: ThrottlingConfig) => void;
value: ThrottlingConfig;
placeholder?: string;
height?: string;
readOnly?: boolean;
disabled?: boolean;
fullWidth?: boolean;
options: typeof PROFILE_OPTIONS;
initialValue?: ThrottlingConfig;
}
export const ThrottlingConfigField = (props: ThrottlingConfigFieldProps) => {
const { value, initialValue } = props;
const isCustom = PROFILES_MAP[value?.id] === undefined;
const isThrottlingDisabled = value?.id === PROFILE_VALUES_ENUM.NO_THROTTLING;
const options = useConnectionProfiles(initialValue);
return (
<>
<EuiSuperSelect
data-test-subj="syntheticsThrottlingSelect"
options={options}
onChange={(newValue) => {
if (newValue === PROFILE_VALUES_ENUM.CUSTOM) {
props.onChange({
...PROFILES_MAP[PROFILE_VALUES_ENUM.DEFAULT],
id: PROFILE_VALUES_ENUM.CUSTOM,
label: CUSTOM_LABEL,
});
} else {
props.onChange({
...PROFILES_MAP[PROFILE_VALUES_ENUM.DEFAULT],
...PROFILES_MAP[newValue],
});
}
}}
defaultValue={PROFILE_VALUES_ENUM.DEFAULT}
valueOfSelected={value?.id}
fullWidth={props.fullWidth}
readOnly={props.readOnly}
/>
{isThrottlingDisabled && <ThrottlingDisabledCallout />}
{isCustom && (
<ThrottlingFields
throttling={props?.value}
setValue={props.onChange}
readOnly={props.readOnly}
/>
)}
</>
);
};
export const PROFILE_OPTIONS = PROFILE_VALUES.map(({ id }) => ({
value: id,
inputDisplay: <ConnectionProfile throttling={PROFILES_MAP[id]} id={id} />,
'data-test-subj': `syntheticsThrottlingSelect-${id}`,
}));

View file

@ -0,0 +1,33 @@
/*
* 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 { EuiCallOut, EuiSpacer } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import React from 'react';
export const ThrottlingDisabledCallout = () => {
return (
<>
<EuiSpacer size="s" />
<EuiCallOut
title={
<FormattedMessage
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.automatic_node_cap.title"
defaultMessage="Automatic cap"
/>
}
color="warning"
iconType="warning"
>
<FormattedMessage
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.automatic_node_cap.message"
defaultMessage="When disabling throttling, your monitor will still have its bandwidth capped by the configurations of the Synthetics Nodes in which it's running."
/>
</EuiCallOut>
</>
);
};

View file

@ -0,0 +1,91 @@
/*
* 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 { EuiFieldNumber, EuiFormRow, EuiText } from '@elastic/eui';
import React from 'react';
import { i18n } from '@kbn/i18n';
import { Validation } from '../../../../../../../common/types';
import {
BandwidthLimitKey,
ConfigKey,
DEFAULT_BANDWIDTH_LIMIT,
ThrottlingConfig,
ThrottlingConfigValue,
} from '../../../../../../../common/runtime_types';
import { ThrottlingExceededMessage } from './throttling_exceeded_callout';
import { OptionalLabel } from '../optional_label';
export const ThrottlingDownloadField = ({
handleInputChange,
readOnly,
onFieldBlur,
validate,
throttling,
throttlingValue,
}: {
readOnly?: boolean;
handleInputChange: (value: string) => void;
onFieldBlur?: (field: keyof ThrottlingConfigValue) => void;
validate?: Validation;
throttling: ThrottlingConfig;
throttlingValue: ThrottlingConfigValue;
}) => {
const maxDownload = Number(DEFAULT_BANDWIDTH_LIMIT[BandwidthLimitKey.DOWNLOAD]);
const exceedsDownloadLimits = Number(throttlingValue.download) > maxDownload;
return (
<EuiFormRow
fullWidth
label={DOWNLOAD_LABEL}
labelAppend={<OptionalLabel />}
isInvalid={
(validate ? !!validate?.[ConfigKey.THROTTLING_CONFIG]?.(throttling) : false) ||
exceedsDownloadLimits
}
error={
exceedsDownloadLimits ? (
<ThrottlingExceededMessage throttlingField="download" limit={maxDownload} />
) : (
DOWNLOAD_SPEED_ERROR
)
}
>
<EuiFieldNumber
fullWidth
min={0}
step={0.001}
value={throttlingValue.download}
onChange={(event) => {
handleInputChange(event.target.value);
}}
onBlur={() => onFieldBlur?.('download')}
data-test-subj="syntheticsBrowserDownloadSpeed"
append={
<EuiText size="xs">
<strong>Mbps</strong>
</EuiText>
}
readOnly={readOnly}
/>
</EuiFormRow>
);
};
export const DOWNLOAD_LABEL = i18n.translate(
'xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.download.label',
{
defaultMessage: 'Download Speed',
}
);
export const DOWNLOAD_SPEED_ERROR = i18n.translate(
'xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.download.error',
{
defaultMessage: 'Download speed must be greater than zero.',
}
);

View file

@ -0,0 +1,49 @@
/*
* 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 { EuiCallOut, EuiSpacer } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import React from 'react';
export const ThrottlingExceededCallout = () => {
return (
<>
<EuiSpacer />
<EuiCallOut
title={
<FormattedMessage
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.exceeded_throttling.title"
defaultMessage="You've exceeded the Synthetics Node bandwidth limits"
/>
}
color="warning"
iconType="warning"
>
<FormattedMessage
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.exceeded_throttling.message"
defaultMessage="When using throttling values larger than a Synthetics Node bandwidth limit, your monitor will still have its bandwidth capped."
/>
</EuiCallOut>
</>
);
};
export const ThrottlingExceededMessage = ({
throttlingField,
limit,
}: {
throttlingField: string;
limit: number;
}) => {
return (
<FormattedMessage
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.throttling_exceeded.message"
defaultMessage="You have exceeded the { throttlingField } limit for Synthetic Nodes. The { throttlingField } value can't be larger than { limit }Mbps."
values={{ throttlingField, limit }}
/>
);
};

View file

@ -0,0 +1,86 @@
/*
* 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 React from 'react';
import { fireEvent, screen } from '@testing-library/react';
import { render } from '../../../../utils/testing';
import { PROFILES_MAP } from '../../../../../../../common/constants/monitor_defaults';
import { ThrottlingFields } from './throttling_fields';
describe('ThrottlingFields', () => {
it('renders', async () => {
render(<ThrottlingFields throttling={PROFILES_MAP.default} setValue={() => {}} />);
expect(await screen.findByText('Download Speed')).toBeInTheDocument();
expect(await screen.findByText('Upload Speed')).toBeInTheDocument();
expect(await screen.findByText('Latency')).toBeInTheDocument();
});
it('calls setValue on change', async () => {
const setValue = jest.fn();
render(<ThrottlingFields throttling={PROFILES_MAP.default} setValue={setValue} />);
const throttling = PROFILES_MAP.default;
const download = await screen.findByTestId('syntheticsBrowserDownloadSpeed');
fireEvent.change(download, { target: { value: '10' } });
expect(setValue).toHaveBeenCalledWith({
...throttling,
label: 'Custom',
id: 'custom',
value: { download: '10', latency: '20', upload: '3' },
});
const upload = await screen.findByTestId('syntheticsBrowserUploadSpeed');
fireEvent.change(upload, { target: { value: '10' } });
expect(setValue).toHaveBeenLastCalledWith({
...throttling,
label: 'Custom',
id: 'custom',
value: { download: '5', latency: '20', upload: '10' },
});
const latency = await screen.findByTestId('syntheticsBrowserLatency');
fireEvent.change(latency, { target: { value: '10' } });
expect(setValue).toHaveBeenLastCalledWith({
...throttling,
label: 'Custom',
id: 'custom',
value: { download: '5', latency: '10', upload: '3' },
});
});
it('shows maximum bandwidth callout on download and upload change', async () => {
const setValue = jest.fn();
const throttling = PROFILES_MAP.default;
render(
<ThrottlingFields
throttling={{
...throttling,
value: {
...throttling.value!,
download: '500',
upload: '500',
},
}}
setValue={setValue}
/>
);
expect(
await screen.findByText(
'When using throttling values larger than a Synthetics Node bandwidth limit, your monitor will still have its bandwidth capped.'
)
).toBeInTheDocument();
expect(
await screen.findByText(
"You have exceeded the download limit for Synthetic Nodes. The download value can't be larger than 100Mbps."
)
).toBeInTheDocument();
});
});

View file

@ -0,0 +1,97 @@
/*
* 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 React, { memo, useCallback } from 'react';
import { EuiSpacer } from '@elastic/eui';
import { ThrottlingUploadField } from './throttling_upload_field';
import { ThrottlingExceededCallout } from './throttling_exceeded_callout';
import {
BandwidthLimitKey,
DEFAULT_BANDWIDTH_LIMIT,
ThrottlingConfig,
ThrottlingConfigValue,
} from '../../../../../../../common/runtime_types';
import { Validation } from '../../types';
import { ThrottlingDisabledCallout } from './throttling_disabled_callout';
import { ThrottlingDownloadField } from './throttling_download_field';
import { ThrottlingLatencyField } from './throttling_latency_field';
import { CUSTOM_LABEL, PROFILE_VALUES_ENUM } from '../../constants';
interface Props {
validate?: Validation;
minColumnWidth?: string;
onFieldBlur?: (field: keyof ThrottlingConfigValue) => void;
readOnly?: boolean;
throttling: ThrottlingConfig;
setValue: (value: ThrottlingConfig) => void;
}
export const ThrottlingFields = memo<Props>(
({ validate, onFieldBlur, setValue, readOnly = false, throttling }) => {
const maxDownload = DEFAULT_BANDWIDTH_LIMIT[BandwidthLimitKey.DOWNLOAD];
const maxUpload = DEFAULT_BANDWIDTH_LIMIT[BandwidthLimitKey.UPLOAD];
const handleInputChange = useCallback(
({ value, configKey }: { value: string; configKey: string }) => {
setValue({
...throttling,
value: { ...throttling.value!, [configKey]: value },
label: CUSTOM_LABEL,
id: PROFILE_VALUES_ENUM.CUSTOM,
});
},
[setValue, throttling]
);
const exceedsDownloadLimits = Number(throttling.value?.download) > maxDownload;
const exceedsUploadLimits = Number(throttling.value?.upload) > maxUpload;
const isThrottlingEnabled = throttling.id !== PROFILE_VALUES_ENUM.NO_THROTTLING;
const hasExceededLimits = isThrottlingEnabled && (exceedsDownloadLimits || exceedsUploadLimits);
if (!isThrottlingEnabled || !throttling.value) {
return <ThrottlingDisabledCallout />;
}
return (
<div>
{hasExceededLimits && <ThrottlingExceededCallout />}
<EuiSpacer size="m" />
<ThrottlingDownloadField
validate={validate}
onFieldBlur={onFieldBlur}
throttling={throttling}
throttlingValue={throttling.value}
handleInputChange={(val) => {
handleInputChange({ value: val, configKey: 'download' });
}}
readOnly={readOnly}
/>
<ThrottlingUploadField
throttlingValue={throttling.value}
validate={validate}
onFieldBlur={onFieldBlur}
throttling={throttling}
handleInputChange={(val) => {
handleInputChange({ value: val, configKey: 'upload' });
}}
readOnly={readOnly}
/>
<ThrottlingLatencyField
throttlingValue={throttling.value}
validate={validate}
onFieldBlur={onFieldBlur}
throttling={throttling}
handleInputChange={(val) => {
handleInputChange({ value: val, configKey: 'latency' });
}}
readOnly={readOnly}
/>
</div>
);
}
);

View file

@ -0,0 +1,72 @@
/*
* 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 { EuiFieldNumber, EuiFormRow, EuiText } from '@elastic/eui';
import React from 'react';
import { i18n } from '@kbn/i18n';
import { Validation } from '../../../../../../../common/types';
import {
ConfigKey,
ThrottlingConfig,
ThrottlingConfigValue,
} from '../../../../../../../common/runtime_types';
import { OptionalLabel } from '../optional_label';
export const ThrottlingLatencyField = ({
throttling,
readOnly,
onFieldBlur,
validate,
handleInputChange,
throttlingValue,
}: {
readOnly?: boolean;
handleInputChange: (value: string) => void;
onFieldBlur?: (field: keyof ThrottlingConfigValue) => void;
validate?: Validation;
throttling: ThrottlingConfig;
throttlingValue: ThrottlingConfigValue;
}) => {
return (
<EuiFormRow
fullWidth
label={LATENCY_LABEL}
labelAppend={<OptionalLabel />}
isInvalid={validate ? !!validate?.[ConfigKey.THROTTLING_CONFIG]?.(throttling) : false}
error={LATENCY_NEGATIVE_ERROR}
>
<EuiFieldNumber
fullWidth
min={0}
value={throttlingValue.latency}
onChange={(event) => handleInputChange(event.target.value)}
onBlur={() => onFieldBlur?.('latency')}
data-test-subj="syntheticsBrowserLatency"
append={
<EuiText size="xs">
<strong>ms</strong>
</EuiText>
}
readOnly={readOnly}
/>
</EuiFormRow>
);
};
export const LATENCY_LABEL = i18n.translate(
'xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.latency.label',
{
defaultMessage: 'Latency',
}
);
export const LATENCY_NEGATIVE_ERROR = i18n.translate(
'xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.latency.error',
{
defaultMessage: 'Latency must not be negative.',
}
);

View file

@ -0,0 +1,87 @@
/*
* 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 { EuiFieldNumber, EuiFormRow, EuiText } from '@elastic/eui';
import React from 'react';
import { i18n } from '@kbn/i18n';
import { Validation } from '../../../../../../../common/types';
import {
BandwidthLimitKey,
ConfigKey,
DEFAULT_BANDWIDTH_LIMIT,
ThrottlingConfig,
ThrottlingConfigValue,
} from '../../../../../../../common/runtime_types';
import { ThrottlingExceededMessage } from './throttling_exceeded_callout';
import { OptionalLabel } from '../optional_label';
export const ThrottlingUploadField = ({
readOnly,
onFieldBlur,
throttling,
validate,
handleInputChange,
throttlingValue,
}: {
readOnly?: boolean;
handleInputChange: (value: string) => void;
onFieldBlur?: (field: keyof ThrottlingConfigValue) => void;
validate?: Validation;
throttling: ThrottlingConfig;
throttlingValue: ThrottlingConfigValue;
}) => {
const maxUpload = Number(DEFAULT_BANDWIDTH_LIMIT[BandwidthLimitKey.UPLOAD]);
const exceedsUploadLimits = Number(throttlingValue.upload) > maxUpload;
return (
<EuiFormRow
fullWidth
label={UPLOAD_LABEL}
labelAppend={<OptionalLabel />}
isInvalid={
(validate ? !!validate?.[ConfigKey.THROTTLING_CONFIG]?.(throttling) : false) ||
exceedsUploadLimits
}
error={
exceedsUploadLimits ? (
<ThrottlingExceededMessage throttlingField="upload" limit={maxUpload} />
) : (
UPLOAD_SPEED_ERROR
)
}
>
<EuiFieldNumber
fullWidth
min={0}
step={0.001}
value={throttlingValue.upload}
onChange={(event) => handleInputChange(event.target.value)}
onBlur={() => onFieldBlur?.('upload')}
data-test-subj="syntheticsBrowserUploadSpeed"
append={
<EuiText size="xs">
<strong>Mbps</strong>
</EuiText>
}
readOnly={readOnly}
/>
</EuiFormRow>
);
};
export const UPLOAD_LABEL = i18n.translate(
'xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.upload.label',
{ defaultMessage: 'Upload Speed' }
);
export const UPLOAD_SPEED_ERROR = i18n.translate(
'xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.upload.error',
{
defaultMessage: 'Upload speed must be greater than zero.',
}
);

View file

@ -0,0 +1,30 @@
/*
* 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 React, { useMemo } from 'react';
import { PROFILE_VALUES_ENUM } from '../../../../../../../common/constants/monitor_defaults';
import { ConnectionProfile } from './connection_profile';
import { PROFILE_OPTIONS } from './throttling_config_field';
import { ThrottlingConfig } from '../../../../../../../common/runtime_types';
export const useConnectionProfiles = (initialValue?: ThrottlingConfig) => {
return useMemo(() => {
return [
...PROFILE_OPTIONS,
{
value: PROFILE_VALUES_ENUM.CUSTOM,
inputDisplay: (
<ConnectionProfile
id="custom"
throttling={initialValue?.id === PROFILE_VALUES_ENUM.CUSTOM ? initialValue : undefined}
/>
),
'data-test-subj': 'syntheticsThrottlingSelectCustom',
},
];
}, [initialValue]);
};

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { ConfigKey, DataStream, FormMonitorType, SyntheticsMonitor } from '../types';
import { DEFAULT_FIELDS } from '../constants';
import { DEFAULT_FIELDS, PROFILE_VALUES_ENUM, PROFILES_MAP } from '../constants';
import { formatDefaultFormValues } from './defaults';
describe('defaults', () => {
@ -52,11 +52,15 @@ describe('defaults', () => {
'ssl.verification_mode': 'full',
synthetics_args: [],
tags: [],
'throttling.config': '5d/3u/20l',
'throttling.download_speed': '5',
'throttling.is_enabled': true,
'throttling.latency': '20',
'throttling.upload_speed': '3',
throttling: {
value: {
download: '5',
latency: '20',
upload: '3',
},
id: 'default',
label: 'Default',
},
timeout: '16',
type: 'browser',
'url.port': null,
@ -114,11 +118,7 @@ describe('defaults', () => {
'ssl.verification_mode': 'full',
synthetics_args: [],
tags: [],
'throttling.config': '5d/3u/20l',
'throttling.download_speed': '5',
'throttling.is_enabled': true,
'throttling.latency': '20',
'throttling.upload_speed': '3',
throttling: PROFILES_MAP[PROFILE_VALUES_ENUM.DEFAULT],
timeout: '16',
type: 'browser',
'url.port': null,

View file

@ -14,8 +14,6 @@ import {
EuiComboBoxOptionOption,
EuiFlexGroup,
EuiFlexItem,
EuiSuperSelect,
EuiText,
EuiLink,
EuiTextArea,
EuiSelectProps,
@ -27,10 +25,13 @@ import {
EuiCheckboxProps,
EuiTextAreaProps,
EuiButtonGroupProps,
EuiSuperSelectProps,
EuiHighlight,
EuiBadge,
} from '@elastic/eui';
import {
PROFILE_OPTIONS,
ThrottlingConfigFieldProps,
} from '../fields/throttling/throttling_config_field';
import {
FieldText,
FieldNumber,
@ -53,6 +54,7 @@ import {
ResponseBodyIndexField,
ResponseBodyIndexFieldProps,
ControlledFieldProp,
ThrottlingWrapper,
} from './field_wrappers';
import { getDocLinks } from '../../../../../kibana_services';
import { useMonitorName } from '../hooks/use_monitor_name';
@ -67,12 +69,9 @@ import {
VerificationMode,
FieldMap,
FormLocation,
ThrottlingConfig,
} from '../types';
import {
AlertConfigKey,
DEFAULT_BROWSER_ADVANCED_FIELDS,
ALLOWED_SCHEDULES_IN_MINUTES,
} from '../constants';
import { AlertConfigKey, ALLOWED_SCHEDULES_IN_MINUTES } from '../constants';
import { getDefaultFormFields } from './defaults';
import { validate, validateHeaders, WHOLE_NUMBERS_ONLY, FLOATS_ONLY } from './validation';
@ -1123,41 +1122,23 @@ export const FIELD = (readOnly?: boolean): FieldMap => ({
},
[ConfigKey.THROTTLING_CONFIG]: {
fieldKey: ConfigKey.THROTTLING_CONFIG,
component: EuiSuperSelect,
component: ThrottlingWrapper,
label: i18n.translate('xpack.synthetics.monitorConfig.throttling.label', {
defaultMessage: 'Connection profile',
}),
required: true,
controlled: true,
helpText: i18n.translate('xpack.synthetics.monitorConfig.throttling.helpText', {
defaultMessage:
'Simulate network throttling (download, upload, latency). More options will be added in a future version.',
}),
props: (): EuiSuperSelectProps<string> => ({
options: [
{
value: DEFAULT_BROWSER_ADVANCED_FIELDS[ConfigKey.THROTTLING_CONFIG],
inputDisplay: (
<EuiFlexGroup alignItems="baseline" gutterSize="xs" responsive={false}>
<EuiFlexItem grow={false}>
<EuiText>
{i18n.translate('xpack.synthetics.monitorConfig.throttling.options.default', {
defaultMessage: 'Default',
})}
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="xs" color="subdued">
{'(5 Mbps, 3 Mbps, 20 ms)'}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
),
},
],
readOnly,
disabled: true, // currently disabled through 1.0 until we define connection profiles
defaultMessage: 'Simulate network throttling (download, upload, latency).',
}),
props: ({ formState }): Partial<ThrottlingConfigFieldProps> => {
return {
options: PROFILE_OPTIONS,
readOnly,
disabled: false,
initialValue: formState.defaultValues?.[ConfigKey.THROTTLING_CONFIG] as ThrottlingConfig,
};
},
validation: () => ({
required: true,
}),

View file

@ -26,6 +26,10 @@ import {
EuiComboBox,
EuiComboBoxProps,
} from '@elastic/eui';
import {
ThrottlingConfigField,
ThrottlingConfigFieldProps,
} from '../fields/throttling/throttling_config_field';
import { SourceField, SourceFieldProps } from '../fields/source_field';
import {
FormattedComboBox as DefaultFormattedComboBox,
@ -132,3 +136,7 @@ export const RequestBodyField = React.forwardRef<unknown, DefaultRequestBodyFiel
export const ResponseBodyIndexField = React.forwardRef<unknown, DefaultResponseBodyIndexFieldProps>(
(props, _ref) => <DefaultResponseBodyIndexField {...props} />
);
export const ThrottlingWrapper = React.forwardRef<unknown, ThrottlingConfigFieldProps>(
(props, _ref) => <ThrottlingConfigField {...props} />
);

View file

@ -121,6 +121,7 @@ export const BROWSER_ADVANCED = (readOnly: boolean) => [
}
),
components: [
FIELD(readOnly)[ConfigKey.THROTTLING_CONFIG],
FIELD(readOnly)[ConfigKey.IGNORE_HTTPS_ERRORS],
FIELD(readOnly)[ConfigKey.SYNTHETICS_ARGS],
FIELD(readOnly)[ConfigKey.PLAYWRIGHT_OPTIONS],
@ -209,7 +210,6 @@ export const FORM_CONFIG = (readOnly: boolean): FieldConfig => ({
FIELD(readOnly)[ConfigKey.NAME],
FIELD(readOnly)[ConfigKey.LOCATIONS],
FIELD(readOnly)[`${ConfigKey.SCHEDULE}.number`],
FIELD(readOnly)[ConfigKey.THROTTLING_CONFIG],
FIELD(readOnly)[ConfigKey.ENABLED],
FIELD(readOnly)[AlertConfigKey.STATUS_ENABLED],
],
@ -236,7 +236,6 @@ export const FORM_CONFIG = (readOnly: boolean): FieldConfig => ({
FIELD(readOnly)[ConfigKey.TEXT_ASSERTION],
FIELD(readOnly)[ConfigKey.LOCATIONS],
FIELD(readOnly)[`${ConfigKey.SCHEDULE}.number`],
FIELD(readOnly)[ConfigKey.THROTTLING_CONFIG],
FIELD(readOnly)[ConfigKey.ENABLED],
FIELD(readOnly)[AlertConfigKey.STATUS_ENABLED],
],

View file

@ -7,7 +7,11 @@
import { format, ALLOWED_FIELDS } from './formatter';
import { DataStream } from '../../../../../../common/runtime_types';
import { DEFAULT_FIELDS } from '../../../../../../common/constants/monitor_defaults';
import {
DEFAULT_FIELDS,
PROFILE_VALUES_ENUM,
PROFILES_MAP,
} from '../../../../../../common/constants/monitor_defaults';
describe('format', () => {
let formValues: Record<string, unknown>;
@ -199,11 +203,6 @@ describe('format', () => {
'filter_journeys.match': '',
'filter_journeys.tags': [],
ignore_https_errors: false,
'throttling.is_enabled': true,
'throttling.download_speed': '5',
'throttling.upload_speed': '3',
'throttling.latency': '20',
'throttling.config': '5d/3u/20l',
'ssl.certificate_authorities': '',
'ssl.certificate': '',
'ssl.key': '',
@ -215,9 +214,7 @@ describe('format', () => {
script: '',
fileName: '',
},
throttling: {
config: '5d/3u/20l',
},
throttling: PROFILES_MAP[PROFILE_VALUES_ENUM.DEFAULT],
source: {
inline: {
type: scriptType,
@ -278,11 +275,6 @@ describe('format', () => {
'ssl.verification_mode': 'full',
synthetics_args: [],
tags: [],
'throttling.config': '5d/3u/20l',
'throttling.download_speed': '5',
'throttling.is_enabled': true,
'throttling.latency': '20',
'throttling.upload_speed': '3',
timeout: '16',
type: 'browser',
'url.port': null,

View file

@ -148,11 +148,15 @@ const validateThrottleValue = (speed: string | undefined, allowZero?: boolean) =
const validateBrowser: ValidationLibrary = {
...validateCommon,
[ConfigKey.SOURCE_INLINE]: ({ [ConfigKey.SOURCE_INLINE]: inlineScript }) => !inlineScript,
[ConfigKey.DOWNLOAD_SPEED]: ({ [ConfigKey.DOWNLOAD_SPEED]: downloadSpeed }) =>
validateThrottleValue(downloadSpeed),
[ConfigKey.UPLOAD_SPEED]: ({ [ConfigKey.UPLOAD_SPEED]: uploadSpeed }) =>
validateThrottleValue(uploadSpeed),
[ConfigKey.LATENCY]: ({ [ConfigKey.LATENCY]: latency }) => validateThrottleValue(latency, true),
[ConfigKey.THROTTLING_CONFIG]: ({ throttling }) => {
if (!throttling || throttling.value === null) return true;
const { download, upload, latency } = throttling.value;
return (
validateThrottleValue(String(download)) ||
validateThrottleValue(String(upload)) ||
validateThrottleValue(String(latency), true)
);
},
[ConfigKey.PLAYWRIGHT_OPTIONS]: ({ [ConfigKey.PLAYWRIGHT_OPTIONS]: playwrightOptions }) =>
playwrightOptions ? !validJSONFormat(playwrightOptions) : false,
[ConfigKey.PARAMS]: ({ [ConfigKey.PARAMS]: params }) =>

View file

@ -12,6 +12,10 @@ import { MonitorEditPage } from './monitor_edit_page';
import { ConfigKey } from '../../../../../common/runtime_types';
import * as observabilityPublic from '@kbn/observability-plugin/public';
import {
PROFILE_VALUES_ENUM,
PROFILES_MAP,
} from '../../../../../common/constants/monitor_defaults';
mockGlobals();
@ -45,6 +49,7 @@ describe('MonitorEditPage', () => {
[ConfigKey.MONITOR_SOURCE_TYPE]: 'ui',
[ConfigKey.FORM_MONITOR_TYPE]: 'multistep',
[ConfigKey.LOCATIONS]: [],
[ConfigKey.THROTTLING_CONFIG]: PROFILES_MAP[PROFILE_VALUES_ENUM.DEFAULT],
},
},
refetch: () => null,

View file

@ -430,11 +430,15 @@ function getMonitorDetailsMockSlice() {
'filter_journeys.match': '',
'filter_journeys.tags': [],
ignore_https_errors: false,
'throttling.is_enabled': true,
'throttling.download_speed': '5',
'throttling.upload_speed': '3',
'throttling.latency': '20',
'throttling.config': '5d/3u/20l',
throttling: {
value: {
download: '5',
upload: '3',
latency: '20',
},
label: 'Regular 3G',
id: 'three_g',
},
'ssl.certificate_authorities': '',
'ssl.certificate': '',
'ssl.key': '',

View file

@ -8,7 +8,11 @@ import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/s
import { migration880 } from './8.8.0';
import { migrationMocks } from '@kbn/core/server/mocks';
import { ConfigKey, ScheduleUnit } from '../../../../../../common/runtime_types';
import { ALLOWED_SCHEDULES_IN_MINUTES } from '../../../../../../common/constants/monitor_defaults';
import {
ALLOWED_SCHEDULES_IN_MINUTES,
PROFILE_VALUES_ENUM,
PROFILES_MAP,
} from '../../../../../../common/constants/monitor_defaults';
import {
browserUI,
browserProject,
@ -18,6 +22,8 @@ import {
httpUptimeUI,
} from './test_fixtures/8.7.0';
import { httpUI as httpUI850 } from './test_fixtures/8.5.0';
import { LegacyConfigKey } from '../../../../../../common/constants/monitor_management';
import { omit } from 'lodash';
const context = migrationMocks.createContext();
const encryptedSavedObjectsSetup = encryptedSavedObjectsMock.createSetup();
@ -151,11 +157,7 @@ describe('Monitor migrations v8.7.0 -> v8.8.0', () => {
'ssl.supported_protocols': ['TLSv1.1', 'TLSv1.2', 'TLSv1.3'],
'ssl.verification_mode': 'full',
tags: [],
'throttling.config': '5d/3u/20l',
'throttling.download_speed': '5',
'throttling.is_enabled': true,
'throttling.latency': '20',
'throttling.upload_speed': '3',
throttling: PROFILES_MAP[PROFILE_VALUES_ENUM.DEFAULT],
timeout: null,
type: 'browser',
'url.port': null,
@ -195,9 +197,24 @@ describe('Monitor migrations v8.7.0 -> v8.8.0', () => {
name: null,
},
};
// @ts-ignore specificially testing monitors with invalid values
// @ts-ignore specifically testing monitors with invalid values
const actual = migration880(encryptedSavedObjectsSetup)(invalidTestMonitor, context);
expect(actual).toEqual(invalidTestMonitor);
expect(actual).toEqual({
...invalidTestMonitor,
attributes: omit(
{
...invalidTestMonitor.attributes,
throttling: PROFILES_MAP[PROFILE_VALUES_ENUM.DEFAULT],
},
[
LegacyConfigKey.THROTTLING_CONFIG,
LegacyConfigKey.IS_THROTTLING_ENABLED,
LegacyConfigKey.DOWNLOAD_SPEED,
LegacyConfigKey.UPLOAD_SPEED,
LegacyConfigKey.LATENCY,
]
),
});
});
});
@ -413,4 +430,77 @@ describe('Monitor migrations v8.7.0 -> v8.8.0', () => {
expect(actual.attributes[ConfigKey.SCHEDULE].unit).toEqual(ScheduleUnit.MINUTES);
});
});
describe('throttling migration', () => {
it('handles migrating with enabled throttling', () => {
const actual = migration880(encryptedSavedObjectsSetup)(browserUI, context);
// @ts-ignore
expect(actual.attributes[ConfigKey.THROTTLING_CONFIG]).toEqual(
PROFILES_MAP[PROFILE_VALUES_ENUM.DEFAULT]
);
});
it('handles migrating with defined throttling value', () => {
const testMonitor = {
...browserUI,
attributes: {
...browserUI.attributes,
[LegacyConfigKey.UPLOAD_SPEED]: '0.75',
[LegacyConfigKey.DOWNLOAD_SPEED]: '9',
[LegacyConfigKey.LATENCY]: '170',
},
};
const actual = migration880(encryptedSavedObjectsSetup)(testMonitor, context);
// @ts-ignore
expect(actual.attributes[ConfigKey.THROTTLING_CONFIG]).toEqual({
id: '4g',
label: '4G',
value: {
download: '9',
upload: '0.75',
latency: '170',
},
});
});
it('handles migrating with custom throttling value', () => {
const testMonitor = {
...browserUI,
attributes: {
...browserUI.attributes,
[LegacyConfigKey.UPLOAD_SPEED]: '5',
[LegacyConfigKey.DOWNLOAD_SPEED]: '10',
[LegacyConfigKey.LATENCY]: '30',
},
};
const actual = migration880(encryptedSavedObjectsSetup)(testMonitor, context);
// @ts-ignore
expect(actual.attributes[ConfigKey.THROTTLING_CONFIG]).toEqual({
id: 'custom',
label: 'Custom',
value: {
download: '10',
upload: '5',
latency: '30',
},
});
});
it('handles migrating with disabled throttling', () => {
const testMonitor = {
...browserUI,
attributes: {
...browserUI.attributes,
[LegacyConfigKey.IS_THROTTLING_ENABLED]: false,
},
};
const actual = migration880(encryptedSavedObjectsSetup)(testMonitor, context);
// @ts-ignore
expect(actual.attributes[ConfigKey.THROTTLING_CONFIG]).toEqual({
id: 'no-throttling',
label: 'No throttling',
value: null,
});
});
});
});

View file

@ -6,23 +6,31 @@
*/
import { omit } from 'lodash';
import { EncryptedSavedObjectsPluginSetup } from '@kbn/encrypted-saved-objects-plugin/server';
import { SavedObjectUnsanitizedDoc } from '@kbn/core/server';
import { SavedObjectMigrationContext, SavedObjectUnsanitizedDoc } from '@kbn/core/server';
import { LegacyConfigKey } from '../../../../../../common/constants/monitor_management';
import {
ConfigKey,
SyntheticsMonitorWithSecrets,
MonitorFields,
BrowserFields,
ConfigKey,
MonitorFields,
ScheduleUnit,
SyntheticsMonitorWithSecrets,
ThrottlingConfig,
} from '../../../../../../common/runtime_types';
import { ALLOWED_SCHEDULES_IN_MINUTES } from '../../../../../../common/constants/monitor_defaults';
import {
ALLOWED_SCHEDULES_IN_MINUTES,
CUSTOM_LABEL,
PROFILE_VALUES,
PROFILE_VALUES_ENUM,
PROFILES_MAP,
} from '../../../../../../common/constants/monitor_defaults';
import {
LEGACY_SYNTHETICS_MONITOR_ENCRYPTED_TYPE,
SYNTHETICS_MONITOR_ENCRYPTED_TYPE,
} from '../../synthetics_monitor';
import { validateMonitor } from '../../../../../routes/monitor_cruds/monitor_validation';
import {
normalizeMonitorSecretAttributes,
formatSecrets,
normalizeMonitorSecretAttributes,
} from '../../../../../synthetics_service/utils/secrets';
export const migration880 = (encryptedSavedObjects: EncryptedSavedObjectsPluginSetup) => {
@ -61,6 +69,8 @@ export const migration880 = (encryptedSavedObjects: EncryptedSavedObjectsPluginS
},
};
if (migrated.attributes.type === 'browser') {
migrated = updateThrottlingFields(migrated, logger);
try {
const normalizedMonitorAttributes = normalizeMonitorSecretAttributes(migrated.attributes);
migrated = {
@ -118,3 +128,103 @@ const omitZipUrlFields = (fields: BrowserFields) => {
return formatSecrets(validationResult.decodedMonitor);
};
const updateThrottlingFields = (
doc: SavedObjectUnsanitizedDoc<
SyntheticsMonitorWithSecrets &
Partial<{
[LegacyConfigKey.THROTTLING_CONFIG]: string;
[LegacyConfigKey.IS_THROTTLING_ENABLED]: boolean;
[LegacyConfigKey.DOWNLOAD_SPEED]: string;
[LegacyConfigKey.UPLOAD_SPEED]: string;
[LegacyConfigKey.LATENCY]: string;
[ConfigKey.THROTTLING_CONFIG]: ThrottlingConfig;
}>
>,
logger: SavedObjectMigrationContext
) => {
try {
const { attributes } = doc;
const migrated = {
...doc,
attributes: {
...doc.attributes,
[ConfigKey.CONFIG_HASH]: '',
},
};
const isThrottlingEnabled = attributes[LegacyConfigKey.IS_THROTTLING_ENABLED];
if (isThrottlingEnabled) {
const defaultProfileValue = PROFILES_MAP[PROFILE_VALUES_ENUM.DEFAULT].value!;
const download =
String(attributes[LegacyConfigKey.DOWNLOAD_SPEED]) || defaultProfileValue.download;
const upload = String(attributes[LegacyConfigKey.UPLOAD_SPEED]) || defaultProfileValue.upload;
const latency = String(attributes[LegacyConfigKey.LATENCY]) || defaultProfileValue.latency;
migrated.attributes[ConfigKey.THROTTLING_CONFIG] = getMatchingThrottlingConfig(
download,
upload,
latency
);
} else {
migrated.attributes[ConfigKey.THROTTLING_CONFIG] =
PROFILES_MAP[PROFILE_VALUES_ENUM.NO_THROTTLING];
}
// filter out legacy throttling fields
return {
...migrated,
attributes: omit(migrated.attributes, [
LegacyConfigKey.THROTTLING_CONFIG,
LegacyConfigKey.IS_THROTTLING_ENABLED,
LegacyConfigKey.DOWNLOAD_SPEED,
LegacyConfigKey.UPLOAD_SPEED,
LegacyConfigKey.LATENCY,
]),
};
} catch (e) {
logger.log.warn(
`Failed to migrate throttling fields from legacy Synthetics monitor: ${e.message}`
);
const { attributes } = doc;
attributes[ConfigKey.THROTTLING_CONFIG] = PROFILES_MAP[PROFILE_VALUES_ENUM.DEFAULT];
return {
...doc,
attributes: omit(attributes, [
LegacyConfigKey.THROTTLING_CONFIG,
LegacyConfigKey.IS_THROTTLING_ENABLED,
LegacyConfigKey.DOWNLOAD_SPEED,
LegacyConfigKey.UPLOAD_SPEED,
LegacyConfigKey.LATENCY,
]),
};
}
};
const getMatchingThrottlingConfig = (download: string, upload: string, latency: string) => {
const matchedProfile = PROFILE_VALUES.find(({ value }) => {
return (
Number(value?.download) === Number(download) &&
Number(value?.upload) === Number(upload) &&
Number(value?.latency) === Number(latency)
);
});
if (matchedProfile) {
return matchedProfile;
} else {
return {
id: PROFILE_VALUES_ENUM.CUSTOM,
label: CUSTOM_LABEL,
value: {
download: String(download),
upload: String(upload),
latency: String(latency),
},
};
}
};

View file

@ -170,6 +170,13 @@ export const getSyntheticsMonitorSavedObjectType = (
},
},
},
throttling: {
properties: {
label: {
type: 'keyword',
},
},
},
},
},
management: {

View file

@ -78,7 +78,7 @@ export const addSyntheticsProjectMonitorRoute: SyntheticsRestApiRouteFactory = (
};
} catch (error) {
server.logger.error(`Error adding monitors to project ${decodedProjectName}`);
if (error.output.statusCode === 404) {
if (error.output?.statusCode === 404) {
const spaceId = server.spaces?.spacesService.getSpaceId(request) ?? DEFAULT_SPACE_ID;
return response.notFound({ body: { message: `Kibana space '${spaceId}' does not exist` } });
}

View file

@ -175,11 +175,15 @@ describe('validateMonitor', () => {
[ConfigKey.JOURNEY_FILTERS_MATCH]: 'false',
[ConfigKey.JOURNEY_FILTERS_TAGS]: testTags,
[ConfigKey.IGNORE_HTTPS_ERRORS]: false,
[ConfigKey.IS_THROTTLING_ENABLED]: true,
[ConfigKey.DOWNLOAD_SPEED]: '5',
[ConfigKey.UPLOAD_SPEED]: '3',
[ConfigKey.LATENCY]: '20',
[ConfigKey.THROTTLING_CONFIG]: '5d/3u/20l',
[ConfigKey.THROTTLING_CONFIG]: {
value: {
download: '5',
upload: '3',
latency: '20',
},
id: 'test',
label: 'test',
},
};
testBrowserFields = {

View file

@ -15,21 +15,21 @@ import { arrayFormatter, objectFormatter, stringToObjectFormatter } from './form
export type BrowserFormatMap = Record<keyof BrowserFields, Formatter>;
const throttlingFormatter: Formatter = (fields) => {
if (!fields[ConfigKey.IS_THROTTLING_ENABLED]) return false;
const value = fields[ConfigKey.THROTTLING_CONFIG];
const defaultThrottling = DEFAULT_BROWSER_ADVANCED_FIELDS[ConfigKey.THROTTLING_CONFIG].value;
const thValue = value?.value;
if (!thValue || !defaultThrottling) return false;
if (thValue?.download === '0' && thValue?.upload === '0' && thValue?.latency === '0')
return false;
if (value?.label === 'no-throttling') return false;
return {
download: parseInt(
fields[ConfigKey.DOWNLOAD_SPEED] || DEFAULT_BROWSER_ADVANCED_FIELDS[ConfigKey.DOWNLOAD_SPEED],
10
),
upload: parseInt(
fields[ConfigKey.UPLOAD_SPEED] || DEFAULT_BROWSER_ADVANCED_FIELDS[ConfigKey.UPLOAD_SPEED],
10
),
latency: parseInt(
fields[ConfigKey.LATENCY] || DEFAULT_BROWSER_ADVANCED_FIELDS[ConfigKey.LATENCY],
10
),
download: Number(thValue?.download ?? defaultThrottling.download),
upload: Number(thValue?.upload ?? defaultThrottling.upload),
latency: Number(thValue?.latency ?? defaultThrottling.latency),
};
};

View file

@ -73,11 +73,15 @@ const testBrowserConfig: Partial<MonitorFields> = {
'filter_journeys.match': '',
'filter_journeys.tags': ['dev'],
ignore_https_errors: false,
'throttling.is_enabled': true,
'throttling.download_speed': '5',
'throttling.upload_speed': '3',
'throttling.latency': '20',
'throttling.config': '5d/3u/20l',
throttling: {
value: {
download: '5',
latency: '20',
upload: '3',
},
id: 'default',
label: 'default',
},
project_id: 'test-project',
};
@ -203,12 +207,16 @@ describe('browser fields', () => {
});
it('excludes UI fields', () => {
testBrowserConfig['throttling.is_enabled'] = false;
testBrowserConfig['throttling.upload_speed'] = '3';
const formattedConfig = formatMonitorConfigFields(
Object.keys(testBrowserConfig) as ConfigKey[],
testBrowserConfig,
{
...testBrowserConfig,
throttling: {
value: null,
label: 'no-throttling',
id: 'no-throttling',
},
},
logger,
{ proxyUrl: 'https://www.google.com' }
);
@ -216,8 +224,6 @@ describe('browser fields', () => {
const expected = {
...formattedConfig,
throttling: false,
'throttling.is_enabled': undefined,
'throttling.upload_speed': undefined,
};
expect(formattedConfig).toEqual(expected);

View file

@ -23,10 +23,6 @@ const UI_KEYS_TO_SKIP = [
ConfigKey.JOURNEY_ID,
ConfigKey.PROJECT_ID,
ConfigKey.METADATA,
ConfigKey.UPLOAD_SPEED,
ConfigKey.DOWNLOAD_SPEED,
ConfigKey.LATENCY,
ConfigKey.IS_THROTTLING_ENABLED,
ConfigKey.REVISION,
ConfigKey.CUSTOM_HEARTBEAT_ID,
ConfigKey.FORM_MONITOR_TYPE,
@ -36,19 +32,13 @@ const UI_KEYS_TO_SKIP = [
'secrets',
];
const uiToHeartbeatKeyMap = {
throttling: ConfigKey.THROTTLING_CONFIG,
};
type YamlKeys = keyof typeof uiToHeartbeatKeyMap;
export const formatMonitorConfigFields = (
configKeys: ConfigKey[],
config: Partial<MonitorFields>,
logger: Logger,
params: Record<string, string>
) => {
const formattedMonitor = {} as Record<ConfigKey | YamlKeys, any>;
const formattedMonitor = {} as Record<ConfigKey, any>;
configKeys.forEach((key) => {
if (!UI_KEYS_TO_SKIP.includes(key)) {
@ -85,13 +75,6 @@ export const formatMonitorConfigFields = (
sslKeys.forEach((key) => (formattedMonitor[key] = null));
}
Object.keys(uiToHeartbeatKeyMap).forEach((key) => {
const hbKey = key as YamlKeys;
const configKey = uiToHeartbeatKeyMap[hbKey];
formattedMonitor[hbKey] = formattedMonitor[configKey];
delete formattedMonitor[configKey];
});
return omitBy(formattedMonitor, isNil) as Partial<MonitorFields>;
};

View file

@ -311,11 +311,7 @@ const dummyBrowserConfig: Partial<MonitorFields> & {
'filter_journeys.match': '',
'filter_journeys.tags': [],
ignore_https_errors: false,
'throttling.is_enabled': true,
'throttling.download_speed': '5',
'throttling.upload_speed': '3',
'throttling.latency': '20',
'throttling.config': '5d/3u/20l',
throttling: { value: { download: '5', upload: '3', latency: '20' }, label: 'test', id: 'test' },
id: '75cdd125-5b62-4459-870c-46f59bf37e89',
config_id: '75cdd125-5b62-4459-870c-46f59bf37e89',
fields: { config_id: '75cdd125-5b62-4459-870c-46f59bf37e89', run_once: true },

View file

@ -13,7 +13,11 @@ import {
ProjectMonitor,
PrivateLocation,
} from '../../../../common/runtime_types';
import { DEFAULT_FIELDS } from '../../../../common/constants/monitor_defaults';
import {
DEFAULT_FIELDS,
PROFILE_VALUES_ENUM,
PROFILES_MAP,
} from '../../../../common/constants/monitor_defaults';
import { normalizeProjectMonitors } from '.';
describe('browser normalizers', () => {
@ -143,11 +147,6 @@ describe('browser normalizers', () => {
'service.name': '',
'source.project.content': 'test content 1',
tags: ['tag1', 'tag2'],
'throttling.config': '5d/10u/20l',
'throttling.download_speed': '5',
'throttling.is_enabled': true,
'throttling.latency': '20',
'throttling.upload_speed': '10',
params: '',
type: 'browser',
project_id: projectId,
@ -157,6 +156,15 @@ describe('browser normalizers', () => {
timeout: null,
id: '',
hash: testHash,
throttling: {
id: 'custom',
label: 'Custom',
value: {
download: '5',
latency: '20',
upload: '10',
},
},
},
unsupportedKeys: [],
errors: [],
@ -198,11 +206,6 @@ describe('browser normalizers', () => {
'service.name': '',
'source.project.content': 'test content 2',
tags: ['tag3', 'tag4'],
'throttling.config': '10d/15u/18l',
'throttling.download_speed': '10',
'throttling.is_enabled': true,
'throttling.latency': '18',
'throttling.upload_speed': '15',
type: 'browser',
project_id: projectId,
namespace: 'test_space',
@ -211,6 +214,15 @@ describe('browser normalizers', () => {
timeout: null,
id: '',
hash: testHash,
throttling: {
id: 'custom',
label: 'Custom',
value: {
download: '10',
latency: '18',
upload: '15',
},
},
},
unsupportedKeys: [],
errors: [],
@ -257,11 +269,6 @@ describe('browser normalizers', () => {
'service.name': '',
'source.project.content': 'test content 3',
tags: ['tag3', 'tag4'],
'throttling.config': '5d/3u/20l',
'throttling.download_speed': '5',
'throttling.is_enabled': false,
'throttling.latency': '20',
'throttling.upload_speed': '3',
type: 'browser',
project_id: projectId,
namespace: 'test_space',
@ -270,6 +277,147 @@ describe('browser normalizers', () => {
timeout: null,
id: '',
hash: testHash,
throttling: PROFILES_MAP[PROFILE_VALUES_ENUM.NO_THROTTLING],
},
unsupportedKeys: [],
errors: [],
},
]);
});
it('handles defined throttling values', () => {
const actual = normalizeProjectMonitors({
locations,
privateLocations,
monitors: [
{
...monitors[0],
throttling: {
download: 9,
upload: 0.75,
latency: 170,
},
},
],
projectId,
namespace: 'test-space',
version: '8.5.0',
});
expect(actual).toEqual([
{
normalizedFields: {
...DEFAULT_FIELDS[DataStream.BROWSER],
journey_id: 'test-id-1',
ignore_https_errors: true,
origin: 'project',
locations: [
{
geo: {
lat: 33.333,
lon: 73.333,
},
id: 'us_central',
isServiceManaged: true,
label: 'Test Location',
},
],
name: 'test-name-1',
schedule: {
number: '3',
unit: 'm',
},
screenshots: 'off',
'service.name': '',
'source.project.content': 'test content 1',
tags: ['tag1', 'tag2'],
params: '',
type: 'browser',
project_id: projectId,
namespace: 'test_space',
original_space: 'test-space',
custom_heartbeat_id: 'test-id-1-test-project-id-test-space',
timeout: null,
id: '',
hash: testHash,
throttling: {
id: '4g',
label: '4G',
value: {
download: '9',
latency: '170',
upload: '0.75',
},
},
},
unsupportedKeys: [],
errors: [],
},
]);
});
it('handles custom throttling values', () => {
const actual = normalizeProjectMonitors({
locations,
privateLocations,
monitors: [
{
...monitors[0],
throttling: {
download: 10,
upload: 5,
latency: 30,
},
},
],
projectId,
namespace: 'test-space',
version: '8.5.0',
});
expect(actual).toEqual([
{
normalizedFields: {
...DEFAULT_FIELDS[DataStream.BROWSER],
journey_id: 'test-id-1',
ignore_https_errors: true,
origin: 'project',
locations: [
{
geo: {
lat: 33.333,
lon: 73.333,
},
id: 'us_central',
isServiceManaged: true,
label: 'Test Location',
},
],
name: 'test-name-1',
schedule: {
number: '3',
unit: 'm',
},
screenshots: 'off',
'service.name': '',
'source.project.content': 'test content 1',
tags: ['tag1', 'tag2'],
params: '',
type: 'browser',
project_id: projectId,
namespace: 'test_space',
original_space: 'test-space',
custom_heartbeat_id: 'test-id-1-test-project-id-test-space',
timeout: null,
id: '',
hash: testHash,
throttling: {
id: 'custom',
label: 'Custom',
value: {
download: '10',
latency: '30',
upload: '5',
},
},
},
unsupportedKeys: [],
errors: [],

View file

@ -10,8 +10,16 @@ import {
ConfigKey,
DataStream,
FormMonitorType,
ProjectMonitor,
ThrottlingConfig,
} from '../../../../common/runtime_types';
import { DEFAULT_FIELDS } from '../../../../common/constants/monitor_defaults';
import {
PROFILE_VALUES_ENUM,
DEFAULT_FIELDS,
PROFILES_MAP,
PROFILE_VALUES,
CUSTOM_LABEL,
} from '../../../../common/constants/monitor_defaults';
import {
NormalizedProjectProps,
NormalizerResult,
@ -38,33 +46,15 @@ export const getNormalizeBrowserFields = ({
version,
});
const throttling = normalizeThrottling(monitor.throttling);
const normalizedFields = {
...commonFields,
[ConfigKey.MONITOR_TYPE]: DataStream.BROWSER,
[ConfigKey.FORM_MONITOR_TYPE]: FormMonitorType.MULTISTEP,
[ConfigKey.SOURCE_PROJECT_CONTENT]:
monitor.content || defaultFields[ConfigKey.SOURCE_PROJECT_CONTENT],
[ConfigKey.THROTTLING_CONFIG]:
typeof monitor.throttling !== 'boolean'
? `${monitor.throttling?.download}d/${monitor.throttling?.upload}u/${monitor.throttling?.latency}l`
: defaultFields[ConfigKey.THROTTLING_CONFIG],
[ConfigKey.DOWNLOAD_SPEED]: `${
typeof monitor.throttling !== 'boolean'
? monitor.throttling?.download
: defaultFields[ConfigKey.DOWNLOAD_SPEED]
}`,
[ConfigKey.UPLOAD_SPEED]: `${
typeof monitor.throttling !== 'boolean'
? monitor.throttling?.upload
: defaultFields[ConfigKey.UPLOAD_SPEED]
}`,
[ConfigKey.IS_THROTTLING_ENABLED]:
Boolean(monitor.throttling) ?? defaultFields[ConfigKey.IS_THROTTLING_ENABLED],
[ConfigKey.LATENCY]: `${
typeof monitor.throttling !== 'boolean'
? monitor.throttling?.latency
: defaultFields[ConfigKey.LATENCY]
}`,
[ConfigKey.THROTTLING_CONFIG]: throttling,
[ConfigKey.IGNORE_HTTPS_ERRORS]:
monitor.ignoreHTTPSErrors || defaultFields[ConfigKey.IGNORE_HTTPS_ERRORS],
[ConfigKey.SCREENSHOTS]: monitor.screenshot || defaultFields[ConfigKey.SCREENSHOTS],
@ -89,3 +79,40 @@ export const getNormalizeBrowserFields = ({
errors,
};
};
export const normalizeThrottling = (
monitorThrottling: ProjectMonitor['throttling']
): ThrottlingConfig => {
const defaultFields = DEFAULT_FIELDS[DataStream.BROWSER];
let throttling = defaultFields[ConfigKey.THROTTLING_CONFIG];
if (typeof monitorThrottling === 'boolean' && !monitorThrottling) {
throttling = PROFILES_MAP[PROFILE_VALUES_ENUM.NO_THROTTLING];
}
if (typeof monitorThrottling === 'object') {
const { download, upload, latency } = monitorThrottling;
const matchedProfile = PROFILE_VALUES.find(({ value }) => {
return (
Number(value?.download) === download &&
Number(value?.upload) === upload &&
Number(value?.latency) === latency
);
});
if (matchedProfile) {
return matchedProfile;
} else {
return {
id: PROFILE_VALUES_ENUM.CUSTOM,
label: CUSTOM_LABEL,
value: {
download: String(download),
upload: String(upload),
latency: String(latency),
},
};
}
}
return throttling;
};

View file

@ -485,11 +485,6 @@ const payloadData = [
'ssl.verification_mode': 'full',
synthetics_args: [],
tags: [],
'throttling.config': '5d/3u/20l',
'throttling.download_speed': '5',
'throttling.is_enabled': true,
'throttling.latency': '20',
'throttling.upload_speed': '3',
timeout: null,
type: 'browser',
'url.port': null,
@ -540,11 +535,6 @@ const payloadData = [
'ssl.verification_mode': 'full',
synthetics_args: [],
tags: [],
'throttling.config': '5d/3u/20l',
'throttling.download_speed': '5',
'throttling.is_enabled': true,
'throttling.latency': '20',
'throttling.upload_speed': '3',
timeout: null,
type: 'browser',
'url.port': null,

View file

@ -554,11 +554,6 @@ const payloadData = [
'ssl.verification_mode': 'full',
synthetics_args: [],
tags: [],
'throttling.config': '5d/3u/20l',
'throttling.download_speed': '5',
'throttling.is_enabled': true,
'throttling.latency': '20',
'throttling.upload_speed': '3',
timeout: null,
type: 'browser',
'url.port': null,
@ -609,11 +604,6 @@ const payloadData = [
'ssl.verification_mode': 'full',
synthetics_args: [],
tags: [],
'throttling.config': '5d/3u/20l',
'throttling.download_speed': '5',
'throttling.is_enabled': true,
'throttling.latency': '20',
'throttling.upload_speed': '3',
timeout: null,
type: 'browser',
'url.port': null,

View file

@ -34909,7 +34909,6 @@
"xpack.synthetics.monitorConfig.textAssertion.label": "Assertion de texte",
"xpack.synthetics.monitorConfig.throttling.helpText": "Simulez la régulation du réseau (téléchargement, chargement, latence). D'autres options seront ajoutées dans une prochaine version.",
"xpack.synthetics.monitorConfig.throttling.label": "Profil de connexion",
"xpack.synthetics.monitorConfig.throttling.options.default": "Par défaut",
"xpack.synthetics.monitorConfig.timeout.formatError": "Le délai d'expiration n'est pas valide.",
"xpack.synthetics.monitorConfig.timeout.greaterThan0Error": "Le délai d'expiration doit être supérieur ou égal à 0.",
"xpack.synthetics.monitorConfig.timeout.helpText": "Temps total autorisé pour tester la connexion et l'échange de données.",

View file

@ -34888,7 +34888,6 @@
"xpack.synthetics.monitorConfig.textAssertion.label": "テキストアサーション",
"xpack.synthetics.monitorConfig.throttling.helpText": "ネットワークスロットリングをシミュレートします(ダウンロード、アップロード、レイテンシ)。今後のバージョンではその他のオプションが追加されます。",
"xpack.synthetics.monitorConfig.throttling.label": "接続プロファイル",
"xpack.synthetics.monitorConfig.throttling.options.default": "デフォルト",
"xpack.synthetics.monitorConfig.timeout.formatError": "タイムアウトが無効です。",
"xpack.synthetics.monitorConfig.timeout.greaterThan0Error": "タイムアウトは0以上でなければなりません。",
"xpack.synthetics.monitorConfig.timeout.helpText": "接続のテストとデータの交換に許可された合計時間。",

View file

@ -34904,7 +34904,6 @@
"xpack.synthetics.monitorConfig.textAssertion.label": "文本断言",
"xpack.synthetics.monitorConfig.throttling.helpText": "模拟网络限制(下载、上传、延迟)。将在未来版本中添加更多选项。",
"xpack.synthetics.monitorConfig.throttling.label": "连接配置文件",
"xpack.synthetics.monitorConfig.throttling.options.default": "默认",
"xpack.synthetics.monitorConfig.timeout.formatError": "超时无效。",
"xpack.synthetics.monitorConfig.timeout.greaterThan0Error": "超时必须大于或等于 0。",
"xpack.synthetics.monitorConfig.timeout.helpText": "允许用于测试连接并交换数据的总时间。",

View file

@ -12,6 +12,10 @@ import { formatKibanaNamespace } from '@kbn/synthetics-plugin/common/formatters'
import { syntheticsMonitorType } from '@kbn/synthetics-plugin/server/legacy_uptime/lib/saved_objects/synthetics_monitor';
import { REQUEST_TOO_LARGE } from '@kbn/synthetics-plugin/server/routes/monitor_cruds/add_monitor_project';
import { PackagePolicy } from '@kbn/fleet-plugin/common';
import {
PROFILE_VALUES_ENUM,
PROFILES_MAP,
} from '@kbn/synthetics-plugin/common/constants/monitor_defaults';
import { FtrProviderContext } from '../../ftr_provider_context';
import { getFixtureJson } from '../uptime/rest/helper/get_fixture_json';
import { PrivateLocationTestService } from './services/private_location_test_service';
@ -190,11 +194,7 @@ export default function ({ getService }: FtrProviderContext) {
'service.name': '',
synthetics_args: [],
tags: [],
'throttling.config': '5d/3u/20l',
'throttling.download_speed': '5',
'throttling.is_enabled': true,
'throttling.latency': '20',
'throttling.upload_speed': '3',
throttling: PROFILES_MAP[PROFILE_VALUES_ENUM.DEFAULT],
'ssl.certificate': '',
'ssl.certificate_authorities': '',
'ssl.supported_protocols': ['TLSv1.1', 'TLSv1.2', 'TLSv1.3'],
@ -253,7 +253,11 @@ export default function ({ getService }: FtrProviderContext) {
.set('kbn-xsrf', 'true')
.expect(200);
expect(decryptedCreatedMonitor.body.attributes['throttling.is_enabled']).to.eql(false);
expect(decryptedCreatedMonitor.body.attributes.throttling).to.eql({
value: null,
id: 'no-throttling',
label: 'No throttling',
});
}
} finally {
await Promise.all([

View file

@ -16,6 +16,10 @@ import { API_URLS } from '@kbn/synthetics-plugin/common/constants';
import { formatKibanaNamespace } from '@kbn/synthetics-plugin/common/formatters';
import { syntheticsMonitorType } from '@kbn/synthetics-plugin/server/legacy_uptime/lib/saved_objects/synthetics_monitor';
import { PackagePolicy } from '@kbn/fleet-plugin/common';
import {
PROFILE_VALUES_ENUM,
PROFILES_MAP,
} from '@kbn/synthetics-plugin/common/constants/monitor_defaults';
import { FtrProviderContext } from '../../ftr_provider_context';
import { getFixtureJson } from '../uptime/rest/helper/get_fixture_json';
import { PrivateLocationTestService } from './services/private_location_test_service';
@ -175,11 +179,7 @@ export default function ({ getService }: FtrProviderContext) {
'service.name': '',
synthetics_args: [],
tags: [],
'throttling.config': '5d/3u/20l',
'throttling.download_speed': '5',
'throttling.is_enabled': true,
'throttling.latency': '20',
'throttling.upload_speed': '3',
throttling: PROFILES_MAP[PROFILE_VALUES_ENUM.DEFAULT],
'ssl.certificate': '',
'ssl.certificate_authorities': '',
'ssl.supported_protocols': ['TLSv1.1', 'TLSv1.2', 'TLSv1.3'],

View file

@ -31,11 +31,15 @@
"filter_journeys.match": "",
"filter_journeys.tags": [],
"ignore_https_errors": false,
"throttling.is_enabled": true,
"throttling.download_speed": "5",
"throttling.upload_speed": "3",
"throttling.latency": "20",
"throttling.config": "5d/3u/20l",
"throttling": {
"value": {
"download": "5",
"latency": "20",
"upload": "3"
},
"id": "default",
"label": "Default"
},
"locations": [],
"name": "Test HTTP Monitor 03",
"namespace": "testnamespace",