mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Synthetics] Add project monitor api (#131270)
Add project monitor api for the Synthetics agent and Uptime Monitor Management Co-authored-by: Shahzad <shahzad31comp@gmail.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: shahzad31 <shahzad.muhammad@elastic.co>
This commit is contained in:
parent
577752ae7c
commit
16e12a20bb
60 changed files with 2365 additions and 459 deletions
|
@ -18,6 +18,7 @@ import {
|
|||
ResponseBodyIndexPolicy,
|
||||
ScheduleUnit,
|
||||
ScreenshotOption,
|
||||
SourceType,
|
||||
TCPAdvancedFields,
|
||||
TCPSimpleFields,
|
||||
TLSFields,
|
||||
|
@ -41,6 +42,7 @@ export const DEFAULT_COMMON_FIELDS: CommonFields = {
|
|||
[ConfigKey.NAME]: '',
|
||||
[ConfigKey.LOCATIONS]: [],
|
||||
[ConfigKey.NAMESPACE]: DEFAULT_NAMESPACE_STRING,
|
||||
[ConfigKey.MONITOR_SOURCE_TYPE]: SourceType.UI,
|
||||
};
|
||||
|
||||
export const DEFAULT_BROWSER_ADVANCED_FIELDS: BrowserAdvancedFields = {
|
||||
|
@ -58,10 +60,9 @@ export const DEFAULT_BROWSER_ADVANCED_FIELDS: BrowserAdvancedFields = {
|
|||
|
||||
export const DEFAULT_BROWSER_SIMPLE_FIELDS: BrowserSimpleFields = {
|
||||
...DEFAULT_COMMON_FIELDS,
|
||||
[ConfigKey.SCHEDULE]: {
|
||||
unit: ScheduleUnit.MINUTES,
|
||||
number: '10',
|
||||
},
|
||||
[ConfigKey.JOURNEY_ID]: '',
|
||||
[ConfigKey.PROJECT_ID]: '',
|
||||
[ConfigKey.PLAYWRIGHT_OPTIONS]: '',
|
||||
[ConfigKey.METADATA]: {
|
||||
script_source: {
|
||||
is_generated_script: false,
|
||||
|
@ -70,21 +71,26 @@ export const DEFAULT_BROWSER_SIMPLE_FIELDS: BrowserSimpleFields = {
|
|||
is_zip_url_tls_enabled: false,
|
||||
},
|
||||
[ConfigKey.MONITOR_TYPE]: DataStream.BROWSER,
|
||||
[ConfigKey.PARAMS]: '',
|
||||
[ConfigKey.PORT]: undefined,
|
||||
[ConfigKey.SCHEDULE]: {
|
||||
unit: ScheduleUnit.MINUTES,
|
||||
number: '10',
|
||||
},
|
||||
[ConfigKey.SOURCE_INLINE]: '',
|
||||
[ConfigKey.SOURCE_PROJECT_CONTENT]: '',
|
||||
[ConfigKey.SOURCE_ZIP_URL]: '',
|
||||
[ConfigKey.SOURCE_ZIP_USERNAME]: '',
|
||||
[ConfigKey.SOURCE_ZIP_PASSWORD]: '',
|
||||
[ConfigKey.SOURCE_ZIP_FOLDER]: '',
|
||||
[ConfigKey.SOURCE_ZIP_PROXY_URL]: '',
|
||||
[ConfigKey.PARAMS]: '',
|
||||
[ConfigKey.ZIP_URL_TLS_CERTIFICATE_AUTHORITIES]: undefined,
|
||||
[ConfigKey.ZIP_URL_TLS_CERTIFICATE]: undefined,
|
||||
[ConfigKey.ZIP_URL_TLS_KEY]: undefined,
|
||||
[ConfigKey.ZIP_URL_TLS_KEY_PASSPHRASE]: undefined,
|
||||
[ConfigKey.ZIP_URL_TLS_VERIFICATION_MODE]: undefined,
|
||||
[ConfigKey.ZIP_URL_TLS_VERSION]: undefined,
|
||||
[ConfigKey.URLS]: undefined,
|
||||
[ConfigKey.PORT]: undefined,
|
||||
[ConfigKey.URLS]: '',
|
||||
};
|
||||
|
||||
export const DEFAULT_HTTP_SIMPLE_FIELDS: HTTPSimpleFields = {
|
||||
|
|
|
@ -8,11 +8,14 @@
|
|||
// values must match keys in the integration package
|
||||
export enum ConfigKey {
|
||||
APM_SERVICE_NAME = 'service.name',
|
||||
CUSTOM_HEARTBEAT_ID = 'custom_heartbeat_id',
|
||||
ENABLED = 'enabled',
|
||||
HOSTS = 'hosts',
|
||||
IGNORE_HTTPS_ERRORS = 'ignore_https_errors',
|
||||
MONITOR_SOURCE_TYPE = 'monitor.origin',
|
||||
JOURNEY_FILTERS_MATCH = 'filter_journeys.match',
|
||||
JOURNEY_FILTERS_TAGS = 'filter_journeys.tags',
|
||||
JOURNEY_ID = 'journey_id',
|
||||
MAX_REDIRECTS = 'max_redirects',
|
||||
METADATA = '__ui',
|
||||
MONITOR_TYPE = 'type',
|
||||
|
@ -21,6 +24,9 @@ export enum ConfigKey {
|
|||
LOCATIONS = 'locations',
|
||||
PARAMS = 'params',
|
||||
PASSWORD = 'password',
|
||||
PLAYWRIGHT_OPTIONS = 'playwright_options',
|
||||
ORIGINAL_SPACE = 'original_space', // the original space the montior was saved in. Used by push monitors to ensure uniqueness of monitor id sent to heartbeat and prevent data collisions
|
||||
PORT = 'url.port',
|
||||
PROXY_URL = 'proxy_url',
|
||||
PROXY_USE_LOCAL_RESOLVER = 'proxy_use_local_resolver',
|
||||
RESPONSE_BODY_CHECK_NEGATIVE = 'check.response.body.negative',
|
||||
|
@ -37,12 +43,14 @@ export enum ConfigKey {
|
|||
REVISION = 'revision',
|
||||
SCHEDULE = 'schedule',
|
||||
SCREENSHOTS = 'screenshots',
|
||||
SOURCE_PROJECT_CONTENT = 'source.project.content',
|
||||
SOURCE_INLINE = 'source.inline.script',
|
||||
SOURCE_ZIP_URL = 'source.zip_url.url',
|
||||
SOURCE_ZIP_USERNAME = 'source.zip_url.username',
|
||||
SOURCE_ZIP_PASSWORD = 'source.zip_url.password',
|
||||
SOURCE_ZIP_FOLDER = 'source.zip_url.folder',
|
||||
SOURCE_ZIP_PROXY_URL = 'source.zip_url.proxy_url',
|
||||
PROJECT_ID = 'project_id',
|
||||
SYNTHETICS_ARGS = 'synthetics_args',
|
||||
TLS_CERTIFICATE_AUTHORITIES = 'ssl.certificate_authorities',
|
||||
TLS_CERTIFICATE = 'ssl.certificate',
|
||||
|
@ -58,7 +66,6 @@ export enum ConfigKey {
|
|||
UPLOAD_SPEED = 'throttling.upload_speed',
|
||||
LATENCY = 'throttling.latency',
|
||||
URLS = 'urls',
|
||||
PORT = 'url.port',
|
||||
USERNAME = 'username',
|
||||
WAIT = 'wait',
|
||||
ZIP_URL_TLS_CERTIFICATE_AUTHORITIES = 'source.zip_url.ssl.certificate_authorities',
|
||||
|
@ -80,6 +87,7 @@ export const secretKeys = [
|
|||
ConfigKey.RESPONSE_HEADERS_CHECK,
|
||||
ConfigKey.RESPONSE_RECEIVE_CHECK,
|
||||
ConfigKey.SOURCE_INLINE,
|
||||
ConfigKey.SOURCE_PROJECT_CONTENT,
|
||||
ConfigKey.SOURCE_ZIP_USERNAME,
|
||||
ConfigKey.SOURCE_ZIP_PASSWORD,
|
||||
ConfigKey.SYNTHETICS_ARGS,
|
||||
|
|
|
@ -44,4 +44,7 @@ export enum API_URLS {
|
|||
RUN_ONCE_MONITOR = '/internal/uptime/service/monitors/run_once',
|
||||
TRIGGER_MONITOR = '/internal/uptime/service/monitors/trigger',
|
||||
SERVICE_ALLOWED = '/internal/uptime/service/allowed',
|
||||
|
||||
// Project monitor public endpoint
|
||||
SYNTHETICS_MONITORS_PROJECT = '/api/synthetics/service/project/monitors',
|
||||
}
|
||||
|
|
|
@ -10,4 +10,5 @@ export * from './config_key';
|
|||
export * from './monitor_configs';
|
||||
export * from './monitor_meta_data';
|
||||
export * from './monitor_types';
|
||||
export * from './monitor_types_project';
|
||||
export * from './locations';
|
||||
|
|
|
@ -119,3 +119,10 @@ export enum ThrottlingSuffix {
|
|||
|
||||
export const ThrottlingSuffixCodec = tEnum<ThrottlingSuffix>('ThrottlingSuffix', ThrottlingSuffix);
|
||||
export type ThrottlingSuffixType = t.TypeOf<typeof ThrottlingSuffixCodec>;
|
||||
|
||||
export enum SourceType {
|
||||
UI = 'ui',
|
||||
PROJECT = 'project',
|
||||
}
|
||||
|
||||
export const SourceTypeCodec = tEnum<SourceType>('SourceType', SourceType);
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
ModeCodec,
|
||||
ResponseBodyIndexPolicyCodec,
|
||||
ScheduleUnitCodec,
|
||||
SourceTypeCodec,
|
||||
TLSVersionCodec,
|
||||
VerificationModeCodec,
|
||||
} from './monitor_configs';
|
||||
|
@ -76,6 +77,7 @@ export const CommonFieldsCodec = t.intersection([
|
|||
t.partial({
|
||||
[ConfigKey.TIMEOUT]: t.union([t.string, t.null]),
|
||||
[ConfigKey.REVISION]: t.number,
|
||||
[ConfigKey.MONITOR_SOURCE_TYPE]: SourceTypeCodec,
|
||||
}),
|
||||
]);
|
||||
|
||||
|
@ -200,12 +202,21 @@ export const ThrottlingConfigKeyCodec = t.union([
|
|||
export type ThrottlingConfigKey = t.TypeOf<typeof ThrottlingConfigKeyCodec>;
|
||||
|
||||
export const EncryptedBrowserSimpleFieldsCodec = t.intersection([
|
||||
t.interface({
|
||||
[ConfigKey.METADATA]: MetadataCodec,
|
||||
[ConfigKey.SOURCE_ZIP_URL]: t.string,
|
||||
[ConfigKey.SOURCE_ZIP_FOLDER]: t.string,
|
||||
[ConfigKey.SOURCE_ZIP_PROXY_URL]: t.string,
|
||||
}),
|
||||
t.intersection([
|
||||
t.interface({
|
||||
[ConfigKey.METADATA]: MetadataCodec,
|
||||
[ConfigKey.SOURCE_ZIP_URL]: t.string,
|
||||
[ConfigKey.SOURCE_ZIP_FOLDER]: t.string,
|
||||
[ConfigKey.SOURCE_ZIP_PROXY_URL]: t.string,
|
||||
}),
|
||||
t.partial({
|
||||
[ConfigKey.PLAYWRIGHT_OPTIONS]: t.string,
|
||||
[ConfigKey.JOURNEY_ID]: t.string,
|
||||
[ConfigKey.PROJECT_ID]: t.string,
|
||||
[ConfigKey.ORIGINAL_SPACE]: t.string,
|
||||
[ConfigKey.CUSTOM_HEARTBEAT_ID]: t.string,
|
||||
}),
|
||||
]),
|
||||
ZipUrlTLSFieldsCodec,
|
||||
ZipUrlTLSSensitiveFieldsCodec,
|
||||
CommonFieldsCodec,
|
||||
|
@ -214,6 +225,7 @@ export const EncryptedBrowserSimpleFieldsCodec = t.intersection([
|
|||
export const BrowserSensitiveSimpleFieldsCodec = t.intersection([
|
||||
t.interface({
|
||||
[ConfigKey.SOURCE_INLINE]: t.string,
|
||||
[ConfigKey.SOURCE_PROJECT_CONTENT]: t.string,
|
||||
[ConfigKey.SOURCE_ZIP_USERNAME]: t.string,
|
||||
[ConfigKey.SOURCE_ZIP_PASSWORD]: t.string,
|
||||
[ConfigKey.PARAMS]: t.string,
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { ScreenshotOptionCodec } from './monitor_configs';
|
||||
|
||||
export const ProjectMonitorThrottlingConfigCodec = t.interface({
|
||||
download: t.number,
|
||||
upload: t.number,
|
||||
latency: t.number,
|
||||
});
|
||||
|
||||
export const ProjectBrowserMonitorCodec = t.intersection([
|
||||
t.interface({
|
||||
id: t.string,
|
||||
name: t.string,
|
||||
schedule: t.number,
|
||||
content: t.string,
|
||||
locations: t.array(t.string),
|
||||
}),
|
||||
t.partial({
|
||||
throttling: ProjectMonitorThrottlingConfigCodec,
|
||||
screenshot: ScreenshotOptionCodec,
|
||||
tags: t.array(t.string),
|
||||
ignoreHTTPSErrors: t.boolean,
|
||||
apmServiceName: t.string,
|
||||
playwrightOptions: t.record(t.string, t.unknown),
|
||||
filter: t.interface({
|
||||
match: t.string,
|
||||
}),
|
||||
params: t.record(t.string, t.unknown),
|
||||
enabled: t.boolean,
|
||||
}),
|
||||
]);
|
||||
|
||||
export const ProjectMonitorsRequestCodec = t.interface({
|
||||
project: t.string,
|
||||
keep_stale: t.boolean,
|
||||
monitors: t.array(ProjectBrowserMonitorCodec),
|
||||
});
|
||||
|
||||
export type ProjectMonitorThrottlingConfig = t.TypeOf<typeof ProjectMonitorThrottlingConfigCodec>;
|
||||
|
||||
export type ProjectBrowserMonitor = t.TypeOf<typeof ProjectBrowserMonitorCodec>;
|
||||
|
||||
export type ProjectMonitorsRequest = t.TypeOf<typeof ProjectMonitorsRequestCodec>;
|
|
@ -20,7 +20,8 @@
|
|||
"taskManager",
|
||||
"triggersActionsUi",
|
||||
"usageCollection",
|
||||
"unifiedSearch"
|
||||
"unifiedSearch",
|
||||
"spaces"
|
||||
],
|
||||
"server": true,
|
||||
"ui": true,
|
||||
|
|
|
@ -45,6 +45,7 @@ export const browserFormatters: BrowserFormatMap = {
|
|||
[ConfigKey.SOURCE_ZIP_PASSWORD]: null,
|
||||
[ConfigKey.SOURCE_ZIP_FOLDER]: null,
|
||||
[ConfigKey.SOURCE_ZIP_PROXY_URL]: null,
|
||||
[ConfigKey.SOURCE_PROJECT_CONTENT]: null,
|
||||
[ConfigKey.SOURCE_INLINE]: (fields) => stringToJsonFormatter(fields[ConfigKey.SOURCE_INLINE]),
|
||||
[ConfigKey.PARAMS]: null,
|
||||
[ConfigKey.SCREENSHOTS]: null,
|
||||
|
@ -71,5 +72,10 @@ export const browserFormatters: BrowserFormatMap = {
|
|||
arrayToJsonFormatter(fields[ConfigKey.JOURNEY_FILTERS_TAGS]),
|
||||
[ConfigKey.THROTTLING_CONFIG]: throttlingFormatter,
|
||||
[ConfigKey.IGNORE_HTTPS_ERRORS]: null,
|
||||
[ConfigKey.JOURNEY_ID]: null,
|
||||
[ConfigKey.PROJECT_ID]: null,
|
||||
[ConfigKey.PLAYWRIGHT_OPTIONS]: null,
|
||||
[ConfigKey.CUSTOM_HEARTBEAT_ID]: null,
|
||||
[ConfigKey.ORIGINAL_SPACE]: null,
|
||||
...commonFormatters,
|
||||
};
|
||||
|
|
|
@ -73,6 +73,7 @@ export const browserNormalizers: BrowserNormalizerMap = {
|
|||
[ConfigKey.SOURCE_ZIP_USERNAME]: getBrowserNormalizer(ConfigKey.SOURCE_ZIP_USERNAME),
|
||||
[ConfigKey.SOURCE_ZIP_PASSWORD]: getBrowserNormalizer(ConfigKey.SOURCE_ZIP_PASSWORD),
|
||||
[ConfigKey.SOURCE_ZIP_FOLDER]: getBrowserNormalizer(ConfigKey.SOURCE_ZIP_FOLDER),
|
||||
[ConfigKey.SOURCE_PROJECT_CONTENT]: getBrowserNormalizer(ConfigKey.SOURCE_PROJECT_CONTENT),
|
||||
[ConfigKey.SOURCE_INLINE]: getBrowserJsonToJavascriptNormalizer(ConfigKey.SOURCE_INLINE),
|
||||
[ConfigKey.SOURCE_ZIP_PROXY_URL]: getBrowserNormalizer(ConfigKey.SOURCE_ZIP_PROXY_URL),
|
||||
[ConfigKey.PARAMS]: getBrowserNormalizer(ConfigKey.PARAMS),
|
||||
|
@ -106,5 +107,10 @@ export const browserNormalizers: BrowserNormalizerMap = {
|
|||
ConfigKey.JOURNEY_FILTERS_TAGS
|
||||
),
|
||||
[ConfigKey.IGNORE_HTTPS_ERRORS]: getBrowserNormalizer(ConfigKey.IGNORE_HTTPS_ERRORS),
|
||||
[ConfigKey.JOURNEY_ID]: getBrowserNormalizer(ConfigKey.JOURNEY_ID),
|
||||
[ConfigKey.PROJECT_ID]: getBrowserNormalizer(ConfigKey.PROJECT_ID),
|
||||
[ConfigKey.PLAYWRIGHT_OPTIONS]: getBrowserNormalizer(ConfigKey.PLAYWRIGHT_OPTIONS),
|
||||
[ConfigKey.CUSTOM_HEARTBEAT_ID]: getBrowserNormalizer(ConfigKey.CUSTOM_HEARTBEAT_ID),
|
||||
[ConfigKey.ORIGINAL_SPACE]: getBrowserNormalizer(ConfigKey.ORIGINAL_SPACE),
|
||||
...commonNormalizers,
|
||||
};
|
||||
|
|
|
@ -22,9 +22,10 @@ import { useBrowserAdvancedFieldsContext, usePolicyConfigContext } from '../cont
|
|||
import { Validation, ConfigKey, BandwidthLimitKey } from '../types';
|
||||
|
||||
interface Props {
|
||||
validate: Validation;
|
||||
validate?: Validation;
|
||||
minColumnWidth?: string;
|
||||
onFieldBlur?: (field: ConfigKey) => void;
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
type ThrottlingConfigs =
|
||||
|
@ -89,193 +90,204 @@ export const ThrottlingExceededMessage = ({
|
|||
);
|
||||
};
|
||||
|
||||
export const ThrottlingFields = memo<Props>(({ validate, minColumnWidth, onFieldBlur }) => {
|
||||
const { fields, setFields } = useBrowserAdvancedFieldsContext();
|
||||
const { runsOnService, throttling } = usePolicyConfigContext();
|
||||
export const ThrottlingFields = memo<Props>(
|
||||
({ validate, minColumnWidth, onFieldBlur, readOnly = false }) => {
|
||||
const { fields, setFields } = useBrowserAdvancedFieldsContext();
|
||||
const { runsOnService, throttling } = usePolicyConfigContext();
|
||||
|
||||
const maxDownload = throttling[BandwidthLimitKey.DOWNLOAD];
|
||||
const maxUpload = throttling[BandwidthLimitKey.UPLOAD];
|
||||
const maxDownload = throttling[BandwidthLimitKey.DOWNLOAD];
|
||||
const maxUpload = throttling[BandwidthLimitKey.UPLOAD];
|
||||
|
||||
const handleInputChange = useCallback(
|
||||
({ value, configKey }: { value: unknown; configKey: ThrottlingConfigs }) => {
|
||||
setFields((prevFields) => ({ ...prevFields, [configKey]: value }));
|
||||
},
|
||||
[setFields]
|
||||
);
|
||||
const handleInputChange = useCallback(
|
||||
({ value, configKey }: { value: unknown; configKey: ThrottlingConfigs }) => {
|
||||
setFields((prevFields) => ({ ...prevFields, [configKey]: value }));
|
||||
},
|
||||
[setFields]
|
||||
);
|
||||
|
||||
const exceedsDownloadLimits =
|
||||
runsOnService && parseFloat(fields[ConfigKey.DOWNLOAD_SPEED]) > maxDownload;
|
||||
const exceedsUploadLimits =
|
||||
runsOnService && parseFloat(fields[ConfigKey.UPLOAD_SPEED]) > maxUpload;
|
||||
const isThrottlingEnabled = fields[ConfigKey.IS_THROTTLING_ENABLED];
|
||||
const exceedsDownloadLimits =
|
||||
runsOnService && parseFloat(fields[ConfigKey.DOWNLOAD_SPEED]) > maxDownload;
|
||||
const exceedsUploadLimits =
|
||||
runsOnService && parseFloat(fields[ConfigKey.UPLOAD_SPEED]) > maxUpload;
|
||||
const isThrottlingEnabled = fields[ConfigKey.IS_THROTTLING_ENABLED];
|
||||
|
||||
const throttlingInputs = isThrottlingEnabled ? (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.download.label"
|
||||
defaultMessage="Download Speed"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
isInvalid={!!validate[ConfigKey.DOWNLOAD_SPEED]?.(fields) || exceedsDownloadLimits}
|
||||
error={
|
||||
exceedsDownloadLimits ? (
|
||||
<ThrottlingExceededMessage throttlingField="download" limit={maxDownload} />
|
||||
) : (
|
||||
const throttlingInputs = isThrottlingEnabled ? (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.download.error"
|
||||
defaultMessage="Download speed must be greater than zero."
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.download.label"
|
||||
defaultMessage="Download Speed"
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
<EuiFieldNumber
|
||||
min={0}
|
||||
step={0.001}
|
||||
value={fields[ConfigKey.DOWNLOAD_SPEED]}
|
||||
onChange={(event) => {
|
||||
handleInputChange({
|
||||
value: event.target.value,
|
||||
configKey: ConfigKey.DOWNLOAD_SPEED,
|
||||
});
|
||||
}}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.DOWNLOAD_SPEED)}
|
||||
data-test-subj="syntheticsBrowserDownloadSpeed"
|
||||
append={
|
||||
<EuiText size="xs">
|
||||
<strong>Mbps</strong>
|
||||
</EuiText>
|
||||
}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={
|
||||
labelAppend={<OptionalLabel />}
|
||||
isInvalid={
|
||||
(validate ? !!validate[ConfigKey.DOWNLOAD_SPEED]?.(fields) : false) ||
|
||||
exceedsDownloadLimits
|
||||
}
|
||||
error={
|
||||
exceedsDownloadLimits ? (
|
||||
<ThrottlingExceededMessage throttlingField="download" limit={maxDownload} />
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.download.error"
|
||||
defaultMessage="Download speed must be greater than zero."
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
<EuiFieldNumber
|
||||
min={0}
|
||||
step={0.001}
|
||||
value={fields[ConfigKey.DOWNLOAD_SPEED]}
|
||||
onChange={(event) => {
|
||||
handleInputChange({
|
||||
value: event.target.value,
|
||||
configKey: ConfigKey.DOWNLOAD_SPEED,
|
||||
});
|
||||
}}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.DOWNLOAD_SPEED)}
|
||||
data-test-subj="syntheticsBrowserDownloadSpeed"
|
||||
append={
|
||||
<EuiText size="xs">
|
||||
<strong>Mbps</strong>
|
||||
</EuiText>
|
||||
}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.upload.label"
|
||||
defaultMessage="Upload Speed"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
isInvalid={
|
||||
(validate ? !!validate[ConfigKey.UPLOAD_SPEED]?.(fields) : false) || exceedsUploadLimits
|
||||
}
|
||||
error={
|
||||
exceedsUploadLimits ? (
|
||||
<ThrottlingExceededMessage throttlingField="upload" limit={maxUpload} />
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.upload.error"
|
||||
defaultMessage="Upload speed must be greater than zero."
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
<EuiFieldNumber
|
||||
min={0}
|
||||
step={0.001}
|
||||
value={fields[ConfigKey.UPLOAD_SPEED]}
|
||||
onChange={(event) =>
|
||||
handleInputChange({
|
||||
value: event.target.value,
|
||||
configKey: ConfigKey.UPLOAD_SPEED,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.UPLOAD_SPEED)}
|
||||
data-test-subj="syntheticsBrowserUploadSpeed"
|
||||
append={
|
||||
<EuiText size="xs">
|
||||
<strong>Mbps</strong>
|
||||
</EuiText>
|
||||
}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.latency.label"
|
||||
defaultMessage="Latency"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
isInvalid={validate ? !!validate[ConfigKey.LATENCY]?.(fields) : false}
|
||||
error={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.latency.error"
|
||||
defaultMessage="Latency must not be negative."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFieldNumber
|
||||
min={0}
|
||||
value={fields[ConfigKey.LATENCY]}
|
||||
onChange={(event) =>
|
||||
handleInputChange({
|
||||
value: event.target.value,
|
||||
configKey: ConfigKey.LATENCY,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.LATENCY)}
|
||||
data-test-subj="syntheticsBrowserLatency"
|
||||
append={
|
||||
<EuiText size="xs">
|
||||
<strong>ms</strong>
|
||||
</EuiText>
|
||||
}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
<ThrottlingDisabledCallout />
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<DescribedFormGroupWithWrap
|
||||
minColumnWidth={minColumnWidth}
|
||||
title={
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.title"
|
||||
defaultMessage="Throttling options"
|
||||
/>
|
||||
</h4>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.upload.label"
|
||||
defaultMessage="Upload Speed"
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.description"
|
||||
defaultMessage="Control the monitor's download and upload speeds, and its latency to simulate your application's behaviour on slower or laggier networks."
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
isInvalid={!!validate[ConfigKey.UPLOAD_SPEED]?.(fields) || exceedsUploadLimits}
|
||||
error={
|
||||
exceedsUploadLimits ? (
|
||||
<ThrottlingExceededMessage throttlingField="upload" limit={maxUpload} />
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.upload.error"
|
||||
defaultMessage="Upload speed must be greater than zero."
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
<EuiFieldNumber
|
||||
min={0}
|
||||
step={0.001}
|
||||
value={fields[ConfigKey.UPLOAD_SPEED]}
|
||||
<EuiSwitch
|
||||
id={'uptimeFleetIsThrottlingEnabled'}
|
||||
aria-label="enable throttling configuration"
|
||||
data-test-subj="syntheticsBrowserIsThrottlingEnabled"
|
||||
checked={fields[ConfigKey.IS_THROTTLING_ENABLED]}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.switch.description"
|
||||
defaultMessage="Enable throttling"
|
||||
/>
|
||||
}
|
||||
onChange={(event) =>
|
||||
handleInputChange({
|
||||
value: event.target.value,
|
||||
configKey: ConfigKey.UPLOAD_SPEED,
|
||||
value: event.target.checked,
|
||||
configKey: ConfigKey.IS_THROTTLING_ENABLED,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.UPLOAD_SPEED)}
|
||||
data-test-subj="syntheticsBrowserUploadSpeed"
|
||||
append={
|
||||
<EuiText size="xs">
|
||||
<strong>Mbps</strong>
|
||||
</EuiText>
|
||||
}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.IS_THROTTLING_ENABLED)}
|
||||
disabled={readOnly}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.latency.label"
|
||||
defaultMessage="Latency"
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
isInvalid={!!validate[ConfigKey.LATENCY]?.(fields)}
|
||||
error={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.latency.error"
|
||||
defaultMessage="Latency must not be negative."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFieldNumber
|
||||
min={0}
|
||||
value={fields[ConfigKey.LATENCY]}
|
||||
onChange={(event) =>
|
||||
handleInputChange({
|
||||
value: event.target.value,
|
||||
configKey: ConfigKey.LATENCY,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.LATENCY)}
|
||||
data-test-subj="syntheticsBrowserLatency"
|
||||
append={
|
||||
<EuiText size="xs">
|
||||
<strong>ms</strong>
|
||||
</EuiText>
|
||||
}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
<ThrottlingDisabledCallout />
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<DescribedFormGroupWithWrap
|
||||
minColumnWidth={minColumnWidth}
|
||||
title={
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.title"
|
||||
defaultMessage="Throttling options"
|
||||
/>
|
||||
</h4>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.description"
|
||||
defaultMessage="Control the monitor's download and upload speeds, and its latency to simulate your application's behaviour on slower or laggier networks."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiSwitch
|
||||
id={'uptimeFleetIsThrottlingEnabled'}
|
||||
aria-label="enable throttling configuration"
|
||||
data-test-subj="syntheticsBrowserIsThrottlingEnabled"
|
||||
checked={fields[ConfigKey.IS_THROTTLING_ENABLED]}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.createPackagePolicy.stepConfigure.browserAdvancedSettings.throttling.switch.description"
|
||||
defaultMessage="Enable throttling"
|
||||
/>
|
||||
}
|
||||
onChange={(event) =>
|
||||
handleInputChange({
|
||||
value: event.target.checked,
|
||||
configKey: ConfigKey.IS_THROTTLING_ENABLED,
|
||||
})
|
||||
}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.IS_THROTTLING_ENABLED)}
|
||||
/>
|
||||
{isThrottlingEnabled && (exceedsDownloadLimits || exceedsUploadLimits) ? (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
<ThrottlingExceededCallout />
|
||||
</>
|
||||
) : null}
|
||||
{throttlingInputs}
|
||||
</DescribedFormGroupWithWrap>
|
||||
);
|
||||
});
|
||||
{isThrottlingEnabled && (exceedsDownloadLimits || exceedsUploadLimits) ? (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
<ThrottlingExceededCallout />
|
||||
</>
|
||||
) : null}
|
||||
{throttlingInputs}
|
||||
</DescribedFormGroupWithWrap>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -12,9 +12,16 @@ export interface Props {
|
|||
onChange: (value: string[]) => void;
|
||||
onBlur?: () => void;
|
||||
selectedOptions: string[];
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
export const ComboBox = ({ onChange, onBlur, selectedOptions, ...props }: Props) => {
|
||||
export const ComboBox = ({
|
||||
onChange,
|
||||
onBlur,
|
||||
selectedOptions,
|
||||
readOnly = false,
|
||||
...props
|
||||
}: Props) => {
|
||||
const [formattedSelectedOptions, setSelectedOptions] = useState<
|
||||
Array<EuiComboBoxOptionOption<string>>
|
||||
>(selectedOptions.map((option) => ({ label: option, key: option })));
|
||||
|
@ -68,6 +75,7 @@ export const ComboBox = ({ onChange, onBlur, selectedOptions, ...props }: Props)
|
|||
onBlur={() => onBlur?.()}
|
||||
onSearchChange={onSearchChange}
|
||||
isInvalid={isInvalid}
|
||||
isDisabled={readOnly}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -14,9 +14,10 @@ interface Props {
|
|||
fields: CommonFields;
|
||||
onChange: ({ value, configKey }: { value: boolean; configKey: ConfigKey }) => void;
|
||||
onBlur?: () => void;
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
export function Enabled({ fields, onChange, onBlur }: Props) {
|
||||
export function Enabled({ fields, onChange, onBlur, readOnly }: Props) {
|
||||
return (
|
||||
<>
|
||||
<EuiFormRow
|
||||
|
@ -43,6 +44,7 @@ export function Enabled({ fields, onChange, onBlur }: Props) {
|
|||
})
|
||||
}
|
||||
onBlur={() => onBlur?.()}
|
||||
disabled={readOnly}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</>
|
||||
|
|
|
@ -25,6 +25,7 @@ export const commonFormatters: CommonFormatMap = {
|
|||
[ConfigKey.TIMEOUT]: (fields) => secondsToCronFormatter(fields[ConfigKey.TIMEOUT] || undefined),
|
||||
[ConfigKey.NAMESPACE]: null,
|
||||
[ConfigKey.REVISION]: null,
|
||||
[ConfigKey.MONITOR_SOURCE_TYPE]: null,
|
||||
};
|
||||
|
||||
export const arrayToJsonFormatter = (value: string[] = []) =>
|
||||
|
|
|
@ -87,4 +87,5 @@ export const commonNormalizers: CommonNormalizerMap = {
|
|||
[ConfigKey.NAMESPACE]: (fields) =>
|
||||
fields?.[ConfigKey.NAMESPACE]?.value ?? DEFAULT_NAMESPACE_STRING,
|
||||
[ConfigKey.REVISION]: getCommonNormalizer(ConfigKey.REVISION),
|
||||
[ConfigKey.MONITOR_SOURCE_TYPE]: getCommonNormalizer(ConfigKey.MONITOR_SOURCE_TYPE),
|
||||
};
|
||||
|
|
|
@ -11,6 +11,7 @@ import { MONITOR_ADD_ROUTE } from '../../../../../common/constants';
|
|||
import { DEFAULT_NAMESPACE_STRING } from '../../../../../common/constants/monitor_defaults';
|
||||
import {
|
||||
ScheduleUnit,
|
||||
SourceType,
|
||||
MonitorServiceLocations,
|
||||
ThrottlingOptions,
|
||||
DEFAULT_THROTTLING,
|
||||
|
@ -41,6 +42,7 @@ interface IPolicyConfigContext {
|
|||
defaultNamespace?: string;
|
||||
namespace?: string;
|
||||
throttling: ThrottlingOptions;
|
||||
sourceType?: SourceType;
|
||||
}
|
||||
|
||||
export interface IPolicyConfigContextProvider {
|
||||
|
@ -56,6 +58,7 @@ export interface IPolicyConfigContextProvider {
|
|||
isZipUrlSourceEnabled?: boolean;
|
||||
allowedScheduleUnits?: ScheduleUnit[];
|
||||
throttling?: ThrottlingOptions;
|
||||
sourceType?: SourceType;
|
||||
}
|
||||
|
||||
export const initialMonitorTypeValue = DataStream.HTTP;
|
||||
|
@ -93,6 +96,7 @@ export const defaultContext: IPolicyConfigContext = {
|
|||
allowedScheduleUnits: [ScheduleUnit.MINUTES, ScheduleUnit.SECONDS],
|
||||
defaultNamespace: DEFAULT_NAMESPACE_STRING,
|
||||
throttling: DEFAULT_THROTTLING,
|
||||
sourceType: SourceType.UI,
|
||||
};
|
||||
|
||||
export const PolicyConfigContext = createContext(defaultContext);
|
||||
|
@ -110,6 +114,7 @@ export function PolicyConfigContextProvider<ExtraFields = unknown>({
|
|||
runsOnService = false,
|
||||
isZipUrlSourceEnabled = true,
|
||||
allowedScheduleUnits = [ScheduleUnit.MINUTES, ScheduleUnit.SECONDS],
|
||||
sourceType,
|
||||
}: IPolicyConfigContextProvider) {
|
||||
const [monitorType, setMonitorType] = useState<DataStream>(defaultMonitorType);
|
||||
const [name, setName] = useState<string>(defaultName);
|
||||
|
@ -150,6 +155,7 @@ export function PolicyConfigContextProvider<ExtraFields = unknown>({
|
|||
namespace,
|
||||
setNamespace,
|
||||
throttling,
|
||||
sourceType,
|
||||
} as IPolicyConfigContext;
|
||||
}, [
|
||||
monitorType,
|
||||
|
@ -168,6 +174,7 @@ export function PolicyConfigContextProvider<ExtraFields = unknown>({
|
|||
allowedScheduleUnits,
|
||||
namespace,
|
||||
throttling,
|
||||
sourceType,
|
||||
]);
|
||||
|
||||
return <PolicyConfigContext.Provider value={value} children={children} />;
|
||||
|
|
|
@ -16,9 +16,10 @@ interface Props {
|
|||
onChange: (schedule: MonitorFields[ConfigKey.SCHEDULE]) => void;
|
||||
onBlur: () => void;
|
||||
unit: ScheduleUnit;
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
export const ScheduleField = ({ number, onChange, onBlur, unit }: Props) => {
|
||||
export const ScheduleField = ({ number, onChange, onBlur, unit, readOnly = false }: Props) => {
|
||||
const { allowedScheduleUnits } = usePolicyConfigContext();
|
||||
const options = !allowedScheduleUnits?.length
|
||||
? allOptions
|
||||
|
@ -55,6 +56,7 @@ export const ScheduleField = ({ number, onChange, onBlur, unit }: Props) => {
|
|||
|
||||
onBlur();
|
||||
}}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
|
@ -74,6 +76,7 @@ export const ScheduleField = ({ number, onChange, onBlur, unit }: Props) => {
|
|||
onChange({ number, unit: updatedUnit as ScheduleUnit });
|
||||
}}
|
||||
onBlur={() => onBlur()}
|
||||
disabled={readOnly}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -25,7 +25,7 @@ import { MONITOR_MANAGEMENT_ROUTE } from '../../../../../common/constants';
|
|||
import { UptimeSettingsContext } from '../../../contexts';
|
||||
import { setMonitor } from '../../../state/api';
|
||||
|
||||
import { SyntheticsMonitor } from '../../../../../common/runtime_types';
|
||||
import { ConfigKey, SyntheticsMonitor, SourceType } from '../../../../../common/runtime_types';
|
||||
import { TestRun } from '../test_now_mode/test_now_mode';
|
||||
|
||||
import { monitorManagementListSelector } from '../../../state/selectors';
|
||||
|
@ -58,6 +58,7 @@ export const ActionBar = ({
|
|||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [isSuccessful, setIsSuccessful] = useState(false);
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState<boolean | undefined>(undefined);
|
||||
const isReadOnly = monitor[ConfigKey.MONITOR_SOURCE_TYPE] === SourceType.PROJECT;
|
||||
|
||||
const { data, status } = useFetcher(() => {
|
||||
if (!isSaving || !isValid) {
|
||||
|
@ -111,72 +112,73 @@ export const ActionBar = ({
|
|||
return isSuccessful ? (
|
||||
<Redirect to={MONITOR_MANAGEMENT_ROUTE + '/all'} />
|
||||
) : (
|
||||
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<WarningText>{!isValid && hasBeenSubmitted && VALIDATION_ERROR_LABEL}</WarningText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexGroup gutterSize="s" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
color="ghost"
|
||||
size="s"
|
||||
href={`${basePath}/app/uptime/${MONITOR_MANAGEMENT_ROUTE}/all`}
|
||||
>
|
||||
{DISCARD_LABEL}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
|
||||
{onTestNow && (
|
||||
<EuiFlexItem grow={false}>
|
||||
{/* Popover is used instead of EuiTooltip until the resolution of https://github.com/elastic/eui/issues/5604 */}
|
||||
<EuiPopover
|
||||
repositionOnScroll={true}
|
||||
initialFocus={false}
|
||||
button={
|
||||
<EuiButton
|
||||
css={{ width: '100%' }}
|
||||
fill
|
||||
size="s"
|
||||
color="success"
|
||||
iconType="play"
|
||||
disabled={!isValid || isTestRunInProgress}
|
||||
data-test-subj={'monitorTestNowRunBtn'}
|
||||
onClick={() => onTestNow()}
|
||||
onMouseEnter={() => {
|
||||
setIsPopoverOpen(true);
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
setIsPopoverOpen(false);
|
||||
}}
|
||||
>
|
||||
{testRun ? RE_RUN_TEST_LABEL : RUN_TEST_LABEL}
|
||||
</EuiButton>
|
||||
}
|
||||
isOpen={isPopoverOpen}
|
||||
>
|
||||
<EuiText style={{ width: 260, outline: 'none' }}>
|
||||
<p>{TEST_NOW_DESCRIPTION}</p>
|
||||
</EuiText>
|
||||
</EuiPopover>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
color="primary"
|
||||
fill
|
||||
size="s"
|
||||
iconType="check"
|
||||
onClick={handleOnSave}
|
||||
isLoading={isSaving}
|
||||
disabled={hasBeenSubmitted && !isValid}
|
||||
>
|
||||
{monitorId ? UPDATE_MONITOR_LABEL : SAVE_MONITOR_LABEL}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiButtonEmpty
|
||||
color="ghost"
|
||||
size="s"
|
||||
href={`${basePath}/app/uptime/${MONITOR_MANAGEMENT_ROUTE}/all`}
|
||||
>
|
||||
{DISCARD_LABEL}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
{!isReadOnly ? (
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup justifyContent="flexEnd" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<WarningText>{!isValid && hasBeenSubmitted && VALIDATION_ERROR_LABEL}</WarningText>
|
||||
</EuiFlexItem>
|
||||
{onTestNow && (
|
||||
<EuiFlexItem grow={false}>
|
||||
{/* Popover is used instead of EuiTooltip until the resolution of https://github.com/elastic/eui/issues/5604 */}
|
||||
<EuiPopover
|
||||
repositionOnScroll={true}
|
||||
initialFocus={false}
|
||||
button={
|
||||
<EuiButton
|
||||
css={{ width: '100%' }}
|
||||
fill
|
||||
size="s"
|
||||
color="success"
|
||||
iconType="play"
|
||||
disabled={!isValid || isTestRunInProgress}
|
||||
data-test-subj={'monitorTestNowRunBtn'}
|
||||
onClick={() => onTestNow()}
|
||||
onMouseEnter={() => {
|
||||
setIsPopoverOpen(true);
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
setIsPopoverOpen(false);
|
||||
}}
|
||||
>
|
||||
{testRun ? RE_RUN_TEST_LABEL : RUN_TEST_LABEL}
|
||||
</EuiButton>
|
||||
}
|
||||
isOpen={isPopoverOpen}
|
||||
>
|
||||
<EuiText style={{ width: 260, outline: 'none' }}>
|
||||
<p>{TEST_NOW_DESCRIPTION}</p>
|
||||
</EuiText>
|
||||
</EuiPopover>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
color="primary"
|
||||
fill
|
||||
size="s"
|
||||
iconType="check"
|
||||
onClick={handleOnSave}
|
||||
isLoading={isSaving}
|
||||
disabled={hasBeenSubmitted && !isValid}
|
||||
>
|
||||
{monitorId ? UPDATE_MONITOR_LABEL : SAVE_MONITOR_LABEL}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
@ -187,7 +189,7 @@ const WarningText = euiStyled(EuiText)`
|
|||
`;
|
||||
|
||||
const DISCARD_LABEL = i18n.translate('xpack.synthetics.monitorManagement.discardLabel', {
|
||||
defaultMessage: 'Discard',
|
||||
defaultMessage: 'Cancel',
|
||||
});
|
||||
|
||||
const SAVE_MONITOR_LABEL = i18n.translate('xpack.synthetics.monitorManagement.saveMonitorLabel', {
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
TLSFields,
|
||||
DataStream,
|
||||
ScheduleUnit,
|
||||
SourceType,
|
||||
ThrottlingOptions,
|
||||
} from '../../../../common/runtime_types';
|
||||
import { SyntheticsProviders } from '../fleet_package/contexts';
|
||||
|
@ -85,6 +86,7 @@ export const EditMonitorConfig = ({ monitor, throttling }: Props) => {
|
|||
isZipUrlSourceEnabled: false,
|
||||
allowedScheduleUnits: [ScheduleUnit.MINUTES],
|
||||
runsOnService: true,
|
||||
sourceType: monitor[ConfigKey.MONITOR_SOURCE_TYPE] || SourceType.UI,
|
||||
}}
|
||||
httpDefaultValues={fullDefaultConfig[DataStream.HTTP]}
|
||||
tcpDefaultValues={fullDefaultConfig[DataStream.TCP]}
|
||||
|
|
|
@ -17,9 +17,16 @@ interface Props {
|
|||
setLocations: React.Dispatch<React.SetStateAction<MonitorServiceLocations>>;
|
||||
isInvalid: boolean;
|
||||
onBlur?: () => void;
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
export const ServiceLocations = ({ selectedLocations, setLocations, isInvalid, onBlur }: Props) => {
|
||||
export const ServiceLocations = ({
|
||||
selectedLocations,
|
||||
setLocations,
|
||||
isInvalid,
|
||||
onBlur,
|
||||
readOnly = false,
|
||||
}: Props) => {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [checkboxIdToSelectedMap, setCheckboxIdToSelectedMap] = useState<Record<string, boolean>>(
|
||||
{}
|
||||
|
@ -76,6 +83,7 @@ export const ServiceLocations = ({ selectedLocations, setLocations, isInvalid, o
|
|||
idToSelectedMap={checkboxIdToSelectedMap}
|
||||
onChange={(id) => onLocationChange(id)}
|
||||
onBlur={() => onBlur?.()}
|
||||
disabled={readOnly}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
|
|
|
@ -9,7 +9,13 @@ 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 {
|
||||
ConfigKey,
|
||||
DataStream,
|
||||
HTTPFields,
|
||||
BrowserFields,
|
||||
SourceType,
|
||||
} from '../../../../../common/runtime_types';
|
||||
import { render } from '../../../lib/helper/rtl_helpers';
|
||||
import {
|
||||
BrowserContextProvider,
|
||||
|
@ -19,26 +25,51 @@ import {
|
|||
TCPContextProvider,
|
||||
TLSFieldsContextProvider,
|
||||
} from '../../fleet_package/contexts';
|
||||
import { defaultConfig } from '../../fleet_package/synthetics_policy_create_extension';
|
||||
import { DEFAULT_FIELDS } from '../../../../../common/constants/monitor_defaults';
|
||||
import { MonitorFields } from './monitor_fields';
|
||||
|
||||
const defaultHTTPConfig = defaultConfig[DataStream.HTTP] as HTTPFields;
|
||||
jest.mock('@kbn/kibana-react-plugin/public', () => {
|
||||
const original = jest.requireActual('@kbn/kibana-react-plugin/public');
|
||||
return {
|
||||
...original,
|
||||
// Mocking CodeEditor, which uses React Monaco under the hood
|
||||
CodeEditor: (props: any) => (
|
||||
<input
|
||||
data-test-subj={props['data-test-subj'] || 'mockCodeEditor'}
|
||||
data-currentvalue={props.value}
|
||||
onChange={(e: any) => {
|
||||
props.onChange(e.jsonContent);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
const defaultHTTPConfig = DEFAULT_FIELDS[DataStream.HTTP] as HTTPFields;
|
||||
const defaultBrowserConfig = DEFAULT_FIELDS[DataStream.BROWSER];
|
||||
|
||||
describe('<MonitorFields />', () => {
|
||||
const WrappedComponent = ({
|
||||
isEditable = true,
|
||||
isFormSubmitted = false,
|
||||
defaultSimpleHttpFields = defaultHTTPConfig,
|
||||
defaultBrowserFields = defaultBrowserConfig,
|
||||
readOnly = false,
|
||||
}: {
|
||||
isEditable?: boolean;
|
||||
isFormSubmitted?: boolean;
|
||||
defaultSimpleHttpFields?: HTTPFields;
|
||||
defaultBrowserFields?: BrowserFields;
|
||||
readOnly?: boolean;
|
||||
}) => {
|
||||
return (
|
||||
<HTTPContextProvider defaultValues={defaultSimpleHttpFields}>
|
||||
<PolicyConfigContextProvider isEditable={isEditable}>
|
||||
<PolicyConfigContextProvider
|
||||
isEditable={isEditable}
|
||||
sourceType={readOnly ? SourceType.PROJECT : SourceType.UI}
|
||||
>
|
||||
<TCPContextProvider>
|
||||
<BrowserContextProvider>
|
||||
<BrowserContextProvider defaultValues={defaultBrowserFields}>
|
||||
<ICMPSimpleFieldsContextProvider>
|
||||
<TLSFieldsContextProvider>
|
||||
<MonitorFields isFormSubmitted={isFormSubmitted} />
|
||||
|
@ -82,13 +113,20 @@ describe('<MonitorFields />', () => {
|
|||
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} />
|
||||
it('is reradonly when source type is project', async () => {
|
||||
const name = 'monitor name';
|
||||
const browserFields = {
|
||||
...defaultBrowserConfig,
|
||||
[ConfigKey.NAME]: name,
|
||||
};
|
||||
const { getByText } = render(
|
||||
<WrappedComponent
|
||||
isFormSubmitted={false}
|
||||
defaultBrowserFields={browserFields}
|
||||
readOnly={true}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(queryByText('Monitor name is required')).toBeNull();
|
||||
expect(queryByText('URL is required')).toBeNull();
|
||||
expect(getByText('Read only')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,18 +7,19 @@
|
|||
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { EuiForm } from '@elastic/eui';
|
||||
import { ConfigKey, DataStream } from '../../../../../common/runtime_types';
|
||||
import { ConfigKey, DataStream, SourceType } from '../../../../../common/runtime_types';
|
||||
import { usePolicyConfigContext } from '../../fleet_package/contexts';
|
||||
|
||||
import { CustomFields } from '../../fleet_package/custom_fields';
|
||||
import { validate } from '../validation';
|
||||
import { MonitorNameAndLocation } from './monitor_name_location';
|
||||
import { MonitorManagementAdvancedFields } from './monitor_advanced_fields';
|
||||
import { ProjectBrowserReadonlyFields } from './read_only_browser_fields';
|
||||
|
||||
const MIN_COLUMN_WRAP_WIDTH = '360px';
|
||||
|
||||
export const MonitorFields = ({ isFormSubmitted = false }: { isFormSubmitted?: boolean }) => {
|
||||
const { monitorType } = usePolicyConfigContext();
|
||||
const { monitorType, sourceType } = usePolicyConfigContext();
|
||||
|
||||
const [touchedFieldsHash, setTouchedFieldsHash] = useState<Record<string, boolean>>({});
|
||||
|
||||
|
@ -41,21 +42,25 @@ export const MonitorFields = ({ isFormSubmitted = false }: { isFormSubmitted?: b
|
|||
|
||||
return (
|
||||
<EuiForm id="syntheticsServiceCreateMonitorForm" component="form">
|
||||
<CustomFields
|
||||
minColumnWidth={MIN_COLUMN_WRAP_WIDTH}
|
||||
validate={fieldValidation}
|
||||
dataStreams={[DataStream.HTTP, DataStream.TCP, DataStream.ICMP, DataStream.BROWSER]}
|
||||
appendAdvancedFields={
|
||||
<MonitorManagementAdvancedFields
|
||||
validate={fieldValidation}
|
||||
minColumnWidth={MIN_COLUMN_WRAP_WIDTH}
|
||||
onFieldBlur={handleFieldBlur}
|
||||
/>
|
||||
}
|
||||
onFieldBlur={handleFieldBlur}
|
||||
>
|
||||
<MonitorNameAndLocation validate={fieldValidation} onFieldBlur={handleFieldBlur} />
|
||||
</CustomFields>
|
||||
{sourceType === SourceType.PROJECT ? (
|
||||
<ProjectBrowserReadonlyFields minColumnWidth={MIN_COLUMN_WRAP_WIDTH} />
|
||||
) : (
|
||||
<CustomFields
|
||||
minColumnWidth={MIN_COLUMN_WRAP_WIDTH}
|
||||
validate={fieldValidation}
|
||||
dataStreams={[DataStream.HTTP, DataStream.TCP, DataStream.ICMP, DataStream.BROWSER]}
|
||||
appendAdvancedFields={
|
||||
<MonitorManagementAdvancedFields
|
||||
validate={fieldValidation}
|
||||
minColumnWidth={MIN_COLUMN_WRAP_WIDTH}
|
||||
onFieldBlur={handleFieldBlur}
|
||||
/>
|
||||
}
|
||||
onFieldBlur={handleFieldBlur}
|
||||
>
|
||||
<MonitorNameAndLocation validate={fieldValidation} onFieldBlur={handleFieldBlur} />
|
||||
</CustomFields>
|
||||
)}
|
||||
</EuiForm>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -16,16 +16,19 @@ import { ServiceLocations } from './locations';
|
|||
import { useMonitorName } from './use_monitor_name';
|
||||
|
||||
interface Props {
|
||||
validate: Validation;
|
||||
validate?: Validation;
|
||||
onFieldBlur?: (field: ConfigKey) => void;
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
export const MonitorNameAndLocation = ({ validate, onFieldBlur }: Props) => {
|
||||
export const MonitorNameAndLocation = ({ validate, onFieldBlur, readOnly }: Props) => {
|
||||
const { name, setName, locations = [], setLocations } = usePolicyConfigContext();
|
||||
const isNameInvalid = !!validate[ConfigKey.NAME]?.({ [ConfigKey.NAME]: name });
|
||||
const isLocationsInvalid = !!validate[ConfigKey.LOCATIONS]?.({
|
||||
[ConfigKey.LOCATIONS]: locations,
|
||||
});
|
||||
const isNameInvalid = validate ? !!validate[ConfigKey.NAME]?.({ [ConfigKey.NAME]: name }) : false;
|
||||
const isLocationsInvalid = validate
|
||||
? !!validate[ConfigKey.LOCATIONS]?.({
|
||||
[ConfigKey.LOCATIONS]: locations,
|
||||
})
|
||||
: false;
|
||||
|
||||
const [localName, setLocalName] = useState(name);
|
||||
|
||||
|
@ -67,6 +70,7 @@ export const MonitorNameAndLocation = ({ validate, onFieldBlur }: Props) => {
|
|||
onChange={(event) => setLocalName(event.target.value)}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.NAME)}
|
||||
data-test-subj="monitorManagementMonitorName"
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<ServiceLocations
|
||||
|
@ -74,6 +78,7 @@ export const MonitorNameAndLocation = ({ validate, onFieldBlur }: Props) => {
|
|||
selectedLocations={locations}
|
||||
isInvalid={isLocationsInvalid}
|
||||
onBlur={() => onFieldBlur?.(ConfigKey.LOCATIONS)}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,168 @@
|
|||
/*
|
||||
* 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 { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiAccordion, EuiCallOut, EuiFormRow, EuiFieldText, EuiSpacer } from '@elastic/eui';
|
||||
import { ConfigKey } from '../../../../../common/runtime_types';
|
||||
import {
|
||||
useBrowserSimpleFieldsContext,
|
||||
useBrowserAdvancedFieldsContext,
|
||||
} from '../../fleet_package/contexts';
|
||||
import { Enabled } from '../../fleet_package/common/enabled';
|
||||
import { ScheduleField } from '../../fleet_package/schedule_field';
|
||||
import { ComboBox } from '../../fleet_package/combo_box';
|
||||
import { MonitorNameAndLocation } from './monitor_name_location';
|
||||
import { ThrottlingFields } from '../../fleet_package/browser/throttling_fields';
|
||||
import { OptionalLabel } from '../../fleet_package/optional_label';
|
||||
import { DescribedFormGroupWithWrap } from '../../fleet_package/common/described_form_group_with_wrap';
|
||||
|
||||
const noop = () => {};
|
||||
|
||||
export const ProjectBrowserReadonlyFields = ({ minColumnWidth }: { minColumnWidth: string }) => {
|
||||
const { fields } = useBrowserSimpleFieldsContext();
|
||||
const { fields: advancedFields } = useBrowserAdvancedFieldsContext();
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiCallOut
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.browser.project.readOnly.callout.title"
|
||||
defaultMessage="Read only"
|
||||
/>
|
||||
}
|
||||
iconType="document"
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.browser.project.readOnly.callout.content"
|
||||
defaultMessage="This monitor was added from an external project. Configuration is read only."
|
||||
/>
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
<EuiSpacer />
|
||||
<DescribedFormGroupWithWrap
|
||||
title={
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.browser.project.monitorIntegrationSettingsSectionTitle"
|
||||
defaultMessage="Monitor settings"
|
||||
/>
|
||||
</h4>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.browser.project.monitorIntegrationSettingsSectionDescription"
|
||||
defaultMessage="Configure your monitor with the following options."
|
||||
/>
|
||||
}
|
||||
data-test-subj="monitorSettingsSection"
|
||||
minColumnWidth={minColumnWidth}
|
||||
>
|
||||
<MonitorNameAndLocation readOnly={true} />
|
||||
<Enabled fields={fields} onChange={noop} onBlur={noop} readOnly={true} />
|
||||
<EuiFormRow
|
||||
id="syntheticsFleetScheduleField--number syntheticsFleetScheduleField--unit"
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.browser.project.monitorIntegrationSettingsSection.monitorInterval"
|
||||
defaultMessage="Frequency"
|
||||
/>
|
||||
}
|
||||
error={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.browser.project.monitorIntegrationSettingsSection.monitorInterval.error"
|
||||
defaultMessage="Monitor frequency is required"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<ScheduleField
|
||||
onBlur={noop}
|
||||
onChange={noop}
|
||||
number={fields[ConfigKey.SCHEDULE].number}
|
||||
unit={fields[ConfigKey.SCHEDULE].unit}
|
||||
readOnly={true}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.browser.project.monitorIntegrationSettingsSection.tags.label"
|
||||
defaultMessage="Tags"
|
||||
/>
|
||||
}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.browser.project.monitorIntegrationSettingsSection.tags.helpText"
|
||||
defaultMessage="A list of tags that will be sent with the monitor event. Press enter to add a new tag. Displayed in Uptime and enables searching by tag."
|
||||
/>
|
||||
}
|
||||
labelAppend={<OptionalLabel />}
|
||||
>
|
||||
<ComboBox
|
||||
selectedOptions={fields[ConfigKey.TAGS]}
|
||||
onChange={noop}
|
||||
onBlur={noop}
|
||||
data-test-subj="syntheticsTags"
|
||||
readOnly={true}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</DescribedFormGroupWithWrap>
|
||||
<EuiSpacer />
|
||||
<EuiAccordion
|
||||
id="syntheticsIntegrationBrowserAdvancedOptions"
|
||||
buttonContent="Advanced Browser options"
|
||||
data-test-subj="syntheticsBrowserAdvancedFieldsAccordion"
|
||||
>
|
||||
<EuiSpacer size="m" />
|
||||
<DescribedFormGroupWithWrap
|
||||
title={
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.browser.project.browserAdvancedSettings.title"
|
||||
defaultMessage="Synthetics agent options"
|
||||
/>
|
||||
</h4>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.browser.project.browserAdvancedSettings.description"
|
||||
defaultMessage="Provide fine-tuned configuration for the synthetics agent."
|
||||
/>
|
||||
}
|
||||
minColumnWidth={minColumnWidth}
|
||||
>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.browser.project.browserAdvancedSettings.screenshots.label"
|
||||
defaultMessage="Screenshot options"
|
||||
/>
|
||||
}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.browser.project.browserAdvancedSettings.screenshots.helpText"
|
||||
defaultMessage="Set this option to manage the screenshots captured by the synthetics agent."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFieldText
|
||||
value={advancedFields[ConfigKey.SCREENSHOTS]}
|
||||
onChange={noop}
|
||||
data-test-subj="syntheticsBrowserScreenshots"
|
||||
readOnly={true}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</DescribedFormGroupWithWrap>
|
||||
|
||||
<ThrottlingFields minColumnWidth={minColumnWidth} readOnly={true} />
|
||||
</EuiAccordion>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -11,6 +11,7 @@ import {
|
|||
EuiLink,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
import { EuiTableSortingType } from '@elastic/eui/src/components/basic_table/table_types';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
@ -23,8 +24,10 @@ import {
|
|||
ICMPSimpleFields,
|
||||
Ping,
|
||||
ServiceLocations,
|
||||
SourceType,
|
||||
EncryptedSyntheticsMonitorWithId,
|
||||
TCPSimpleFields,
|
||||
BrowserFields,
|
||||
} from '../../../../../common/runtime_types';
|
||||
import { UptimeSettingsContext } from '../../../contexts';
|
||||
import { useBreakpoints } from '../../../../hooks/use_breakpoints';
|
||||
|
@ -119,8 +122,14 @@ export const MonitorManagementList = ({
|
|||
defaultMessage: 'Monitor name',
|
||||
}),
|
||||
sortable: true,
|
||||
render: (name: string, { id }: EncryptedSyntheticsMonitorWithId) => (
|
||||
<EuiLink href={`${basePath}/app/uptime/monitor/${btoa(id)}`}>{name}</EuiLink>
|
||||
render: (name: string, monitor: EncryptedSyntheticsMonitorWithId) => (
|
||||
<EuiLink
|
||||
href={`${basePath}/app/uptime/monitor/${btoa(
|
||||
(monitor as unknown as BrowserFields)[ConfigKey.CUSTOM_HEARTBEAT_ID] || monitor.id
|
||||
)}`}
|
||||
>
|
||||
{name}
|
||||
</EuiLink>
|
||||
),
|
||||
},
|
||||
{
|
||||
|
@ -174,12 +183,23 @@ export const MonitorManagementList = ({
|
|||
defaultMessage: 'Enabled',
|
||||
}),
|
||||
render: (_enabled: boolean, monitor: EncryptedSyntheticsMonitorWithId) => (
|
||||
<MonitorEnabled
|
||||
id={monitor.id}
|
||||
monitor={monitor}
|
||||
isDisabled={!canEdit}
|
||||
onUpdate={onUpdate}
|
||||
/>
|
||||
<EuiToolTip
|
||||
content={
|
||||
monitor[ConfigKey.MONITOR_SOURCE_TYPE] === SourceType.PROJECT
|
||||
? i18n.translate('xpack.synthetics.monitorManagement.monitorList.enabled.tooltip', {
|
||||
defaultMessage:
|
||||
'This monitor was added from an external project. Configuration is read only.',
|
||||
})
|
||||
: ''
|
||||
}
|
||||
>
|
||||
<MonitorEnabled
|
||||
id={monitor.id}
|
||||
monitor={monitor}
|
||||
isDisabled={!canEdit || monitor[ConfigKey.MONITOR_SOURCE_TYPE] === SourceType.PROJECT}
|
||||
onUpdate={onUpdate}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
),
|
||||
},
|
||||
{
|
||||
|
|
|
@ -27,6 +27,7 @@ import { MlPluginSetup as MlSetup } from '@kbn/ml-plugin/server';
|
|||
import { RuleRegistryPluginSetupContract } from '@kbn/rule-registry-plugin/server';
|
||||
import { SecurityPluginStart } from '@kbn/security-plugin/server';
|
||||
import { CloudSetup } from '@kbn/cloud-plugin/server';
|
||||
import { SpacesPluginSetup } from '@kbn/spaces-plugin/server';
|
||||
import { FleetStartContract } from '@kbn/fleet-plugin/server';
|
||||
import { UptimeESClient } from '../../lib';
|
||||
import type { TelemetryEventsSender } from '../../telemetry/sender';
|
||||
|
@ -50,6 +51,7 @@ export interface UptimeServerSetup {
|
|||
router: UptimeRouter;
|
||||
config: UptimeConfig;
|
||||
cloud?: CloudSetup;
|
||||
spaces: SpacesPluginSetup;
|
||||
fleet: FleetStartContract;
|
||||
security: SecurityPluginStart;
|
||||
savedObjectsClient?: SavedObjectsClientContract;
|
||||
|
@ -71,6 +73,7 @@ export interface UptimeCorePluginsSetup {
|
|||
usageCollection: UsageCollectionSetup;
|
||||
ml: MlSetup;
|
||||
cloud?: CloudSetup;
|
||||
spaces: SpacesPluginSetup;
|
||||
ruleRegistry: RuleRegistryPluginSetupContract;
|
||||
encryptedSavedObjects: EncryptedSavedObjectsPluginSetup;
|
||||
taskManager: TaskManagerSetupContract;
|
||||
|
|
|
@ -10,8 +10,10 @@ import { EncryptedSavedObjectsClient } from '@kbn/encrypted-saved-objects-plugin
|
|||
import {
|
||||
SyntheticsMonitorWithSecrets,
|
||||
EncryptedSyntheticsMonitor,
|
||||
SyntheticsMonitor,
|
||||
} from '../../../../common/runtime_types';
|
||||
import { syntheticsMonitor, syntheticsMonitorType } from '../saved_objects/synthetics_monitor';
|
||||
import { normalizeSecrets } from '../../../synthetics_service/utils/secrets';
|
||||
|
||||
export const getSyntheticsMonitor = async ({
|
||||
monitorId,
|
||||
|
@ -21,20 +23,22 @@ export const getSyntheticsMonitor = async ({
|
|||
monitorId: string;
|
||||
encryptedSavedObjectsClient: EncryptedSavedObjectsClient;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
}): Promise<SavedObject<SyntheticsMonitorWithSecrets>> => {
|
||||
}): Promise<SavedObject<SyntheticsMonitor>> => {
|
||||
try {
|
||||
const encryptedMonitor = await savedObjectsClient.get<EncryptedSyntheticsMonitor>(
|
||||
syntheticsMonitorType,
|
||||
monitorId
|
||||
);
|
||||
|
||||
return await encryptedSavedObjectsClient.getDecryptedAsInternalUser<SyntheticsMonitorWithSecrets>(
|
||||
syntheticsMonitor.name,
|
||||
monitorId,
|
||||
{
|
||||
namespace: encryptedMonitor.namespaces?.[0],
|
||||
}
|
||||
);
|
||||
const decryptedMonitor =
|
||||
await encryptedSavedObjectsClient.getDecryptedAsInternalUser<SyntheticsMonitorWithSecrets>(
|
||||
syntheticsMonitor.name,
|
||||
monitorId,
|
||||
{
|
||||
namespace: encryptedMonitor.namespaces?.[0],
|
||||
}
|
||||
);
|
||||
return normalizeSecrets(decryptedMonitor);
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
|
|
|
@ -44,6 +44,18 @@ export const syntheticsMonitor: SavedObjectsType = {
|
|||
},
|
||||
},
|
||||
},
|
||||
journey_id: {
|
||||
type: 'keyword',
|
||||
},
|
||||
project_id: {
|
||||
type: 'keyword',
|
||||
},
|
||||
'monitor.origin': {
|
||||
type: 'keyword',
|
||||
},
|
||||
custom_heartbeat_id: {
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
},
|
||||
management: {
|
||||
|
|
|
@ -28,7 +28,7 @@ export interface MonitorUpdateEvent {
|
|||
monitorInterval: number;
|
||||
locations: string[];
|
||||
locationsCount: number;
|
||||
scriptType?: 'inline' | 'recorder' | 'zip';
|
||||
scriptType?: 'inline' | 'recorder' | 'zip' | 'project';
|
||||
revision?: number;
|
||||
errors?: ServiceLocationErrors;
|
||||
configId: string;
|
||||
|
|
|
@ -10,6 +10,7 @@ import { UMServerLibs } from '../../lib/lib';
|
|||
import { UMRestApiRouteFactory } from '../types';
|
||||
import { API_URLS } from '../../../../common/constants';
|
||||
import { ConfigKey, MonitorFields } from '../../../../common/runtime_types';
|
||||
import { syntheticsMonitorType } from '../../../../common/types/saved_objects';
|
||||
|
||||
export const createGetStatusBarRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({
|
||||
method: 'GET',
|
||||
|
@ -23,7 +24,6 @@ export const createGetStatusBarRoute: UMRestApiRouteFactory = (libs: UMServerLib
|
|||
},
|
||||
handler: async ({ uptimeEsClient, request, server, savedObjectsClient }): Promise<any> => {
|
||||
const { monitorId, dateStart, dateEnd } = request.query;
|
||||
const encryptedSavedObjectsClient = server.encryptedSavedObjects.getClient();
|
||||
|
||||
const latestMonitor = await libs.requests.getLatestMonitor({
|
||||
uptimeEsClient,
|
||||
|
@ -41,10 +41,13 @@ export const createGetStatusBarRoute: UMRestApiRouteFactory = (libs: UMServerLib
|
|||
}
|
||||
|
||||
try {
|
||||
const monitorSavedObject = await libs.requests.getSyntheticsMonitor({
|
||||
monitorId,
|
||||
encryptedSavedObjectsClient,
|
||||
savedObjectsClient,
|
||||
const {
|
||||
saved_objects: [monitorSavedObject],
|
||||
} = await savedObjectsClient.find({
|
||||
type: syntheticsMonitorType,
|
||||
perPage: 1,
|
||||
page: 1,
|
||||
filter: `${syntheticsMonitorType}.id: "${syntheticsMonitorType}:${monitorId}" OR ${syntheticsMonitorType}.attributes.${ConfigKey.CUSTOM_HEARTBEAT_ID}: "${monitorId}"`,
|
||||
});
|
||||
|
||||
if (!monitorSavedObject) {
|
||||
|
|
|
@ -84,6 +84,7 @@ export class Plugin implements PluginType {
|
|||
logger: this.logger,
|
||||
telemetry: this.telemetryEventsSender,
|
||||
isDev: this.initContext.env.mode.dev,
|
||||
spaces: plugins.spaces,
|
||||
} as UptimeServerSetup;
|
||||
|
||||
if (this.server.config.service) {
|
||||
|
|
|
@ -22,9 +22,11 @@ import { testNowMonitorRoute } from './synthetics_service/test_now_monitor';
|
|||
import { installIndexTemplatesRoute } from './synthetics_service/install_index_templates';
|
||||
import { editSyntheticsMonitorRoute } from './monitor_cruds/edit_monitor';
|
||||
import { addSyntheticsMonitorRoute } from './monitor_cruds/add_monitor';
|
||||
import { addSyntheticsProjectMonitorRoute } from './monitor_cruds/add_monitor_project';
|
||||
import { UMRestApiRouteFactory } from '../legacy_uptime/routes';
|
||||
|
||||
export const syntheticsAppRestApiRoutes: UMRestApiRouteFactory[] = [
|
||||
addSyntheticsProjectMonitorRoute,
|
||||
addSyntheticsMonitorRoute,
|
||||
getSyntheticsEnablementRoute,
|
||||
deleteSyntheticsMonitorRoute,
|
||||
|
|
|
@ -18,6 +18,7 @@ import { syntheticsMonitorType } from '../../legacy_uptime/lib/saved_objects/syn
|
|||
import { validateMonitor } from './monitor_validation';
|
||||
import { sendTelemetryEvents, formatTelemetryEvent } from '../telemetry/monitor_upgrade_sender';
|
||||
import { formatSecrets } from '../../synthetics_service/utils/secrets';
|
||||
import type { UptimeServerSetup } from '../../legacy_uptime/lib/adapters/framework';
|
||||
|
||||
export const addSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({
|
||||
method: 'POST',
|
||||
|
@ -58,27 +59,7 @@ export const addSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({
|
|||
});
|
||||
}
|
||||
|
||||
const { syntheticsService } = server;
|
||||
|
||||
const errors = await syntheticsService.addConfig({
|
||||
...monitor,
|
||||
id: newMonitor.id,
|
||||
fields: {
|
||||
config_id: newMonitor.id,
|
||||
},
|
||||
fields_under_root: true,
|
||||
});
|
||||
|
||||
sendTelemetryEvents(
|
||||
server.logger,
|
||||
server.telemetry,
|
||||
formatTelemetryEvent({
|
||||
monitor: newMonitor,
|
||||
errors,
|
||||
isInlineScript: Boolean((monitor as MonitorFields)[ConfigKey.SOURCE_INLINE]),
|
||||
kibanaVersion: server.kibanaVersion,
|
||||
})
|
||||
);
|
||||
const errors = await syncNewMonitor({ monitor, monitorSavedObject: newMonitor, server });
|
||||
|
||||
if (errors && errors.length > 0) {
|
||||
return response.ok({
|
||||
|
@ -93,3 +74,35 @@ export const addSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({
|
|||
return response.ok({ body: newMonitor });
|
||||
},
|
||||
});
|
||||
|
||||
export const syncNewMonitor = async ({
|
||||
monitor,
|
||||
monitorSavedObject,
|
||||
server,
|
||||
}: {
|
||||
monitor: SyntheticsMonitor;
|
||||
monitorSavedObject: SavedObject<EncryptedSyntheticsMonitor>;
|
||||
server: UptimeServerSetup;
|
||||
}) => {
|
||||
const errors = await server.syntheticsService.addConfig({
|
||||
...monitor,
|
||||
id: (monitor as MonitorFields)[ConfigKey.CUSTOM_HEARTBEAT_ID] || monitorSavedObject.id,
|
||||
fields: {
|
||||
config_id: monitorSavedObject.id,
|
||||
},
|
||||
fields_under_root: true,
|
||||
});
|
||||
|
||||
sendTelemetryEvents(
|
||||
server.logger,
|
||||
server.telemetry,
|
||||
formatTelemetryEvent({
|
||||
monitor: monitorSavedObject,
|
||||
errors,
|
||||
isInlineScript: Boolean((monitor as MonitorFields)[ConfigKey.SOURCE_INLINE]),
|
||||
kibanaVersion: server.kibanaVersion,
|
||||
})
|
||||
);
|
||||
|
||||
return errors;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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 { schema } from '@kbn/config-schema';
|
||||
import { UMServerLibs } from '../../legacy_uptime/lib/lib';
|
||||
import { ProjectBrowserMonitor, Locations } from '../../../common/runtime_types';
|
||||
|
||||
import { UMRestApiRouteFactory } from '../../legacy_uptime/routes/types';
|
||||
import { API_URLS } from '../../../common/constants';
|
||||
import { getServiceLocations } from '../../synthetics_service/get_service_locations';
|
||||
import { ProjectMonitorFormatter } from '../../synthetics_service/project_monitor_formatter';
|
||||
|
||||
export const addSyntheticsProjectMonitorRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({
|
||||
method: 'PUT',
|
||||
path: API_URLS.SYNTHETICS_MONITORS_PROJECT,
|
||||
validate: {
|
||||
body: schema.object({
|
||||
project: schema.string(),
|
||||
keep_stale: schema.boolean(),
|
||||
monitors: schema.arrayOf(schema.any()),
|
||||
}),
|
||||
},
|
||||
handler: async ({ request, response, savedObjectsClient, server }): Promise<any> => {
|
||||
const monitors = (request.body?.monitors as ProjectBrowserMonitor[]) || [];
|
||||
const spaceId = server.spaces.spacesService.getSpaceId(request);
|
||||
const { keep_stale: keepStale, project: projectId } = request.body || {};
|
||||
const locations: Locations = (await getServiceLocations(server)).locations;
|
||||
const encryptedSavedObjectsClient = server.encryptedSavedObjects.getClient();
|
||||
|
||||
const pushMonitorFormatter = new ProjectMonitorFormatter({
|
||||
projectId,
|
||||
spaceId,
|
||||
keepStale,
|
||||
locations,
|
||||
encryptedSavedObjectsClient,
|
||||
savedObjectsClient,
|
||||
monitors,
|
||||
server,
|
||||
});
|
||||
|
||||
await pushMonitorFormatter.configureAllProjectMonitors();
|
||||
|
||||
return response.ok({
|
||||
body: {
|
||||
createdMonitors: pushMonitorFormatter.createdMonitors,
|
||||
updatedMonitors: pushMonitorFormatter.updatedMonitors,
|
||||
staleMonitors: pushMonitorFormatter.staleMonitors,
|
||||
deletedMonitors: pushMonitorFormatter.deletedMonitors,
|
||||
failedMonitors: pushMonitorFormatter.failedMonitors,
|
||||
failedStaleMonitors: pushMonitorFormatter.failedStaleMonitors,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { SavedObjectsErrorHelpers } from '@kbn/core/server';
|
||||
import { SavedObjectsClientContract, SavedObjectsErrorHelpers } from '@kbn/core/server';
|
||||
import {
|
||||
ConfigKey,
|
||||
MonitorFields,
|
||||
|
@ -24,6 +24,7 @@ import {
|
|||
formatTelemetryDeleteEvent,
|
||||
} from '../telemetry/monitor_upgrade_sender';
|
||||
import { normalizeSecrets } from '../../synthetics_service/utils/secrets';
|
||||
import type { UptimeServerSetup } from '../../legacy_uptime/lib/adapters/framework';
|
||||
|
||||
export const deleteSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({
|
||||
method: 'DELETE',
|
||||
|
@ -33,49 +34,11 @@ export const deleteSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({
|
|||
monitorId: schema.string({ minLength: 1, maxLength: 1024 }),
|
||||
}),
|
||||
},
|
||||
handler: async ({
|
||||
request,
|
||||
response,
|
||||
savedObjectsClient,
|
||||
server: { encryptedSavedObjects, syntheticsService, logger, telemetry, kibanaVersion },
|
||||
}): Promise<any> => {
|
||||
const encryptedSavedObjectsClient = encryptedSavedObjects.getClient();
|
||||
|
||||
handler: async ({ request, response, savedObjectsClient, server }): Promise<any> => {
|
||||
const { monitorId } = request.params;
|
||||
|
||||
try {
|
||||
const encryptedMonitor = await savedObjectsClient.get<EncryptedSyntheticsMonitor>(
|
||||
syntheticsMonitorType,
|
||||
monitorId
|
||||
);
|
||||
|
||||
const monitor =
|
||||
await encryptedSavedObjectsClient.getDecryptedAsInternalUser<SyntheticsMonitorWithSecrets>(
|
||||
syntheticsMonitor.name,
|
||||
monitorId,
|
||||
{
|
||||
namespace: encryptedMonitor.namespaces?.[0],
|
||||
}
|
||||
);
|
||||
|
||||
const normalizedMonitor = normalizeSecrets(monitor);
|
||||
|
||||
await savedObjectsClient.delete(syntheticsMonitorType, monitorId);
|
||||
const errors = await syntheticsService.deleteConfigs([
|
||||
{ ...normalizedMonitor.attributes, id: monitorId },
|
||||
]);
|
||||
|
||||
sendTelemetryEvents(
|
||||
logger,
|
||||
telemetry,
|
||||
formatTelemetryDeleteEvent(
|
||||
monitor,
|
||||
kibanaVersion,
|
||||
new Date().toISOString(),
|
||||
Boolean((normalizedMonitor.attributes as MonitorFields)[ConfigKey.SOURCE_INLINE]),
|
||||
errors
|
||||
)
|
||||
);
|
||||
const errors = await deleteMonitor({ savedObjectsClient, server, monitorId });
|
||||
|
||||
if (errors && errors.length > 0) {
|
||||
return response.ok({
|
||||
|
@ -93,3 +56,59 @@ export const deleteSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({
|
|||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const deleteMonitor = async ({
|
||||
savedObjectsClient,
|
||||
server,
|
||||
monitorId,
|
||||
}: {
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
server: UptimeServerSetup;
|
||||
monitorId: string;
|
||||
}) => {
|
||||
const { syntheticsService, logger, telemetry, kibanaVersion, encryptedSavedObjects } = server;
|
||||
const encryptedSavedObjectsClient = encryptedSavedObjects.getClient();
|
||||
try {
|
||||
const encryptedMonitor = await savedObjectsClient.get<EncryptedSyntheticsMonitor>(
|
||||
syntheticsMonitorType,
|
||||
monitorId
|
||||
);
|
||||
|
||||
const monitor =
|
||||
await encryptedSavedObjectsClient.getDecryptedAsInternalUser<SyntheticsMonitorWithSecrets>(
|
||||
syntheticsMonitor.name,
|
||||
monitorId,
|
||||
{
|
||||
namespace: encryptedMonitor.namespaces?.[0],
|
||||
}
|
||||
);
|
||||
|
||||
const normalizedMonitor = normalizeSecrets(monitor);
|
||||
|
||||
await savedObjectsClient.delete(syntheticsMonitorType, monitorId);
|
||||
const errors = await syntheticsService.deleteConfigs([
|
||||
{
|
||||
...normalizedMonitor.attributes,
|
||||
id:
|
||||
(normalizedMonitor.attributes as MonitorFields)[ConfigKey.CUSTOM_HEARTBEAT_ID] ||
|
||||
monitorId,
|
||||
},
|
||||
]);
|
||||
|
||||
sendTelemetryEvents(
|
||||
logger,
|
||||
telemetry,
|
||||
formatTelemetryDeleteEvent(
|
||||
monitor,
|
||||
kibanaVersion,
|
||||
new Date().toISOString(),
|
||||
Boolean((normalizedMonitor.attributes as MonitorFields)[ConfigKey.SOURCE_INLINE]),
|
||||
errors
|
||||
)
|
||||
);
|
||||
|
||||
return errors;
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -28,6 +28,7 @@ import {
|
|||
formatTelemetryUpdateEvent,
|
||||
} from '../telemetry/monitor_upgrade_sender';
|
||||
import { formatSecrets, normalizeSecrets } from '../../synthetics_service/utils/secrets';
|
||||
import type { UptimeServerSetup } from '../../legacy_uptime/lib/adapters/framework';
|
||||
|
||||
// Simplify return promise type and type it with runtime_types
|
||||
export const editSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({
|
||||
|
@ -39,12 +40,8 @@ export const editSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({
|
|||
}),
|
||||
body: schema.any(),
|
||||
},
|
||||
handler: async ({
|
||||
request,
|
||||
response,
|
||||
savedObjectsClient,
|
||||
server: { encryptedSavedObjects, syntheticsService, logger, telemetry, kibanaVersion },
|
||||
}): Promise<any> => {
|
||||
handler: async ({ request, response, savedObjectsClient, server }): Promise<any> => {
|
||||
const { encryptedSavedObjects, logger } = server;
|
||||
const encryptedSavedObjectsClient = encryptedSavedObjects.getClient();
|
||||
const monitor = request.body as SyntheticsMonitor;
|
||||
const { monitorId } = request.params;
|
||||
|
@ -85,38 +82,19 @@ export const editSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({
|
|||
};
|
||||
const formattedMonitor = formatSecrets(monitorWithRevision);
|
||||
|
||||
const updatedMonitor: SavedObjectsUpdateResponse<EncryptedSyntheticsMonitor> =
|
||||
const editedMonitorSavedObject: SavedObjectsUpdateResponse<EncryptedSyntheticsMonitor> =
|
||||
await savedObjectsClient.update<MonitorFields>(
|
||||
syntheticsMonitorType,
|
||||
monitorId,
|
||||
monitor.type === 'browser' ? { ...formattedMonitor, urls: '' } : formattedMonitor
|
||||
);
|
||||
|
||||
const errors = await syntheticsService.pushConfigs(
|
||||
[
|
||||
{
|
||||
...editedMonitor,
|
||||
id: updatedMonitor.id,
|
||||
fields: {
|
||||
config_id: updatedMonitor.id,
|
||||
},
|
||||
fields_under_root: true,
|
||||
},
|
||||
],
|
||||
true
|
||||
);
|
||||
|
||||
sendTelemetryEvents(
|
||||
logger,
|
||||
telemetry,
|
||||
formatTelemetryUpdateEvent(
|
||||
updatedMonitor,
|
||||
previousMonitor,
|
||||
kibanaVersion,
|
||||
Boolean((monitor as MonitorFields)[ConfigKey.SOURCE_INLINE]),
|
||||
errors
|
||||
)
|
||||
);
|
||||
const errors = await syncEditedMonitor({
|
||||
server,
|
||||
editedMonitor,
|
||||
editedMonitorSavedObject,
|
||||
previousMonitor,
|
||||
});
|
||||
|
||||
// Return service sync errors in OK response
|
||||
if (errors && errors.length > 0) {
|
||||
|
@ -125,7 +103,7 @@ export const editSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({
|
|||
});
|
||||
}
|
||||
|
||||
return updatedMonitor;
|
||||
return editedMonitorSavedObject;
|
||||
} catch (updateErr) {
|
||||
if (SavedObjectsErrorHelpers.isNotFoundError(updateErr)) {
|
||||
return getMonitorNotFoundResponse(response, monitorId);
|
||||
|
@ -136,3 +114,42 @@ export const editSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({
|
|||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const syncEditedMonitor = async ({
|
||||
editedMonitor,
|
||||
editedMonitorSavedObject,
|
||||
previousMonitor,
|
||||
server,
|
||||
}: {
|
||||
editedMonitor: SyntheticsMonitor;
|
||||
editedMonitorSavedObject: SavedObjectsUpdateResponse<EncryptedSyntheticsMonitor>;
|
||||
previousMonitor: SavedObject<EncryptedSyntheticsMonitor>;
|
||||
server: UptimeServerSetup;
|
||||
}) => {
|
||||
const errors = await server.syntheticsService.pushConfigs([
|
||||
{
|
||||
...editedMonitor,
|
||||
id:
|
||||
(editedMonitor as MonitorFields)[ConfigKey.CUSTOM_HEARTBEAT_ID] ||
|
||||
editedMonitorSavedObject.id,
|
||||
fields: {
|
||||
config_id: editedMonitorSavedObject.id,
|
||||
},
|
||||
fields_under_root: true,
|
||||
},
|
||||
]);
|
||||
|
||||
sendTelemetryEvents(
|
||||
server.logger,
|
||||
server.telemetry,
|
||||
formatTelemetryUpdateEvent(
|
||||
editedMonitorSavedObject,
|
||||
previousMonitor,
|
||||
server.kibanaVersion,
|
||||
Boolean((editedMonitor as MonitorFields)[ConfigKey.SOURCE_INLINE]),
|
||||
errors
|
||||
)
|
||||
);
|
||||
|
||||
return errors;
|
||||
};
|
||||
|
|
|
@ -12,7 +12,6 @@ import { UMRestApiRouteFactory } from '../../legacy_uptime/routes/types';
|
|||
import { API_URLS } from '../../../common/constants';
|
||||
import { syntheticsMonitorType } from '../../legacy_uptime/lib/saved_objects/synthetics_monitor';
|
||||
import { getMonitorNotFoundResponse } from '../synthetics_service/service_errors';
|
||||
import { normalizeSecrets } from '../../synthetics_service/utils/secrets';
|
||||
|
||||
export const getSyntheticsMonitorRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({
|
||||
method: 'GET',
|
||||
|
@ -31,12 +30,11 @@ export const getSyntheticsMonitorRoute: UMRestApiRouteFactory = (libs: UMServerL
|
|||
const { monitorId } = request.params;
|
||||
const encryptedSavedObjectsClient = encryptedSavedObjects.getClient();
|
||||
try {
|
||||
const monitorWithSecrets = await libs.requests.getSyntheticsMonitor({
|
||||
return await libs.requests.getSyntheticsMonitor({
|
||||
monitorId,
|
||||
encryptedSavedObjectsClient,
|
||||
savedObjectsClient,
|
||||
});
|
||||
return normalizeSecrets(monitorWithSecrets);
|
||||
} catch (getErr) {
|
||||
if (SavedObjectsErrorHelpers.isNotFoundError(getErr)) {
|
||||
return getMonitorNotFoundResponse(response, monitorId);
|
||||
|
@ -57,10 +55,11 @@ export const getAllSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({
|
|||
sortField: schema.maybe(schema.string()),
|
||||
sortOrder: schema.maybe(schema.oneOf([schema.literal('desc'), schema.literal('asc')])),
|
||||
search: schema.maybe(schema.string()),
|
||||
query: schema.maybe(schema.string()),
|
||||
}),
|
||||
},
|
||||
handler: async ({ request, savedObjectsClient, server }): Promise<any> => {
|
||||
const { perPage = 50, page, sortField, sortOrder, search } = request.query;
|
||||
const { perPage = 50, page, sortField, sortOrder, search, query } = request.query;
|
||||
// TODO: add query/filtering params
|
||||
const {
|
||||
saved_objects: monitors,
|
||||
|
@ -72,7 +71,7 @@ export const getAllSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({
|
|||
page,
|
||||
sortField,
|
||||
sortOrder,
|
||||
filter: search ? `${syntheticsMonitorType}.attributes.name: ${search}` : '',
|
||||
filter: query || (search ? `${syntheticsMonitorType}.attributes.name: ${search}` : ''),
|
||||
});
|
||||
return {
|
||||
...rest,
|
||||
|
|
|
@ -21,6 +21,7 @@ import {
|
|||
MonitorFields,
|
||||
ResponseBodyIndexPolicy,
|
||||
ScheduleUnit,
|
||||
SourceType,
|
||||
TCPAdvancedFields,
|
||||
TCPFields,
|
||||
TCPSimpleFields,
|
||||
|
@ -29,7 +30,7 @@ import {
|
|||
VerificationMode,
|
||||
ZipUrlTLSFields,
|
||||
} from '../../../common/runtime_types';
|
||||
import { validateMonitor } from '../monitor_cruds/monitor_validation';
|
||||
import { validateMonitor } from './monitor_validation';
|
||||
|
||||
describe('validateMonitor', () => {
|
||||
let testSchedule;
|
||||
|
@ -160,8 +161,12 @@ describe('validateMonitor', () => {
|
|||
testBrowserSimpleFields = {
|
||||
...testZipUrlTLSFields,
|
||||
...testCommonFields,
|
||||
[ConfigKey.MONITOR_SOURCE_TYPE]: SourceType.PROJECT,
|
||||
[ConfigKey.JOURNEY_ID]: '',
|
||||
[ConfigKey.PROJECT_ID]: '',
|
||||
[ConfigKey.METADATA]: testMetaData,
|
||||
[ConfigKey.SOURCE_INLINE]: '',
|
||||
[ConfigKey.SOURCE_PROJECT_CONTENT]: '',
|
||||
[ConfigKey.SOURCE_ZIP_URL]: '',
|
||||
[ConfigKey.SOURCE_ZIP_FOLDER]: '',
|
||||
[ConfigKey.SOURCE_ZIP_USERNAME]: 'test-username',
|
|
@ -10,6 +10,8 @@ import { formatErrors } from '@kbn/securitysolution-io-ts-utils';
|
|||
|
||||
import {
|
||||
BrowserFieldsCodec,
|
||||
ProjectBrowserMonitorCodec,
|
||||
ProjectBrowserMonitor,
|
||||
ConfigKey,
|
||||
DataStream,
|
||||
DataStreamCodec,
|
||||
|
@ -79,3 +81,42 @@ export function validateMonitor(monitorFields: MonitorFields): {
|
|||
|
||||
return { valid: true, reason: '', details: '', payload: monitorFields };
|
||||
}
|
||||
|
||||
export function validateProjectMonitor(
|
||||
monitorFields: ProjectBrowserMonitor,
|
||||
projectId: string
|
||||
): {
|
||||
valid: boolean;
|
||||
reason: string;
|
||||
details: string;
|
||||
payload: object;
|
||||
} {
|
||||
const locationsError =
|
||||
monitorFields.locations && monitorFields.locations.length === 0
|
||||
? 'Invalid value "[]" supplied to field "locations"'
|
||||
: '';
|
||||
// Cast it to ICMPCodec to satisfy typing. During runtime, correct codec will be used to decode.
|
||||
const decodedMonitor = ProjectBrowserMonitorCodec.decode(monitorFields);
|
||||
|
||||
if (isLeft(decodedMonitor)) {
|
||||
return {
|
||||
valid: false,
|
||||
reason: `Failed to save or update monitor. Configuration is not valid`,
|
||||
details: [...formatErrors(decodedMonitor.left), locationsError]
|
||||
.filter((error) => error !== '')
|
||||
.join(' | '),
|
||||
payload: monitorFields,
|
||||
};
|
||||
}
|
||||
|
||||
if (locationsError) {
|
||||
return {
|
||||
valid: false,
|
||||
reason: `Failed to save or update monitor. Configuration is not valid`,
|
||||
details: locationsError,
|
||||
payload: monitorFields,
|
||||
};
|
||||
}
|
||||
|
||||
return { valid: true, reason: '', details: '', payload: monitorFields };
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import { schema } from '@kbn/config-schema';
|
|||
import { v4 as uuidv4 } from 'uuid';
|
||||
import {
|
||||
ConfigKey,
|
||||
MonitorFields,
|
||||
SyntheticsMonitor,
|
||||
SyntheticsMonitorWithSecrets,
|
||||
} from '../../../common/runtime_types';
|
||||
|
@ -41,6 +42,7 @@ export const testNowMonitorRoute: UMRestApiRouteFactory = () => ({
|
|||
syntheticsMonitor.name,
|
||||
monitorId
|
||||
);
|
||||
const normalizedMonitor = normalizeSecrets(monitorWithSecrets);
|
||||
|
||||
const { [ConfigKey.SCHEDULE]: schedule, [ConfigKey.LOCATIONS]: locations } = monitor.attributes;
|
||||
|
||||
|
@ -50,8 +52,10 @@ export const testNowMonitorRoute: UMRestApiRouteFactory = () => ({
|
|||
|
||||
const errors = await syntheticsService.triggerConfigs(request, [
|
||||
{
|
||||
...normalizeSecrets(monitorWithSecrets).attributes,
|
||||
id: monitorId,
|
||||
...normalizedMonitor.attributes,
|
||||
id:
|
||||
(normalizedMonitor.attributes as MonitorFields)[ConfigKey.CUSTOM_HEARTBEAT_ID] ||
|
||||
monitorId,
|
||||
fields_under_root: true,
|
||||
fields: { config_id: monitorId, test_run_id: testRunId },
|
||||
},
|
||||
|
|
|
@ -13,7 +13,9 @@ import {
|
|||
ConfigKey,
|
||||
DataStream,
|
||||
ScheduleUnit,
|
||||
SourceType,
|
||||
} from '../../../common/runtime_types/monitor_management';
|
||||
import { DEFAULT_FIELDS } from '../../../common/constants/monitor_defaults';
|
||||
|
||||
import type { TelemetryEventsSender } from '../../legacy_uptime/lib/telemetry/sender';
|
||||
import { createMockTelemetryEventsSender } from '../../legacy_uptime/lib/telemetry/__mocks__';
|
||||
|
@ -45,6 +47,7 @@ const testConfig: SavedObject<SyntheticsMonitor> = {
|
|||
updated_at: '2011-10-05T14:48:00.000Z',
|
||||
id,
|
||||
attributes: {
|
||||
...DEFAULT_FIELDS[DataStream.BROWSER],
|
||||
[ConfigKey.MONITOR_TYPE]: DataStream.HTTP,
|
||||
[ConfigKey.LOCATIONS]: [
|
||||
{
|
||||
|
@ -111,15 +114,16 @@ describe('monitor upgrade telemetry helpers', () => {
|
|||
});
|
||||
|
||||
it.each([
|
||||
[ConfigKey.SOURCE_INLINE, 'recorder', true, true],
|
||||
[ConfigKey.SOURCE_INLINE, 'inline', false, true],
|
||||
[ConfigKey.SOURCE_ZIP_URL, 'zip', false, false],
|
||||
[ConfigKey.MONITOR_SOURCE_TYPE, SourceType.PROJECT, 'project', false, false],
|
||||
[ConfigKey.SOURCE_INLINE, 'test', 'recorder', true, true],
|
||||
[ConfigKey.SOURCE_INLINE, 'test', 'inline', false, true],
|
||||
[ConfigKey.SOURCE_ZIP_URL, 'test', 'zip', false, false],
|
||||
])(
|
||||
'handles formatting scriptType for browser monitors',
|
||||
(config, scriptType, isRecorder, isInlineScript) => {
|
||||
(config, value, scriptType, isRecorder, isInlineScript) => {
|
||||
const actual = formatTelemetryEvent({
|
||||
monitor: createTestConfig({
|
||||
[config]: 'test',
|
||||
[config]: value,
|
||||
[ConfigKey.METADATA]: {
|
||||
script_source: {
|
||||
is_generated_script: isRecorder,
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
EncryptedSyntheticsMonitor,
|
||||
ConfigKey,
|
||||
ServiceLocationErrors,
|
||||
SourceType,
|
||||
} from '../../../common/runtime_types';
|
||||
import type { MonitorUpdateEvent } from '../../legacy_uptime/lib/telemetry/types';
|
||||
|
||||
|
@ -184,14 +185,19 @@ export function formatTelemetrySyncEvent() {}
|
|||
function getScriptType(
|
||||
attributes: Partial<MonitorFields>,
|
||||
isInlineScript: boolean
|
||||
): 'inline' | 'recorder' | 'zip' | undefined {
|
||||
if (attributes[ConfigKey.SOURCE_ZIP_URL]) {
|
||||
return 'zip';
|
||||
} else if (isInlineScript && attributes[ConfigKey.METADATA]?.script_source?.is_generated_script) {
|
||||
return 'recorder';
|
||||
} else if (isInlineScript) {
|
||||
return 'inline';
|
||||
): MonitorUpdateEvent['scriptType'] | undefined {
|
||||
switch (true) {
|
||||
case Boolean(attributes[ConfigKey.SOURCE_ZIP_URL]):
|
||||
return 'zip';
|
||||
case Boolean(
|
||||
isInlineScript && attributes[ConfigKey.METADATA]?.script_source?.is_generated_script
|
||||
):
|
||||
return 'recorder';
|
||||
case Boolean(isInlineScript):
|
||||
return 'inline';
|
||||
case attributes[ConfigKey.MONITOR_SOURCE_TYPE] === SourceType.PROJECT:
|
||||
return 'project';
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
|
|
@ -7,22 +7,27 @@
|
|||
|
||||
import { Formatter, commonFormatters, objectFormatter, arrayFormatter } from './common';
|
||||
import { BrowserFields, ConfigKey } from '../../../common/runtime_types/monitor_management';
|
||||
import { DEFAULT_BROWSER_ADVANCED_FIELDS } from '../../../common/constants/monitor_defaults';
|
||||
|
||||
export type BrowserFormatMap = Record<keyof BrowserFields, Formatter>;
|
||||
|
||||
const throttlingFormatter: Formatter = (fields) => {
|
||||
if (!fields[ConfigKey.IS_THROTTLING_ENABLED]) return false;
|
||||
|
||||
const getThrottlingValue = (v: string | undefined, suffix: 'd' | 'u' | 'l') =>
|
||||
v !== '' && v !== undefined ? `${v}${suffix}` : null;
|
||||
|
||||
return [
|
||||
getThrottlingValue(fields[ConfigKey.DOWNLOAD_SPEED], 'd'),
|
||||
getThrottlingValue(fields[ConfigKey.UPLOAD_SPEED], 'u'),
|
||||
getThrottlingValue(fields[ConfigKey.LATENCY], 'l'),
|
||||
]
|
||||
.filter((v) => v !== null)
|
||||
.join('/');
|
||||
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
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
export const browserFormatters: BrowserFormatMap = {
|
||||
|
@ -36,6 +41,7 @@ export const browserFormatters: BrowserFormatMap = {
|
|||
[ConfigKey.SOURCE_ZIP_PASSWORD]: null,
|
||||
[ConfigKey.SOURCE_ZIP_FOLDER]: null,
|
||||
[ConfigKey.SOURCE_ZIP_PROXY_URL]: null,
|
||||
[ConfigKey.SOURCE_PROJECT_CONTENT]: null,
|
||||
[ConfigKey.SOURCE_INLINE]: null,
|
||||
[ConfigKey.PARAMS]: null,
|
||||
[ConfigKey.SCREENSHOTS]: null,
|
||||
|
@ -54,5 +60,10 @@ export const browserFormatters: BrowserFormatMap = {
|
|||
[ConfigKey.JOURNEY_FILTERS_TAGS]: (fields) =>
|
||||
arrayFormatter(fields[ConfigKey.JOURNEY_FILTERS_TAGS]),
|
||||
[ConfigKey.IGNORE_HTTPS_ERRORS]: null,
|
||||
[ConfigKey.JOURNEY_ID]: null,
|
||||
[ConfigKey.PROJECT_ID]: null,
|
||||
[ConfigKey.PLAYWRIGHT_OPTIONS]: null,
|
||||
[ConfigKey.CUSTOM_HEARTBEAT_ID]: null,
|
||||
[ConfigKey.ORIGINAL_SPACE]: null,
|
||||
...commonFormatters,
|
||||
};
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { CommonFields, ConfigKey, MonitorFields } from '../../../common/runtime_types';
|
||||
import { CommonFields, ConfigKey, MonitorFields, SourceType } from '../../../common/runtime_types';
|
||||
|
||||
export type FormattedValue = boolean | string | string[] | Record<string, string> | null;
|
||||
export type FormattedValue = boolean | string | string[] | Record<string, unknown> | null;
|
||||
|
||||
export type Formatter = null | ((fields: Partial<MonitorFields>) => FormattedValue);
|
||||
|
||||
|
@ -26,6 +26,8 @@ export const commonFormatters: CommonFormatMap = {
|
|||
[ConfigKey.TIMEOUT]: (fields) => secondsToCronFormatter(fields[ConfigKey.TIMEOUT] || undefined),
|
||||
[ConfigKey.NAMESPACE]: null,
|
||||
[ConfigKey.REVISION]: null,
|
||||
[ConfigKey.MONITOR_SOURCE_TYPE]: (fields) =>
|
||||
fields[ConfigKey.MONITOR_SOURCE_TYPE] || SourceType.UI,
|
||||
};
|
||||
|
||||
export const arrayFormatter = (value: string[] = []) => (value.length ? value : null);
|
||||
|
|
|
@ -116,7 +116,11 @@ describe('formatMonitorConfig', () => {
|
|||
screenshots: 'on',
|
||||
'source.inline.script':
|
||||
"step('Go to https://www.google.com/', async () => {\n await page.goto('https://www.google.com/');\n});",
|
||||
throttling: '5d/3u/20l',
|
||||
throttling: {
|
||||
download: 5,
|
||||
latency: 20,
|
||||
upload: 3,
|
||||
},
|
||||
timeout: '16s',
|
||||
type: 'browser',
|
||||
synthetics_args: ['--hasTouch true'],
|
||||
|
|
|
@ -10,12 +10,15 @@ import { ConfigKey, MonitorFields } from '../../../common/runtime_types';
|
|||
import { formatters } from '.';
|
||||
|
||||
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,
|
||||
'secrets',
|
||||
];
|
||||
|
||||
|
|
|
@ -0,0 +1,255 @@
|
|||
/*
|
||||
* 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 {
|
||||
DataStream,
|
||||
ScreenshotOption,
|
||||
Locations,
|
||||
LocationStatus,
|
||||
ProjectBrowserMonitor,
|
||||
} from '../../../common/runtime_types';
|
||||
import { DEFAULT_FIELDS } from '../../../common/constants/monitor_defaults';
|
||||
import { normalizeProjectMonitors } from './browser';
|
||||
|
||||
describe('browser normalizers', () => {
|
||||
describe('normalize push monitors', () => {
|
||||
const playwrightOptions = {
|
||||
headless: true,
|
||||
};
|
||||
const params = {
|
||||
url: 'test-url',
|
||||
};
|
||||
const projectId = 'test-project-id';
|
||||
const locations: Locations = [
|
||||
{
|
||||
id: 'us_central',
|
||||
label: 'Test Location',
|
||||
geo: { lat: 33.333, lon: 73.333 },
|
||||
url: 'test-url',
|
||||
isServiceManaged: true,
|
||||
status: LocationStatus.GA,
|
||||
},
|
||||
{
|
||||
id: 'us_east',
|
||||
label: 'Test Location',
|
||||
geo: { lat: 33.333, lon: 73.333 },
|
||||
url: 'test-url',
|
||||
isServiceManaged: true,
|
||||
status: LocationStatus.GA,
|
||||
},
|
||||
];
|
||||
const monitors: ProjectBrowserMonitor[] = [
|
||||
{
|
||||
id: 'test-id-1',
|
||||
screenshot: ScreenshotOption.OFF,
|
||||
name: 'test-name-1',
|
||||
content: 'test content 1',
|
||||
schedule: 3,
|
||||
throttling: {
|
||||
latency: 20,
|
||||
upload: 10,
|
||||
download: 5,
|
||||
},
|
||||
locations: ['us_central'],
|
||||
tags: ['tag1', 'tag2'],
|
||||
ignoreHTTPSErrors: true,
|
||||
apmServiceName: 'cart-service',
|
||||
},
|
||||
{
|
||||
id: 'test-id-2',
|
||||
screenshot: ScreenshotOption.ON,
|
||||
name: 'test-name-2',
|
||||
content: 'test content 2',
|
||||
schedule: 10,
|
||||
throttling: {
|
||||
latency: 18,
|
||||
upload: 15,
|
||||
download: 10,
|
||||
},
|
||||
params: {},
|
||||
playwrightOptions: {},
|
||||
locations: ['us_central', 'us_east'],
|
||||
tags: ['tag3', 'tag4'],
|
||||
ignoreHTTPSErrors: false,
|
||||
apmServiceName: 'bean-service',
|
||||
},
|
||||
{
|
||||
id: 'test-id-3',
|
||||
screenshot: ScreenshotOption.ON,
|
||||
name: 'test-name-3',
|
||||
content: 'test content 3',
|
||||
schedule: 10,
|
||||
throttling: {
|
||||
latency: 18,
|
||||
upload: 15,
|
||||
download: 10,
|
||||
},
|
||||
params,
|
||||
playwrightOptions,
|
||||
locations: ['us_central', 'us_east'],
|
||||
tags: ['tag3', 'tag4'],
|
||||
ignoreHTTPSErrors: false,
|
||||
apmServiceName: 'bean-service',
|
||||
},
|
||||
];
|
||||
|
||||
it('properly normalizes browser monitor', () => {
|
||||
const actual = normalizeProjectMonitors({
|
||||
locations,
|
||||
monitors,
|
||||
projectId,
|
||||
namespace: 'test-space',
|
||||
});
|
||||
expect(actual).toEqual([
|
||||
{
|
||||
...DEFAULT_FIELDS[DataStream.BROWSER],
|
||||
journey_id: 'test-id-1',
|
||||
ignore_https_errors: true,
|
||||
'monitor.origin': 'project',
|
||||
locations: [
|
||||
{
|
||||
geo: {
|
||||
lat: 33.333,
|
||||
lon: 73.333,
|
||||
},
|
||||
id: 'us_central',
|
||||
isServiceManaged: true,
|
||||
label: 'Test Location',
|
||||
url: 'test-url',
|
||||
status: 'ga',
|
||||
},
|
||||
],
|
||||
name: 'test-name-1',
|
||||
schedule: {
|
||||
number: '3',
|
||||
unit: 'm',
|
||||
},
|
||||
screenshots: 'off',
|
||||
'service.name': 'cart-service',
|
||||
'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,
|
||||
namespace: 'test-space',
|
||||
original_space: 'test-space',
|
||||
custom_heartbeat_id: 'test-id-1-test-project-id-test-space',
|
||||
timeout: null,
|
||||
},
|
||||
{
|
||||
...DEFAULT_FIELDS[DataStream.BROWSER],
|
||||
journey_id: 'test-id-2',
|
||||
ignore_https_errors: false,
|
||||
'monitor.origin': 'project',
|
||||
locations: [
|
||||
{
|
||||
geo: {
|
||||
lat: 33.333,
|
||||
lon: 73.333,
|
||||
},
|
||||
id: 'us_central',
|
||||
isServiceManaged: true,
|
||||
label: 'Test Location',
|
||||
url: 'test-url',
|
||||
status: 'ga',
|
||||
},
|
||||
{
|
||||
geo: {
|
||||
lat: 33.333,
|
||||
lon: 73.333,
|
||||
},
|
||||
id: 'us_east',
|
||||
isServiceManaged: true,
|
||||
label: 'Test Location',
|
||||
url: 'test-url',
|
||||
status: 'ga',
|
||||
},
|
||||
],
|
||||
name: 'test-name-2',
|
||||
params: '',
|
||||
playwright_options: '',
|
||||
schedule: {
|
||||
number: '10',
|
||||
unit: 'm',
|
||||
},
|
||||
screenshots: 'on',
|
||||
'service.name': 'bean-service',
|
||||
'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',
|
||||
original_space: 'test-space',
|
||||
custom_heartbeat_id: 'test-id-2-test-project-id-test-space',
|
||||
timeout: null,
|
||||
},
|
||||
{
|
||||
...DEFAULT_FIELDS[DataStream.BROWSER],
|
||||
journey_id: 'test-id-3',
|
||||
ignore_https_errors: false,
|
||||
'monitor.origin': 'project',
|
||||
locations: [
|
||||
{
|
||||
geo: {
|
||||
lat: 33.333,
|
||||
lon: 73.333,
|
||||
},
|
||||
id: 'us_central',
|
||||
isServiceManaged: true,
|
||||
label: 'Test Location',
|
||||
url: 'test-url',
|
||||
status: 'ga',
|
||||
},
|
||||
{
|
||||
geo: {
|
||||
lat: 33.333,
|
||||
lon: 73.333,
|
||||
},
|
||||
id: 'us_east',
|
||||
isServiceManaged: true,
|
||||
label: 'Test Location',
|
||||
url: 'test-url',
|
||||
status: 'ga',
|
||||
},
|
||||
],
|
||||
name: 'test-name-3',
|
||||
params: JSON.stringify(params),
|
||||
playwright_options: JSON.stringify(playwrightOptions),
|
||||
schedule: {
|
||||
number: '10',
|
||||
unit: 'm',
|
||||
},
|
||||
screenshots: 'on',
|
||||
'service.name': 'bean-service',
|
||||
'source.project.content': 'test content 3',
|
||||
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',
|
||||
original_space: 'test-space',
|
||||
custom_heartbeat_id: 'test-id-3-test-project-id-test-space',
|
||||
timeout: null,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* 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 { DEFAULT_FIELDS } from '../../../common/constants/monitor_defaults';
|
||||
|
||||
import {
|
||||
BrowserFields,
|
||||
ConfigKey,
|
||||
DataStream,
|
||||
Locations,
|
||||
ProjectBrowserMonitor,
|
||||
ScheduleUnit,
|
||||
SourceType,
|
||||
} from '../../../common/runtime_types/monitor_management';
|
||||
|
||||
/* Represents all of the push-monitor related fields that need to be
|
||||
* normalized. Excludes fields that we do not support for push monitors
|
||||
* This type ensures that contributors remember to add normalizers for push
|
||||
* monitors where appropriate when new keys are added to browser montiors */
|
||||
type NormalizedPublicFields = Omit<
|
||||
BrowserFields,
|
||||
| ConfigKey.METADATA
|
||||
| ConfigKey.SOURCE_INLINE
|
||||
| ConfigKey.SOURCE_ZIP_URL
|
||||
| ConfigKey.SOURCE_ZIP_USERNAME
|
||||
| ConfigKey.SOURCE_ZIP_PASSWORD
|
||||
| ConfigKey.SOURCE_ZIP_FOLDER
|
||||
| ConfigKey.SOURCE_ZIP_PROXY_URL
|
||||
| ConfigKey.ZIP_URL_TLS_CERTIFICATE_AUTHORITIES
|
||||
| ConfigKey.ZIP_URL_TLS_CERTIFICATE
|
||||
| ConfigKey.ZIP_URL_TLS_KEY
|
||||
| ConfigKey.ZIP_URL_TLS_KEY_PASSPHRASE
|
||||
| ConfigKey.ZIP_URL_TLS_VERIFICATION_MODE
|
||||
| ConfigKey.ZIP_URL_TLS_VERSION
|
||||
| ConfigKey.JOURNEY_FILTERS_TAGS
|
||||
| ConfigKey.SYNTHETICS_ARGS
|
||||
| ConfigKey.PORT
|
||||
| ConfigKey.URLS
|
||||
>;
|
||||
|
||||
export const normalizeProjectMonitor = ({
|
||||
locations = [],
|
||||
monitor,
|
||||
projectId,
|
||||
namespace,
|
||||
}: {
|
||||
locations: Locations;
|
||||
monitor: ProjectBrowserMonitor;
|
||||
projectId: string;
|
||||
namespace: string;
|
||||
}): BrowserFields => {
|
||||
const defaultFields = DEFAULT_FIELDS[DataStream.BROWSER];
|
||||
const normalizedFields: NormalizedPublicFields = {
|
||||
[ConfigKey.MONITOR_TYPE]: DataStream.BROWSER,
|
||||
[ConfigKey.MONITOR_SOURCE_TYPE]: SourceType.PROJECT,
|
||||
[ConfigKey.NAME]: monitor.name || '',
|
||||
[ConfigKey.SCHEDULE]: {
|
||||
number: `${monitor.schedule}`,
|
||||
unit: ScheduleUnit.MINUTES,
|
||||
},
|
||||
[ConfigKey.PROJECT_ID]: projectId || defaultFields[ConfigKey.PROJECT_ID],
|
||||
[ConfigKey.JOURNEY_ID]: monitor.id || defaultFields[ConfigKey.JOURNEY_ID],
|
||||
[ConfigKey.SOURCE_PROJECT_CONTENT]:
|
||||
monitor.content || defaultFields[ConfigKey.SOURCE_PROJECT_CONTENT],
|
||||
[ConfigKey.LOCATIONS]: monitor.locations
|
||||
?.map((key) => {
|
||||
return locations.find((location) => location.id === key);
|
||||
})
|
||||
.filter((location) => location !== undefined) as BrowserFields[ConfigKey.LOCATIONS],
|
||||
[ConfigKey.THROTTLING_CONFIG]: monitor.throttling
|
||||
? `${monitor.throttling.download}d/${monitor.throttling.upload}u/${monitor.throttling.latency}l`
|
||||
: defaultFields[ConfigKey.THROTTLING_CONFIG],
|
||||
[ConfigKey.DOWNLOAD_SPEED]: `${
|
||||
monitor.throttling?.download || defaultFields[ConfigKey.DOWNLOAD_SPEED]
|
||||
}`,
|
||||
[ConfigKey.UPLOAD_SPEED]: `${
|
||||
monitor.throttling?.upload || defaultFields[ConfigKey.UPLOAD_SPEED]
|
||||
}`,
|
||||
[ConfigKey.IS_THROTTLING_ENABLED]:
|
||||
Boolean(monitor.throttling) || defaultFields[ConfigKey.IS_THROTTLING_ENABLED],
|
||||
[ConfigKey.LATENCY]: `${monitor.throttling?.latency || defaultFields[ConfigKey.LATENCY]}`,
|
||||
[ConfigKey.APM_SERVICE_NAME]:
|
||||
monitor.apmServiceName || defaultFields[ConfigKey.APM_SERVICE_NAME],
|
||||
[ConfigKey.IGNORE_HTTPS_ERRORS]:
|
||||
monitor.ignoreHTTPSErrors || defaultFields[ConfigKey.IGNORE_HTTPS_ERRORS],
|
||||
[ConfigKey.SCREENSHOTS]: monitor.screenshot || defaultFields[ConfigKey.SCREENSHOTS],
|
||||
[ConfigKey.TAGS]: monitor.tags || defaultFields[ConfigKey.TAGS],
|
||||
[ConfigKey.PLAYWRIGHT_OPTIONS]: Object.keys(monitor.playwrightOptions || {}).length
|
||||
? JSON.stringify(monitor.playwrightOptions)
|
||||
: defaultFields[ConfigKey.PLAYWRIGHT_OPTIONS],
|
||||
[ConfigKey.PARAMS]: Object.keys(monitor.params || {}).length
|
||||
? JSON.stringify(monitor.params)
|
||||
: defaultFields[ConfigKey.PARAMS],
|
||||
[ConfigKey.JOURNEY_FILTERS_MATCH]:
|
||||
monitor.filter?.match || defaultFields[ConfigKey.JOURNEY_FILTERS_MATCH],
|
||||
[ConfigKey.NAMESPACE]: namespace || defaultFields[ConfigKey.NAMESPACE],
|
||||
[ConfigKey.ORIGINAL_SPACE]: namespace || defaultFields[ConfigKey.ORIGINAL_SPACE],
|
||||
[ConfigKey.CUSTOM_HEARTBEAT_ID]: `${monitor.id}-${projectId}-${namespace}`,
|
||||
[ConfigKey.TIMEOUT]: null,
|
||||
[ConfigKey.ENABLED]: monitor.enabled || defaultFields[ConfigKey.ENABLED],
|
||||
};
|
||||
return {
|
||||
...DEFAULT_FIELDS[DataStream.BROWSER],
|
||||
...normalizedFields,
|
||||
};
|
||||
};
|
||||
|
||||
export const normalizeProjectMonitors = ({
|
||||
locations = [],
|
||||
monitors = [],
|
||||
projectId,
|
||||
namespace,
|
||||
}: {
|
||||
locations: Locations;
|
||||
monitors: ProjectBrowserMonitor[];
|
||||
projectId: string;
|
||||
namespace: string;
|
||||
}) => {
|
||||
return monitors.map((monitor) => {
|
||||
return normalizeProjectMonitor({ monitor, locations, projectId, namespace });
|
||||
});
|
||||
};
|
|
@ -0,0 +1,302 @@
|
|||
/*
|
||||
* 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 { isEqual } from 'lodash';
|
||||
import {
|
||||
SavedObjectsUpdateResponse,
|
||||
SavedObjectsClientContract,
|
||||
SavedObjectsFindResult,
|
||||
} from '@kbn/core/server';
|
||||
import { EncryptedSavedObjectsClient } from '@kbn/encrypted-saved-objects-plugin/server';
|
||||
import {
|
||||
BrowserFields,
|
||||
ConfigKey,
|
||||
MonitorFields,
|
||||
SyntheticsMonitorWithSecrets,
|
||||
EncryptedSyntheticsMonitor,
|
||||
ServiceLocationErrors,
|
||||
ProjectBrowserMonitor,
|
||||
Locations,
|
||||
} from '../../common/runtime_types';
|
||||
import {
|
||||
syntheticsMonitorType,
|
||||
syntheticsMonitor,
|
||||
} from '../legacy_uptime/lib/saved_objects/synthetics_monitor';
|
||||
import { normalizeProjectMonitor } from './normalizers/browser';
|
||||
import { formatSecrets, normalizeSecrets } from './utils/secrets';
|
||||
import { syncNewMonitor } from '../routes/monitor_cruds/add_monitor';
|
||||
import { syncEditedMonitor } from '../routes/monitor_cruds/edit_monitor';
|
||||
import { deleteMonitor } from '../routes/monitor_cruds/delete_monitor';
|
||||
import { validateProjectMonitor } from '../routes/monitor_cruds/monitor_validation';
|
||||
import type { UptimeServerSetup } from '../legacy_uptime/lib/adapters/framework';
|
||||
|
||||
interface StaleMonitor {
|
||||
stale: boolean;
|
||||
journeyId: string;
|
||||
savedObjectId: string;
|
||||
}
|
||||
type StaleMonitorMap = Record<string, StaleMonitor>;
|
||||
type FailedMonitors = Array<{ id: string; reason: string; details: string; payload?: object }>;
|
||||
|
||||
export class ProjectMonitorFormatter {
|
||||
private projectId: string;
|
||||
private spaceId: string;
|
||||
private keepStale: boolean;
|
||||
private locations: Locations;
|
||||
private savedObjectsClient: SavedObjectsClientContract;
|
||||
private encryptedSavedObjectsClient: EncryptedSavedObjectsClient;
|
||||
private staleMonitorsMap: StaleMonitorMap = {};
|
||||
private monitors: ProjectBrowserMonitor[] = [];
|
||||
public createdMonitors: string[] = [];
|
||||
public deletedMonitors: string[] = [];
|
||||
public updatedMonitors: string[] = [];
|
||||
public staleMonitors: string[] = [];
|
||||
public failedMonitors: FailedMonitors = [];
|
||||
public failedStaleMonitors: FailedMonitors = [];
|
||||
private server: UptimeServerSetup;
|
||||
private projectFilter: string;
|
||||
|
||||
constructor({
|
||||
locations,
|
||||
keepStale,
|
||||
savedObjectsClient,
|
||||
encryptedSavedObjectsClient,
|
||||
projectId,
|
||||
spaceId,
|
||||
monitors,
|
||||
server,
|
||||
}: {
|
||||
locations: Locations;
|
||||
keepStale: boolean;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
encryptedSavedObjectsClient: EncryptedSavedObjectsClient;
|
||||
projectId: string;
|
||||
spaceId: string;
|
||||
monitors: ProjectBrowserMonitor[];
|
||||
server: UptimeServerSetup;
|
||||
}) {
|
||||
this.projectId = projectId;
|
||||
this.spaceId = spaceId;
|
||||
this.locations = locations;
|
||||
this.keepStale = keepStale;
|
||||
this.savedObjectsClient = savedObjectsClient;
|
||||
this.encryptedSavedObjectsClient = encryptedSavedObjectsClient;
|
||||
this.monitors = monitors;
|
||||
this.server = server;
|
||||
this.projectFilter = `${syntheticsMonitorType}.attributes.${ConfigKey.PROJECT_ID}: "${this.projectId}"`;
|
||||
}
|
||||
|
||||
public configureAllProjectMonitors = async () => {
|
||||
this.staleMonitorsMap = await this.getAllProjectMonitorsForProject();
|
||||
await Promise.all(
|
||||
this.monitors.map((monitor) =>
|
||||
this.configureProjectMonitor({
|
||||
monitor,
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
await this.handleStaleMonitors();
|
||||
};
|
||||
|
||||
private configureProjectMonitor = async ({ monitor }: { monitor: ProjectBrowserMonitor }) => {
|
||||
try {
|
||||
// check to see if monitor already exists
|
||||
const normalizedMonitor = normalizeProjectMonitor({
|
||||
locations: this.locations,
|
||||
monitor,
|
||||
projectId: this.projectId,
|
||||
namespace: this.spaceId,
|
||||
});
|
||||
|
||||
const validationResult = validateProjectMonitor(monitor, this.projectId);
|
||||
|
||||
if (!validationResult.valid) {
|
||||
const { reason: message, details, payload } = validationResult;
|
||||
this.failedMonitors.push({
|
||||
id: monitor.id,
|
||||
reason: message,
|
||||
details,
|
||||
payload,
|
||||
});
|
||||
if (this.staleMonitorsMap[monitor.id]) {
|
||||
this.staleMonitorsMap[monitor.id].stale = false;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const previousMonitor = await this.getExistingMonitor(monitor.id);
|
||||
|
||||
if (previousMonitor) {
|
||||
await this.updateMonitor(previousMonitor, normalizedMonitor);
|
||||
this.updatedMonitors.push(monitor.id);
|
||||
if (this.staleMonitorsMap[monitor.id]) {
|
||||
this.staleMonitorsMap[monitor.id].stale = false;
|
||||
}
|
||||
} else {
|
||||
const newMonitor = await this.savedObjectsClient.create<EncryptedSyntheticsMonitor>(
|
||||
syntheticsMonitorType,
|
||||
formatSecrets({
|
||||
...normalizedMonitor,
|
||||
revision: 1,
|
||||
})
|
||||
);
|
||||
await syncNewMonitor({
|
||||
server: this.server,
|
||||
monitor: normalizedMonitor,
|
||||
monitorSavedObject: newMonitor,
|
||||
});
|
||||
this.createdMonitors.push(monitor.id);
|
||||
}
|
||||
} catch (e) {
|
||||
this.server.logger.error(e);
|
||||
this.failedMonitors.push({
|
||||
id: monitor.id,
|
||||
reason: 'Failed to create or update monitor',
|
||||
details: e.message,
|
||||
payload: monitor,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private getAllProjectMonitorsForProject = async (): Promise<StaleMonitorMap> => {
|
||||
const staleMonitors: StaleMonitorMap = {};
|
||||
let page = 1;
|
||||
let totalMonitors = 0;
|
||||
do {
|
||||
const { total, saved_objects: savedObjects } = await this.getProjectMonitorsForProject(page);
|
||||
savedObjects.forEach((savedObject) => {
|
||||
const journeyId = (savedObject.attributes as BrowserFields)[ConfigKey.JOURNEY_ID];
|
||||
if (journeyId) {
|
||||
staleMonitors[journeyId] = {
|
||||
stale: true,
|
||||
savedObjectId: savedObject.id,
|
||||
journeyId,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
page++;
|
||||
totalMonitors = total;
|
||||
} while (Object.keys(staleMonitors).length < totalMonitors);
|
||||
return staleMonitors;
|
||||
};
|
||||
|
||||
private getProjectMonitorsForProject = async (page: number) => {
|
||||
return await this.savedObjectsClient.find<EncryptedSyntheticsMonitor>({
|
||||
type: syntheticsMonitorType,
|
||||
page,
|
||||
perPage: 500,
|
||||
filter: this.projectFilter,
|
||||
});
|
||||
};
|
||||
|
||||
private getExistingMonitor = async (
|
||||
journeyId: string
|
||||
): Promise<SavedObjectsFindResult<EncryptedSyntheticsMonitor>> => {
|
||||
const filter = `${this.projectFilter} AND ${syntheticsMonitorType}.attributes.${ConfigKey.JOURNEY_ID}: "${journeyId}"`;
|
||||
const { saved_objects: savedObjects } =
|
||||
await this.savedObjectsClient.find<EncryptedSyntheticsMonitor>({
|
||||
type: syntheticsMonitorType,
|
||||
perPage: 1,
|
||||
filter,
|
||||
});
|
||||
return savedObjects?.[0];
|
||||
};
|
||||
|
||||
private updateMonitor = async (
|
||||
previousMonitor: SavedObjectsFindResult<EncryptedSyntheticsMonitor>,
|
||||
normalizedMonitor: BrowserFields
|
||||
): Promise<{
|
||||
editedMonitor: SavedObjectsUpdateResponse<EncryptedSyntheticsMonitor>;
|
||||
errors: ServiceLocationErrors;
|
||||
}> => {
|
||||
const decryptedPreviousMonitor =
|
||||
await this.encryptedSavedObjectsClient.getDecryptedAsInternalUser<SyntheticsMonitorWithSecrets>(
|
||||
syntheticsMonitor.name,
|
||||
previousMonitor.id,
|
||||
{
|
||||
namespace: previousMonitor.namespaces?.[0],
|
||||
}
|
||||
);
|
||||
const {
|
||||
attributes: { [ConfigKey.REVISION]: _, ...normalizedPreviousMonitorAttributes },
|
||||
} = normalizeSecrets(decryptedPreviousMonitor);
|
||||
const hasMonitorBeenEdited = !isEqual(normalizedMonitor, normalizedPreviousMonitorAttributes);
|
||||
const monitorWithRevision = formatSecrets({
|
||||
...normalizedMonitor,
|
||||
revision: hasMonitorBeenEdited
|
||||
? (previousMonitor.attributes[ConfigKey.REVISION] || 0) + 1
|
||||
: previousMonitor.attributes[ConfigKey.REVISION],
|
||||
});
|
||||
const editedMonitor: SavedObjectsUpdateResponse<EncryptedSyntheticsMonitor> =
|
||||
await this.savedObjectsClient.update<MonitorFields>(
|
||||
syntheticsMonitorType,
|
||||
previousMonitor.id,
|
||||
{
|
||||
...monitorWithRevision,
|
||||
urls: '',
|
||||
}
|
||||
);
|
||||
|
||||
if (hasMonitorBeenEdited) {
|
||||
syncEditedMonitor({
|
||||
editedMonitor: normalizedMonitor,
|
||||
editedMonitorSavedObject: editedMonitor,
|
||||
previousMonitor,
|
||||
server: this.server,
|
||||
});
|
||||
}
|
||||
|
||||
return { editedMonitor, errors: [] };
|
||||
};
|
||||
|
||||
private handleStaleMonitors = async () => {
|
||||
try {
|
||||
const staleMonitorsData = Object.values(this.staleMonitorsMap).filter(
|
||||
(monitor) => monitor.stale === true
|
||||
);
|
||||
await Promise.all(
|
||||
staleMonitorsData.map((monitor) => {
|
||||
if (!this.keepStale) {
|
||||
return this.deleteStaleMonitor({
|
||||
monitorId: monitor.savedObjectId,
|
||||
journeyId: monitor.journeyId,
|
||||
});
|
||||
} else {
|
||||
this.staleMonitors.push(monitor.journeyId);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
);
|
||||
} catch (e) {
|
||||
this.server.logger.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
private deleteStaleMonitor = async ({
|
||||
monitorId,
|
||||
journeyId,
|
||||
}: {
|
||||
monitorId: string;
|
||||
journeyId: string;
|
||||
}) => {
|
||||
try {
|
||||
await deleteMonitor({
|
||||
savedObjectsClient: this.savedObjectsClient,
|
||||
server: this.server,
|
||||
monitorId,
|
||||
});
|
||||
this.deletedMonitors.push(journeyId);
|
||||
} catch (e) {
|
||||
this.failedStaleMonitors.push({
|
||||
id: monitorId,
|
||||
reason: 'Failed to delete stale monitor',
|
||||
details: e.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
|
@ -427,12 +427,16 @@ export class SyntheticsService {
|
|||
});
|
||||
}
|
||||
|
||||
return (monitors ?? []).map((monitor) => ({
|
||||
...normalizeSecrets(monitor).attributes,
|
||||
id: monitor.id,
|
||||
fields_under_root: true,
|
||||
fields: { config_id: monitor.id },
|
||||
}));
|
||||
return (monitors ?? []).map((monitor) => {
|
||||
const attributes = monitor.attributes as unknown as MonitorFields;
|
||||
const id = attributes[ConfigKey.CUSTOM_HEARTBEAT_ID] || monitor.id;
|
||||
return {
|
||||
...normalizeSecrets(monitor).attributes,
|
||||
id, // heartbeat id
|
||||
fields_under_root: true,
|
||||
fields: { config_id: monitor.id }, // monitor saved object id
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
formatConfigs(configs: SyntheticsMonitorWithId[]) {
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export * from './secrets';
|
|
@ -8,9 +8,11 @@ import { omit, pick } from 'lodash';
|
|||
import { SavedObject } from '@kbn/core/server';
|
||||
import { secretKeys } from '../../../common/constants/monitor_management';
|
||||
import {
|
||||
ConfigKey,
|
||||
SyntheticsMonitor,
|
||||
SyntheticsMonitorWithSecrets,
|
||||
} from '../../../common/runtime_types/monitor_management';
|
||||
import { DEFAULT_FIELDS } from '../../../common/constants/monitor_defaults';
|
||||
|
||||
export function formatSecrets(monitor: SyntheticsMonitor): SyntheticsMonitorWithSecrets {
|
||||
const monitorWithoutSecrets = omit(monitor, secretKeys) as SyntheticsMonitorWithSecrets;
|
||||
|
@ -25,11 +27,15 @@ export function formatSecrets(monitor: SyntheticsMonitor): SyntheticsMonitorWith
|
|||
export function normalizeSecrets(
|
||||
monitor: SavedObject<SyntheticsMonitorWithSecrets>
|
||||
): SavedObject<SyntheticsMonitor> {
|
||||
return {
|
||||
const defaultFields = DEFAULT_FIELDS[monitor.attributes[ConfigKey.MONITOR_TYPE]];
|
||||
const normalizedMonitor = {
|
||||
...monitor,
|
||||
attributes: {
|
||||
...defaultFields,
|
||||
...monitor.attributes,
|
||||
...JSON.parse(monitor.attributes.secrets || ''),
|
||||
},
|
||||
};
|
||||
delete normalizedMonitor.attributes.secrets;
|
||||
return normalizedMonitor;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,578 @@
|
|||
/*
|
||||
* 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 uuid from 'uuid';
|
||||
import expect from '@kbn/expect';
|
||||
import { ConfigKey, ProjectMonitorsRequest } from '@kbn/synthetics-plugin/common/runtime_types';
|
||||
import { API_URLS } from '@kbn/synthetics-plugin/common/constants';
|
||||
import { syntheticsMonitorType } from '@kbn/synthetics-plugin/server/legacy_uptime/lib/saved_objects/synthetics_monitor';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
import { getFixtureJson } from './helper/get_fixture_json';
|
||||
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
describe('[PUT] /api/uptime/service/monitors', () => {
|
||||
const supertest = getService('supertest');
|
||||
const security = getService('security');
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
|
||||
let projectMonitors: ProjectMonitorsRequest;
|
||||
|
||||
const setUniqueIds = (request: ProjectMonitorsRequest) => {
|
||||
return {
|
||||
...request,
|
||||
monitors: request.monitors.map((monitor) => ({ ...monitor, id: uuid.v4() })),
|
||||
};
|
||||
};
|
||||
|
||||
const deleteMonitor = async (
|
||||
journeyId: string,
|
||||
projectId: string,
|
||||
space: string = 'default',
|
||||
username: string = '',
|
||||
password: string = ''
|
||||
) => {
|
||||
try {
|
||||
const response = await supertest
|
||||
.get(`/s/${space}${API_URLS.SYNTHETICS_MONITORS}`)
|
||||
.auth(username, password)
|
||||
.query({
|
||||
query: `${syntheticsMonitorType}.attributes.journey_id: "${journeyId}" AND ${syntheticsMonitorType}.attributes.project_id: "${projectId}"`,
|
||||
})
|
||||
.set('kbn-xsrf', 'true')
|
||||
.expect(200);
|
||||
const { monitors } = response.body;
|
||||
if (monitors[0]?.id) {
|
||||
await supertest
|
||||
.delete(`/s/${space}${API_URLS.SYNTHETICS_MONITORS}/${monitors[0].id}`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(projectMonitors)
|
||||
.expect(200);
|
||||
}
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
projectMonitors = setUniqueIds(getFixtureJson('project_browser_monitor'));
|
||||
});
|
||||
|
||||
it('project monitors - returns a list of successfully created monitors', async () => {
|
||||
try {
|
||||
const apiResponse = await supertest
|
||||
.put(API_URLS.SYNTHETICS_MONITORS_PROJECT)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(projectMonitors);
|
||||
|
||||
expect(apiResponse.body.updatedMonitors).eql([]);
|
||||
expect(apiResponse.body.failedMonitors).eql([]);
|
||||
expect(apiResponse.body.createdMonitors).eql(
|
||||
projectMonitors.monitors.map((monitor) => monitor.id)
|
||||
);
|
||||
} finally {
|
||||
await Promise.all([
|
||||
projectMonitors.monitors.map((monitor) => {
|
||||
return deleteMonitor(monitor.id, projectMonitors.project);
|
||||
}),
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
it('project monitors - returns a list of successfully updated monitors', async () => {
|
||||
try {
|
||||
await supertest
|
||||
.put(API_URLS.SYNTHETICS_MONITORS_PROJECT)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(projectMonitors);
|
||||
|
||||
const apiResponse = await supertest
|
||||
.put(API_URLS.SYNTHETICS_MONITORS_PROJECT)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(projectMonitors);
|
||||
|
||||
expect(apiResponse.body.createdMonitors).eql([]);
|
||||
expect(apiResponse.body.failedMonitors).eql([]);
|
||||
expect(apiResponse.body.updatedMonitors).eql(
|
||||
projectMonitors.monitors.map((monitor) => monitor.id)
|
||||
);
|
||||
} finally {
|
||||
await Promise.all([
|
||||
projectMonitors.monitors.map((monitor) => {
|
||||
return deleteMonitor(monitor.id, projectMonitors.project);
|
||||
}),
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
it('project monitors - does not increment monitor revision unless a change has been made', async () => {
|
||||
try {
|
||||
await supertest
|
||||
.put(API_URLS.SYNTHETICS_MONITORS_PROJECT)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(projectMonitors);
|
||||
|
||||
await supertest
|
||||
.put(API_URLS.SYNTHETICS_MONITORS_PROJECT)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(projectMonitors);
|
||||
|
||||
const updatedMonitorsResponse = await Promise.all(
|
||||
projectMonitors.monitors.map((monitor) => {
|
||||
return supertest
|
||||
.get(API_URLS.SYNTHETICS_MONITORS)
|
||||
.query({ query: `${syntheticsMonitorType}.attributes.journey_id: ${monitor.id}` })
|
||||
.set('kbn-xsrf', 'true')
|
||||
.expect(200);
|
||||
})
|
||||
);
|
||||
|
||||
updatedMonitorsResponse.forEach((response) => {
|
||||
expect(response.body.monitors[0].attributes.revision).eql(1);
|
||||
});
|
||||
} finally {
|
||||
await Promise.all([
|
||||
projectMonitors.monitors.map((monitor) => {
|
||||
return deleteMonitor(monitor.id, projectMonitors.project);
|
||||
}),
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
it('project monitors - increments monitor revision when a change has been made', async () => {
|
||||
try {
|
||||
await supertest
|
||||
.put(API_URLS.SYNTHETICS_MONITORS_PROJECT)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(projectMonitors);
|
||||
|
||||
const editedMonitors = {
|
||||
...projectMonitors,
|
||||
monitors: projectMonitors.monitors.map((monitor) => ({
|
||||
...monitor,
|
||||
content: 'changed content',
|
||||
})),
|
||||
};
|
||||
|
||||
await supertest
|
||||
.put(API_URLS.SYNTHETICS_MONITORS_PROJECT)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(editedMonitors);
|
||||
|
||||
const updatedMonitorsResponse = await Promise.all(
|
||||
projectMonitors.monitors.map((monitor) => {
|
||||
return supertest
|
||||
.get(API_URLS.SYNTHETICS_MONITORS)
|
||||
.query({ query: `${syntheticsMonitorType}.attributes.journey_id: ${monitor.id}` })
|
||||
.set('kbn-xsrf', 'true')
|
||||
.expect(200);
|
||||
})
|
||||
);
|
||||
|
||||
updatedMonitorsResponse.forEach((response) => {
|
||||
expect(response.body.monitors[0].attributes.revision).eql(2);
|
||||
});
|
||||
} finally {
|
||||
await Promise.all([
|
||||
projectMonitors.monitors.map((monitor) => {
|
||||
return deleteMonitor(monitor.id, projectMonitors.project);
|
||||
}),
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
it('project monitors - does not delete monitors when keep stale is true', async () => {
|
||||
const secondMonitor = { ...projectMonitors.monitors[0], id: 'test-id-2' };
|
||||
const testMonitors = [projectMonitors.monitors[0], secondMonitor];
|
||||
|
||||
try {
|
||||
await supertest
|
||||
.put(API_URLS.SYNTHETICS_MONITORS_PROJECT)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send({
|
||||
...projectMonitors,
|
||||
monitors: testMonitors,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const apiResponse = await supertest
|
||||
.put(API_URLS.SYNTHETICS_MONITORS_PROJECT)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(projectMonitors)
|
||||
.expect(200);
|
||||
|
||||
// does not delete the stale monitor
|
||||
const getResponse = await supertest
|
||||
.get(API_URLS.SYNTHETICS_MONITORS)
|
||||
.query({
|
||||
query: `${syntheticsMonitorType}.attributes.journey_id: ${secondMonitor.id}`,
|
||||
})
|
||||
.set('kbn-xsrf', 'true')
|
||||
.expect(200);
|
||||
|
||||
const { monitors } = getResponse.body;
|
||||
|
||||
expect(monitors.length).eql(1);
|
||||
|
||||
expect(apiResponse.body.createdMonitors).eql([]);
|
||||
expect(apiResponse.body.failedMonitors).eql([]);
|
||||
expect(apiResponse.body.deletedMonitors).eql([]);
|
||||
expect(apiResponse.body.updatedMonitors).eql([projectMonitors.monitors[0].id]);
|
||||
expect(apiResponse.body.staleMonitors).eql([secondMonitor.id]);
|
||||
} finally {
|
||||
await Promise.all([
|
||||
testMonitors.map((monitor) => {
|
||||
return deleteMonitor(monitor.id, projectMonitors.project);
|
||||
}),
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
it('project monitors - deletes monitors when keep stale is false', async () => {
|
||||
const secondMonitor = { ...projectMonitors.monitors[0], id: 'test-id-2' };
|
||||
const testMonitors = [projectMonitors.monitors[0], secondMonitor];
|
||||
|
||||
try {
|
||||
await supertest
|
||||
.put(API_URLS.SYNTHETICS_MONITORS_PROJECT)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send({
|
||||
...projectMonitors,
|
||||
keep_stale: false,
|
||||
monitors: testMonitors,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const projectResponse = await supertest
|
||||
.put(API_URLS.SYNTHETICS_MONITORS_PROJECT)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send({ ...projectMonitors, keep_stale: false })
|
||||
.expect(200);
|
||||
|
||||
// expect monitor to have been deleted
|
||||
const getResponse = await supertest
|
||||
.get(API_URLS.SYNTHETICS_MONITORS)
|
||||
.query({
|
||||
query: `${syntheticsMonitorType}.attributes.journey_id: ${secondMonitor.id}`,
|
||||
})
|
||||
.set('kbn-xsrf', 'true')
|
||||
.expect(200);
|
||||
|
||||
const { monitors } = getResponse.body;
|
||||
|
||||
expect(monitors[0]).eql(undefined);
|
||||
|
||||
expect(projectResponse.body.createdMonitors).eql([]);
|
||||
expect(projectResponse.body.failedMonitors).eql([]);
|
||||
expect(projectResponse.body.updatedMonitors).eql([projectMonitors.monitors[0].id]);
|
||||
expect(projectResponse.body.deletedMonitors).eql([secondMonitor.id]);
|
||||
expect(projectResponse.body.staleMonitors).eql([]);
|
||||
} finally {
|
||||
await Promise.all([
|
||||
testMonitors.map((monitor) => {
|
||||
return deleteMonitor(monitor.id, projectMonitors.project);
|
||||
}),
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
it('project monitors - does not delete monitors from different suites when keep stale is false', async () => {
|
||||
const secondMonitor = { ...projectMonitors.monitors[0], id: 'test-id-2' };
|
||||
const testMonitors = [projectMonitors.monitors[0], secondMonitor];
|
||||
const testprojectId = 'test-suite-2';
|
||||
try {
|
||||
await supertest
|
||||
.put(API_URLS.SYNTHETICS_MONITORS_PROJECT)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send({
|
||||
...projectMonitors,
|
||||
keep_stale: false,
|
||||
monitors: testMonitors,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const projectResponse = await supertest
|
||||
.put(API_URLS.SYNTHETICS_MONITORS_PROJECT)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send({ ...projectMonitors, keep_stale: false, project: testprojectId })
|
||||
.expect(200);
|
||||
|
||||
// expect monitor not to have been deleted
|
||||
const getResponse = await supertest
|
||||
.get(API_URLS.SYNTHETICS_MONITORS)
|
||||
.query({
|
||||
query: `${syntheticsMonitorType}.attributes.journey_id: ${secondMonitor.id}`,
|
||||
})
|
||||
.set('kbn-xsrf', 'true')
|
||||
.expect(200);
|
||||
|
||||
const { monitors } = getResponse.body;
|
||||
|
||||
expect(monitors.length).eql(1);
|
||||
|
||||
expect(projectResponse.body.createdMonitors).eql([projectMonitors.monitors[0].id]);
|
||||
expect(projectResponse.body.failedMonitors).eql([]);
|
||||
expect(projectResponse.body.deletedMonitors).eql([]);
|
||||
expect(projectResponse.body.updatedMonitors).eql([]);
|
||||
expect(projectResponse.body.staleMonitors).eql([]);
|
||||
} finally {
|
||||
await Promise.all([
|
||||
testMonitors.map((monitor) => {
|
||||
return deleteMonitor(monitor.id, projectMonitors.project);
|
||||
}),
|
||||
]);
|
||||
|
||||
await Promise.all([
|
||||
testMonitors.map((monitor) => {
|
||||
return deleteMonitor(monitor.id, testprojectId);
|
||||
}),
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
it('project monitors - does not delete a monitor from the same suite in a different space', async () => {
|
||||
const secondMonitor = { ...projectMonitors.monitors[0], id: 'test-id-2' };
|
||||
const testMonitors = [projectMonitors.monitors[0], secondMonitor];
|
||||
const username = 'admin';
|
||||
const roleName = `synthetics_admin`;
|
||||
const password = `${username}-password`;
|
||||
const SPACE_ID = `test-space-${uuid.v4()}`;
|
||||
const SPACE_NAME = `test-space-name ${uuid.v4()}`;
|
||||
await kibanaServer.spaces.create({ id: SPACE_ID, name: SPACE_NAME });
|
||||
try {
|
||||
await security.role.create(roleName, {
|
||||
kibana: [
|
||||
{
|
||||
feature: {
|
||||
uptime: ['all'],
|
||||
},
|
||||
spaces: ['*'],
|
||||
},
|
||||
],
|
||||
});
|
||||
await security.user.create(username, {
|
||||
password,
|
||||
roles: [roleName],
|
||||
full_name: 'a kibana user',
|
||||
});
|
||||
await supertest
|
||||
.put(API_URLS.SYNTHETICS_MONITORS_PROJECT)
|
||||
.auth(username, password)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send({
|
||||
...projectMonitors,
|
||||
keep_stale: false,
|
||||
monitors: testMonitors,
|
||||
})
|
||||
.expect(200);
|
||||
const projectResponse = await supertest
|
||||
.put(`/s/${SPACE_ID}${API_URLS.SYNTHETICS_MONITORS_PROJECT}`)
|
||||
.auth(username, password)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send({ ...projectMonitors, keep_stale: false })
|
||||
.expect(200);
|
||||
// expect monitor not to have been deleted
|
||||
const getResponse = await supertest
|
||||
.get(API_URLS.SYNTHETICS_MONITORS)
|
||||
.auth(username, password)
|
||||
.query({
|
||||
query: `${syntheticsMonitorType}.attributes.journey_id: ${secondMonitor.id}`,
|
||||
})
|
||||
.set('kbn-xsrf', 'true')
|
||||
.expect(200);
|
||||
const { monitors } = getResponse.body;
|
||||
expect(monitors.length).eql(1);
|
||||
expect(projectResponse.body.createdMonitors).eql([projectMonitors.monitors[0].id]);
|
||||
expect(projectResponse.body.failedMonitors).eql([]);
|
||||
expect(projectResponse.body.deletedMonitors).eql([]);
|
||||
expect(projectResponse.body.updatedMonitors).eql([]);
|
||||
expect(projectResponse.body.staleMonitors).eql([]);
|
||||
} finally {
|
||||
await Promise.all([
|
||||
testMonitors.map((monitor) => {
|
||||
return deleteMonitor(
|
||||
monitor.id,
|
||||
projectMonitors.project,
|
||||
'default',
|
||||
username,
|
||||
password
|
||||
);
|
||||
}),
|
||||
]);
|
||||
await deleteMonitor(
|
||||
projectMonitors.monitors[0].id,
|
||||
projectMonitors.project,
|
||||
SPACE_ID,
|
||||
username,
|
||||
password
|
||||
);
|
||||
await security.user.delete(username);
|
||||
await security.role.delete(roleName);
|
||||
}
|
||||
});
|
||||
|
||||
it('project monitors - validates monitor type', async () => {
|
||||
try {
|
||||
const apiResponse = await supertest
|
||||
.put(API_URLS.SYNTHETICS_MONITORS_PROJECT)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send({
|
||||
...projectMonitors,
|
||||
monitors: [{ ...projectMonitors.monitors[0], schedule: '3m', tags: '' }],
|
||||
});
|
||||
|
||||
expect(apiResponse.body.updatedMonitors).eql([]);
|
||||
expect(apiResponse.body.failedMonitors).eql([
|
||||
{
|
||||
details:
|
||||
'Invalid value "3m" supplied to "schedule" | Invalid value "" supplied to "tags"',
|
||||
id: projectMonitors.monitors[0].id,
|
||||
payload: {
|
||||
content:
|
||||
'UEsDBBQACAAIAON5qVQAAAAAAAAAAAAAAAAfAAAAZXhhbXBsZXMvdG9kb3MvYmFzaWMuam91cm5leS50c22Q0WrDMAxF3/sVF7MHB0LMXlc6RvcN+wDPVWNviW0sdUsp/fe5SSiD7UFCWFfHujIGlpnkybwxFTZfoY/E3hsaLEtwhs9RPNWKDU12zAOxkXRIbN4tB9d9pFOJdO6EN2HMqQguWN9asFBuQVMmJ7jiWNII9fIXrbabdUYr58l9IhwhQQZCYORCTFFUC31Btj21NRc7Mq4Nds+4bDD/pNVgT9F52Jyr2Fa+g75LAPttg8yErk+S9ELpTmVotlVwnfNCuh2lepl3+JflUmSBJ3uggt1v9INW/lHNLKze9dJe1J3QJK8pSvWkm6aTtCet5puq+x63+AFQSwcIAPQ3VfcAAACcAQAAUEsBAi0DFAAIAAgA43mpVAD0N1X3AAAAnAEAAB8AAAAAAAAAAAAgAKSBAAAAAGV4YW1wbGVzL3RvZG9zL2Jhc2ljLmpvdXJuZXkudHNQSwUGAAAAAAEAAQBNAAAARAEAAAAA',
|
||||
filter: {
|
||||
match: 'check if title is present',
|
||||
},
|
||||
id: projectMonitors.monitors[0].id,
|
||||
locations: ['us-east4-a'],
|
||||
name: 'check if title is present',
|
||||
params: {},
|
||||
playwrightOptions: {
|
||||
chromiumSandbox: false,
|
||||
headless: true,
|
||||
},
|
||||
schedule: '3m',
|
||||
tags: '',
|
||||
throttling: {
|
||||
download: 5,
|
||||
latency: 20,
|
||||
upload: 3,
|
||||
},
|
||||
},
|
||||
reason: 'Failed to save or update monitor. Configuration is not valid',
|
||||
},
|
||||
]);
|
||||
expect(apiResponse.body.createdMonitors).eql([]);
|
||||
} finally {
|
||||
await Promise.all([
|
||||
projectMonitors.monitors.map((monitor) => {
|
||||
return deleteMonitor(monitor.id, projectMonitors.project);
|
||||
}),
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
it('project monitors - saves space as data stream namespace', async () => {
|
||||
const username = 'admin';
|
||||
const roleName = `synthetics_admin`;
|
||||
const password = `${username}-password`;
|
||||
const SPACE_ID = `test-space-${uuid.v4()}`;
|
||||
const SPACE_NAME = `test-space-name ${uuid.v4()}`;
|
||||
await kibanaServer.spaces.create({ id: SPACE_ID, name: SPACE_NAME });
|
||||
try {
|
||||
await security.role.create(roleName, {
|
||||
kibana: [
|
||||
{
|
||||
feature: {
|
||||
uptime: ['all'],
|
||||
},
|
||||
spaces: ['*'],
|
||||
},
|
||||
],
|
||||
});
|
||||
await security.user.create(username, {
|
||||
password,
|
||||
roles: [roleName],
|
||||
full_name: 'a kibana user',
|
||||
});
|
||||
await supertest
|
||||
.put(`/s/${SPACE_ID}${API_URLS.SYNTHETICS_MONITORS_PROJECT}`)
|
||||
.auth(username, password)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(projectMonitors)
|
||||
.expect(200);
|
||||
// expect monitor not to have been deleted
|
||||
const getResponse = await supertest
|
||||
.get(`/s/${SPACE_ID}${API_URLS.SYNTHETICS_MONITORS}`)
|
||||
.auth(username, password)
|
||||
.query({
|
||||
query: `${syntheticsMonitorType}.attributes.journey_id: ${projectMonitors.monitors[0].id}`,
|
||||
})
|
||||
.set('kbn-xsrf', 'true')
|
||||
.expect(200);
|
||||
const { monitors } = getResponse.body;
|
||||
expect(monitors.length).eql(1);
|
||||
expect(monitors[0].attributes[ConfigKey.NAMESPACE]).eql(SPACE_ID);
|
||||
} finally {
|
||||
await deleteMonitor(
|
||||
projectMonitors.monitors[0].id,
|
||||
projectMonitors.project,
|
||||
SPACE_ID,
|
||||
username,
|
||||
password
|
||||
);
|
||||
await security.user.delete(username);
|
||||
await security.role.delete(roleName);
|
||||
}
|
||||
});
|
||||
|
||||
it('project monitors - formats custom id appropriately', async () => {
|
||||
const username = 'admin';
|
||||
const roleName = `synthetics_admin`;
|
||||
const password = `${username}-password`;
|
||||
const SPACE_ID = `test-space-${uuid.v4()}`;
|
||||
const SPACE_NAME = `test-space-name ${uuid.v4()}`;
|
||||
await kibanaServer.spaces.create({ id: SPACE_ID, name: SPACE_NAME });
|
||||
try {
|
||||
await security.role.create(roleName, {
|
||||
kibana: [
|
||||
{
|
||||
feature: {
|
||||
uptime: ['all'],
|
||||
},
|
||||
spaces: ['*'],
|
||||
},
|
||||
],
|
||||
});
|
||||
await security.user.create(username, {
|
||||
password,
|
||||
roles: [roleName],
|
||||
full_name: 'a kibana user',
|
||||
});
|
||||
await supertest
|
||||
.put(`/s/${SPACE_ID}${API_URLS.SYNTHETICS_MONITORS_PROJECT}`)
|
||||
.auth(username, password)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(projectMonitors)
|
||||
.expect(200);
|
||||
// expect monitor not to have been deleted
|
||||
const getResponse = await supertest
|
||||
.get(`/s/${SPACE_ID}${API_URLS.SYNTHETICS_MONITORS}`)
|
||||
.auth(username, password)
|
||||
.query({
|
||||
query: `${syntheticsMonitorType}.attributes.journey_id: ${projectMonitors.monitors[0].id}`,
|
||||
})
|
||||
.set('kbn-xsrf', 'true')
|
||||
.expect(200);
|
||||
const { monitors } = getResponse.body;
|
||||
expect(monitors.length).eql(1);
|
||||
expect(monitors[0].attributes[ConfigKey.CUSTOM_HEARTBEAT_ID]).eql(
|
||||
`${projectMonitors.monitors[0].id}-${projectMonitors.project}-${SPACE_ID}`
|
||||
);
|
||||
} finally {
|
||||
await deleteMonitor(
|
||||
projectMonitors.monitors[0].id,
|
||||
projectMonitors.project,
|
||||
SPACE_ID,
|
||||
username,
|
||||
password
|
||||
);
|
||||
await security.user.delete(username);
|
||||
await security.role.delete(roleName);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
{
|
||||
"type": "browser",
|
||||
"enabled": true,
|
||||
"journey_id": "",
|
||||
"project_id": "",
|
||||
"schedule": {
|
||||
"number": "3",
|
||||
"unit": "m"
|
||||
|
@ -25,6 +27,8 @@
|
|||
"source.zip_url.folder": "",
|
||||
"source.zip_url.proxy_url": "",
|
||||
"source.inline.script": "step(\"Visit /users api route\", async () => {\\n const response = await page.goto('https://nextjs-test-synthetics.vercel.app/api/users');\\n expect(response.status()).toEqual(200);\\n});",
|
||||
"source.project.content": "",
|
||||
"is_push_monitor": false,
|
||||
"params": "",
|
||||
"screenshots": "on",
|
||||
"synthetics_args": [],
|
||||
|
@ -38,5 +42,6 @@
|
|||
"throttling.config": "5d/3u/20l",
|
||||
"locations": [],
|
||||
"name": "Test HTTP Monitor 03",
|
||||
"namespace": "testnamespace"
|
||||
"namespace": "testnamespace",
|
||||
"monitor.origin": "ui"
|
||||
}
|
||||
|
|
|
@ -60,5 +60,6 @@
|
|||
"isServiceManaged": true
|
||||
}],
|
||||
"namespace": "testnamespace",
|
||||
"revision": 1
|
||||
"revision": 1,
|
||||
"monitor.origin": "ui"
|
||||
}
|
||||
|
|
|
@ -32,5 +32,6 @@
|
|||
"TLSv1.3"
|
||||
],
|
||||
"name": "Test HTTP Monitor 04",
|
||||
"namespace": "testnamespace"
|
||||
"namespace": "testnamespace",
|
||||
"monitor.origin": "ui"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"keep_stale": true,
|
||||
"project": "test-suite",
|
||||
"monitors": [{
|
||||
"throttling": {
|
||||
"download": 5,
|
||||
"upload": 3,
|
||||
"latency": 20
|
||||
},
|
||||
"schedule": 10,
|
||||
"locations": [
|
||||
"us-east4-a"
|
||||
],
|
||||
"params": {},
|
||||
"playwrightOptions": {
|
||||
"headless": true,
|
||||
"chromiumSandbox": false
|
||||
},
|
||||
"name": "check if title is present",
|
||||
"id": "check-if-title-is-present",
|
||||
"tags": [],
|
||||
"content": "UEsDBBQACAAIAON5qVQAAAAAAAAAAAAAAAAfAAAAZXhhbXBsZXMvdG9kb3MvYmFzaWMuam91cm5leS50c22Q0WrDMAxF3/sVF7MHB0LMXlc6RvcN+wDPVWNviW0sdUsp/fe5SSiD7UFCWFfHujIGlpnkybwxFTZfoY/E3hsaLEtwhs9RPNWKDU12zAOxkXRIbN4tB9d9pFOJdO6EN2HMqQguWN9asFBuQVMmJ7jiWNII9fIXrbabdUYr58l9IhwhQQZCYORCTFFUC31Btj21NRc7Mq4Nds+4bDD/pNVgT9F52Jyr2Fa+g75LAPttg8yErk+S9ELpTmVotlVwnfNCuh2lepl3+JflUmSBJ3uggt1v9INW/lHNLKze9dJe1J3QJK8pSvWkm6aTtCet5puq+x63+AFQSwcIAPQ3VfcAAACcAQAAUEsBAi0DFAAIAAgA43mpVAD0N1X3AAAAnAEAAB8AAAAAAAAAAAAgAKSBAAAAAGV4YW1wbGVzL3RvZG9zL2Jhc2ljLmpvdXJuZXkudHNQSwUGAAAAAAEAAQBNAAAARAEAAAAA",
|
||||
"filter": {
|
||||
"match": "check if title is present"
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
|
@ -28,5 +28,6 @@
|
|||
"TLSv1.3"
|
||||
],
|
||||
"name": "Test HTTP Monitor 04",
|
||||
"namespace": "testnamespace"
|
||||
"namespace": "testnamespace",
|
||||
"monitor.origin": "ui"
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ import expect from '@kbn/expect';
|
|||
import { SimpleSavedObject } from '@kbn/core/public';
|
||||
import { MonitorFields } from '@kbn/synthetics-plugin/common/runtime_types';
|
||||
import { API_URLS } from '@kbn/synthetics-plugin/common/constants';
|
||||
import { formatSecrets } from '@kbn/synthetics-plugin/server/synthetics_service/utils/secrets';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
import { getFixtureJson } from './helper/get_fixture_json';
|
||||
|
||||
|
@ -97,7 +96,6 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
expect(apiResponse.body.attributes).eql({
|
||||
...monitors[0],
|
||||
revision: 1,
|
||||
secrets: formatSecrets(monitors[0]).secrets,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -75,6 +75,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) {
|
|||
describe('uptime CRUD routes', () => {
|
||||
loadTestFile(require.resolve('./get_monitor'));
|
||||
loadTestFile(require.resolve('./add_monitor'));
|
||||
loadTestFile(require.resolve('./add_monitor_project'));
|
||||
loadTestFile(require.resolve('./edit_monitor'));
|
||||
loadTestFile(require.resolve('./delete_monitor'));
|
||||
loadTestFile(require.resolve('./synthetics_enablement'));
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue