mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 10:40:07 -04:00
[Synthetics] Maintenance windows !! (#222174)
## Summary fixes https://github.com/elastic/kibana/issues/211540 User will be able to choose maintenance window in the form <img width="1723" alt="image" src="https://github.com/user-attachments/assets/c4d75aff-687f-40d3-a614-160e99ce9ac2" /> A callout will be displayed on the form <img width="1728" alt="image" src="https://github.com/user-attachments/assets/124727bd-0bb6-4934-9406-a36c3584670a" /> ### Task manager When changes are made to maintenance windows, those are sync via task manager to private location monitors, public location monitors are automatically synced as well in already existing task. ### Testing Create a maintenance window in stack management UI, apply it to monitor, make sure, it never runs during maintenance window. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
c8d07ed3d6
commit
b991e82700
69 changed files with 1384 additions and 97 deletions
|
@ -1160,6 +1160,7 @@
|
|||
"locations",
|
||||
"locations.id",
|
||||
"locations.label",
|
||||
"maintenance_windows",
|
||||
"name",
|
||||
"origin",
|
||||
"project_id",
|
||||
|
|
|
@ -3757,6 +3757,9 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"maintenance_windows": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"name": {
|
||||
"fields": {
|
||||
"keyword": {
|
||||
|
|
|
@ -176,7 +176,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
|
|||
"space": "953a72d8962d829e7ea465849297c5e44d8e9a2d",
|
||||
"spaces-usage-stats": "3abca98713c52af8b30300e386c7779b3025a20e",
|
||||
"synthetics-dynamic-settings": "4b40a93eb3e222619bf4e7fe34a9b9e7ab91a0a7",
|
||||
"synthetics-monitor": "5ceb25b6249bd26902c9b34273c71c3dce06dbea",
|
||||
"synthetics-monitor": "078401644f1a6cecdd4294093df20f8ff4063405",
|
||||
"synthetics-param": "3ebb744e5571de678b1312d5c418c8188002cf5e",
|
||||
"synthetics-private-location": "8cecc9e4f39637d2f8244eb7985c0690ceab24be",
|
||||
"synthetics-privates-locations": "f53d799d5c9bc8454aaa32c6abc99a899b025d5c",
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
export { AlertLifecycleStatusBadge } from './src/alert_lifecycle_status_badge';
|
||||
export type { AlertLifecycleStatusBadgeProps } from './src/alert_lifecycle_status_badge';
|
||||
export { MaintenanceWindowCallout } from './src/maintenance_window_callout';
|
||||
export { useFetchActiveMaintenanceWindows } from './src/maintenance_window_callout/use_fetch_active_maintenance_windows';
|
||||
export { AddMessageVariables } from './src/add_message_variables';
|
||||
|
||||
export * from './src/common/hooks';
|
||||
|
|
|
@ -154,6 +154,7 @@ export const DEFAULT_COMMON_FIELDS: CommonFields = {
|
|||
[ConfigKey.PARAMS]: '',
|
||||
[ConfigKey.LABELS]: {},
|
||||
[ConfigKey.MAX_ATTEMPTS]: 2,
|
||||
[ConfigKey.MAINTENANCE_WINDOWS]: [],
|
||||
revision: 1,
|
||||
};
|
||||
|
||||
|
|
|
@ -78,6 +78,7 @@ export enum ConfigKey {
|
|||
WAIT = 'wait',
|
||||
MONITOR_QUERY_ID = 'id',
|
||||
MAX_ATTEMPTS = 'max_attempts',
|
||||
MAINTENANCE_WINDOWS = 'maintenance_windows',
|
||||
}
|
||||
|
||||
export const secretKeys = [
|
||||
|
|
|
@ -46,6 +46,7 @@ export enum SYNTHETICS_API_URLS {
|
|||
CERTS = '/internal/synthetics/certs',
|
||||
|
||||
SUGGESTIONS = `/internal/synthetics/suggestions`,
|
||||
MAINTENANCE_WINDOWS = `/internal/synthetics/monitors/maintenance_windows`,
|
||||
|
||||
// Project monitor public endpoint
|
||||
SYNTHETICS_MONITORS_PROJECT = '/api/synthetics/project/{projectName}/monitors',
|
||||
|
|
|
@ -87,6 +87,7 @@ export const CommonFieldsCodec = t.intersection([
|
|||
[ConfigKey.ALERT_CONFIG]: AlertConfigsCodec,
|
||||
[ConfigKey.PARAMS]: t.string,
|
||||
[ConfigKey.LABELS]: t.record(t.string, t.string),
|
||||
[ConfigKey.MAINTENANCE_WINDOWS]: t.array(t.string),
|
||||
retest_on_failure: t.boolean,
|
||||
}),
|
||||
]);
|
||||
|
|
|
@ -64,6 +64,7 @@ export const ProjectMonitorCodec = t.intersection([
|
|||
retestOnFailure: t.boolean,
|
||||
fields: t.record(t.string, t.string),
|
||||
'service.name': t.string,
|
||||
maintenanceWindows: t.array(t.string),
|
||||
}),
|
||||
]);
|
||||
|
||||
|
|
|
@ -54,6 +54,7 @@ export const OverviewStatusMetaDataCodec = t.intersection([
|
|||
timestamp: t.string,
|
||||
spaceId: t.string,
|
||||
urls: t.string,
|
||||
maintenanceWindows: t.array(t.string),
|
||||
}),
|
||||
]);
|
||||
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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 { useFetchActiveMaintenanceWindows } from '@kbn/alerts-ui-shared';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { MwsCalloutContent } from './mws_callout_content';
|
||||
import { ConfigKey } from '../../../../../../common/runtime_types';
|
||||
import { useSelectedMonitor } from '../../monitor_details/hooks/use_selected_monitor';
|
||||
import { ClientPluginsStart } from '../../../../../plugin';
|
||||
|
||||
export const MonitorMWsCallout = () => {
|
||||
const { monitor } = useSelectedMonitor();
|
||||
|
||||
const services = useKibana<ClientPluginsStart>().services;
|
||||
const { data } = useFetchActiveMaintenanceWindows(services, {
|
||||
enabled: true,
|
||||
});
|
||||
if (!monitor) {
|
||||
return null;
|
||||
}
|
||||
const monitorMWs = monitor[ConfigKey.MAINTENANCE_WINDOWS];
|
||||
const hasMonitorMWs = monitorMWs && monitorMWs.length > 0;
|
||||
|
||||
if (data?.length && hasMonitorMWs) {
|
||||
const activeMWs = data.filter((mw) => monitorMWs.includes(mw.id));
|
||||
if (activeMWs) {
|
||||
return <MwsCalloutContent activeMWs={activeMWs} />;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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 { useFetchActiveMaintenanceWindows } from '@kbn/alerts-ui-shared';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { MwsCalloutContent } from './mws_callout_content';
|
||||
import { ClientPluginsStart } from '../../../../../plugin';
|
||||
import { selectOverviewStatus } from '../../../state/overview_status';
|
||||
|
||||
export const MonitorsMWsCallout = () => {
|
||||
const { allConfigs } = useSelector(selectOverviewStatus);
|
||||
|
||||
const services = useKibana<ClientPluginsStart>().services;
|
||||
const { data } = useFetchActiveMaintenanceWindows(services, {
|
||||
enabled: true,
|
||||
});
|
||||
|
||||
const monitorMWs = new Set(allConfigs?.flatMap((config) => config.maintenanceWindows ?? []));
|
||||
const hasMonitorMWs = monitorMWs && monitorMWs.size > 0;
|
||||
|
||||
if (data?.length && hasMonitorMWs) {
|
||||
const activeMWs = data.filter((mw) => monitorMWs.has(mw.id));
|
||||
if (activeMWs.length) {
|
||||
return <MwsCalloutContent activeMWs={activeMWs} />;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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 { EuiCallOut, EuiSpacer } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { MaintenanceWindow } from '@kbn/alerts-ui-shared/src/maintenance_window_callout/types';
|
||||
import { MaintenanceWindowsLink } from '../../monitor_add_edit/fields/maintenance_windows/create_maintenance_windows_btn';
|
||||
|
||||
export const MwsCalloutContent = ({ activeMWs }: { activeMWs: MaintenanceWindow[] }) => {
|
||||
if (activeMWs.length) {
|
||||
return (
|
||||
<>
|
||||
<EuiCallOut
|
||||
title={i18n.translate(
|
||||
'xpack.synthetics.maintenanceWindowCallout.maintenanceWindowActive.monitors',
|
||||
{
|
||||
defaultMessage: 'Maintenance windows are active',
|
||||
}
|
||||
)}
|
||||
color="warning"
|
||||
iconType="iInCircle"
|
||||
data-test-subj="maintenanceWindowCallout"
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.synthetics.maintenanceWindowCallout.maintenanceWindowActiveDescription.monitors',
|
||||
{
|
||||
defaultMessage:
|
||||
'Monitors are stopped while maintenance windows are running. Active maintenance windows are ',
|
||||
}
|
||||
)}
|
||||
{activeMWs.map((mws, index) => (
|
||||
<span key={mws.id}>
|
||||
<MaintenanceWindowsLink id={mws.id} label={mws.title} />
|
||||
{index !== activeMWs.length - 1 ? <span>, </span> : <span>.</span>}
|
||||
</span>
|
||||
))}
|
||||
</EuiCallOut>
|
||||
<EuiSpacer size="s" />
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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 { EuiLink } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
|
||||
export const MaintenanceWindowsLink = ({ id, label }: { id?: string; label?: string }) => {
|
||||
const { http } = useKibana().services;
|
||||
if (!id) {
|
||||
return (
|
||||
<EuiLink
|
||||
external
|
||||
data-test-subj="syntheticsCreateMaintenanceWindowsBtnButton"
|
||||
href={http?.basePath.prepend(
|
||||
'/app/management/insightsAndAlerting/maintenanceWindows/create'
|
||||
)}
|
||||
target="_blank"
|
||||
>
|
||||
{i18n.translate('xpack.synthetics.monitorConfig.maintenanceWindows.createButton', {
|
||||
defaultMessage: 'Create',
|
||||
})}
|
||||
</EuiLink>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<EuiLink
|
||||
external
|
||||
data-test-subj="syntheticsEditMaintenanceWindowsBtnButton"
|
||||
href={http?.basePath.prepend(
|
||||
`/app/management/insightsAndAlerting/maintenanceWindows/edit/${id}`
|
||||
)}
|
||||
target="_blank"
|
||||
>
|
||||
{label}
|
||||
</EuiLink>
|
||||
);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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 { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useMaintenanceWindows } from './use_maintenance_windows';
|
||||
|
||||
export interface MaintenanceWindowsFieldProps {
|
||||
fullWidth?: boolean;
|
||||
onChange: (value: string[]) => void;
|
||||
value?: string[];
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
export const MaintenanceWindowsField = ({
|
||||
value,
|
||||
readOnly,
|
||||
onChange,
|
||||
fullWidth,
|
||||
}: MaintenanceWindowsFieldProps) => {
|
||||
const { data } = useMaintenanceWindows();
|
||||
const options: Array<EuiComboBoxOptionOption<string>> =
|
||||
data?.data?.map((option) => ({
|
||||
value: option.id,
|
||||
label: option.title,
|
||||
})) ?? [];
|
||||
|
||||
return (
|
||||
<EuiComboBox<string>
|
||||
placeholder={i18n.translate('xpack.synthetics.monitorConfig.maintenanceWindows.placeholder', {
|
||||
defaultMessage: 'Select maintenance windows',
|
||||
})}
|
||||
options={options}
|
||||
onChange={(newValue) => {
|
||||
onChange(newValue.map((option) => option.value as string));
|
||||
}}
|
||||
defaultValue={[]}
|
||||
selectedOptions={options.filter((option) => value?.includes(option.value as string))}
|
||||
fullWidth={fullWidth}
|
||||
isDisabled={readOnly}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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 { useDispatch, useSelector } from 'react-redux';
|
||||
import { useEffect } from 'react';
|
||||
import {
|
||||
getMaintenanceWindowsAction,
|
||||
selectMaintenanceWindowsState,
|
||||
} from '../../../../state/maintenance_windows';
|
||||
|
||||
export function useMaintenanceWindows() {
|
||||
const dispatch = useDispatch();
|
||||
const { isLoading, data } = useSelector(selectMaintenanceWindowsState);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getMaintenanceWindowsAction.get());
|
||||
}, [dispatch]);
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
data,
|
||||
};
|
||||
}
|
|
@ -20,6 +20,7 @@ export const Field = memo<Props>(
|
|||
component: Component,
|
||||
helpText,
|
||||
label,
|
||||
labelAppend,
|
||||
ariaLabel,
|
||||
props,
|
||||
fieldKey,
|
||||
|
@ -57,6 +58,7 @@ export const Field = memo<Props>(
|
|||
'aria-label': ariaLabel,
|
||||
helpText,
|
||||
fullWidth: true,
|
||||
labelAppend,
|
||||
};
|
||||
|
||||
return controlled ? (
|
||||
|
|
|
@ -30,6 +30,8 @@ import {
|
|||
EuiBadge,
|
||||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
import { MaintenanceWindowsLink } from '../fields/maintenance_windows/create_maintenance_windows_btn';
|
||||
import { MaintenanceWindowsFieldProps } from '../fields/maintenance_windows/maintenance_windows';
|
||||
import { kibanaService } from '../../../../../utils/kibana_service';
|
||||
import {
|
||||
PROFILE_OPTIONS,
|
||||
|
@ -60,6 +62,7 @@ import {
|
|||
KeyValuePairsField,
|
||||
TextArea,
|
||||
ThrottlingWrapper,
|
||||
MaintenanceWindowsFieldWrapper,
|
||||
} from './field_wrappers';
|
||||
import { useMonitorName } from '../../../hooks/use_monitor_name';
|
||||
import {
|
||||
|
@ -1676,4 +1679,26 @@ export const FIELD = (readOnly?: boolean): FieldMap => ({
|
|||
'data-test-subj': 'syntheticsEnableAttemptSwitch',
|
||||
}),
|
||||
},
|
||||
[ConfigKey.MAINTENANCE_WINDOWS]: {
|
||||
fieldKey: ConfigKey.MAINTENANCE_WINDOWS,
|
||||
label: i18n.translate('xpack.synthetics.monitorConfig.maintenanceWindows.label', {
|
||||
defaultMessage: 'Maintenance windows',
|
||||
}),
|
||||
controlled: true,
|
||||
component: MaintenanceWindowsFieldWrapper,
|
||||
helpText: i18n.translate('xpack.synthetics.monitorConfig.maintenanceWindows.helpText', {
|
||||
defaultMessage:
|
||||
'A list of maintenance windows to apply to this monitor. The monitor will not run during these times.',
|
||||
}),
|
||||
props: ({ field, setValue, trigger }): MaintenanceWindowsFieldProps => ({
|
||||
readOnly,
|
||||
onChange: async (value) => {
|
||||
setValue(ConfigKey.MAINTENANCE_WINDOWS, value);
|
||||
await trigger(ConfigKey.MAINTENANCE_WINDOWS);
|
||||
},
|
||||
fullWidth: true,
|
||||
value: field?.value as string[],
|
||||
}),
|
||||
labelAppend: <MaintenanceWindowsLink />,
|
||||
},
|
||||
});
|
||||
|
|
|
@ -28,6 +28,10 @@ import {
|
|||
EuiTextArea,
|
||||
EuiTextAreaProps,
|
||||
} from '@elastic/eui';
|
||||
import {
|
||||
MaintenanceWindowsField,
|
||||
MaintenanceWindowsFieldProps,
|
||||
} from '../fields/maintenance_windows/maintenance_windows';
|
||||
import {
|
||||
ThrottlingConfigField,
|
||||
ThrottlingConfigFieldProps,
|
||||
|
@ -154,3 +158,8 @@ export const ResponseBodyIndexField = React.forwardRef<unknown, DefaultResponseB
|
|||
export const ThrottlingWrapper = React.forwardRef<unknown, ThrottlingConfigFieldProps>(
|
||||
(props, _ref) => <ThrottlingConfigField {...props} />
|
||||
);
|
||||
|
||||
export const MaintenanceWindowsFieldWrapper = React.forwardRef<
|
||||
unknown,
|
||||
MaintenanceWindowsFieldProps
|
||||
>((props, _ref) => <MaintenanceWindowsField {...props} />);
|
||||
|
|
|
@ -25,6 +25,20 @@ const DEFAULT_DATA_OPTIONS = (readOnly: boolean) => ({
|
|||
],
|
||||
});
|
||||
|
||||
const MAINTENANCE_WINDOWS_OPTIONS = (readOnly: boolean) => ({
|
||||
title: i18n.translate('xpack.synthetics.monitorConfig.section.maintenanceWindows.title', {
|
||||
defaultMessage: 'Maintenance windows',
|
||||
}),
|
||||
description: i18n.translate(
|
||||
'xpack.synthetics.monitorConfig.section.maintenanceWindows.description',
|
||||
{
|
||||
defaultMessage:
|
||||
'Configure maintenance windows to prevent alerts from being triggered during scheduled downtime.',
|
||||
}
|
||||
),
|
||||
components: [FIELD(readOnly)[ConfigKey.MAINTENANCE_WINDOWS]],
|
||||
});
|
||||
|
||||
const HTTP_ADVANCED = (readOnly: boolean) => ({
|
||||
requestConfig: {
|
||||
title: i18n.translate('xpack.synthetics.monitorConfig.section.requestConfiguration.title', {
|
||||
|
@ -206,6 +220,7 @@ export const FORM_CONFIG = (readOnly: boolean): FieldConfig => ({
|
|||
],
|
||||
advanced: [
|
||||
DEFAULT_DATA_OPTIONS(readOnly),
|
||||
MAINTENANCE_WINDOWS_OPTIONS(readOnly),
|
||||
HTTP_ADVANCED(readOnly).requestConfig,
|
||||
HTTP_ADVANCED(readOnly).responseConfig,
|
||||
HTTP_ADVANCED(readOnly).responseChecks,
|
||||
|
@ -227,6 +242,7 @@ export const FORM_CONFIG = (readOnly: boolean): FieldConfig => ({
|
|||
],
|
||||
advanced: [
|
||||
DEFAULT_DATA_OPTIONS(readOnly),
|
||||
MAINTENANCE_WINDOWS_OPTIONS(readOnly),
|
||||
TCP_ADVANCED(readOnly).requestConfig,
|
||||
TCP_ADVANCED(readOnly).responseChecks,
|
||||
TLS_OPTIONS(readOnly),
|
||||
|
@ -255,6 +271,7 @@ export const FORM_CONFIG = (readOnly: boolean): FieldConfig => ({
|
|||
FIELD(readOnly)[ConfigKey.NAMESPACE],
|
||||
],
|
||||
},
|
||||
MAINTENANCE_WINDOWS_OPTIONS(readOnly),
|
||||
...BROWSER_ADVANCED(readOnly),
|
||||
],
|
||||
},
|
||||
|
@ -281,6 +298,7 @@ export const FORM_CONFIG = (readOnly: boolean): FieldConfig => ({
|
|||
FIELD(readOnly)[ConfigKey.NAMESPACE],
|
||||
],
|
||||
},
|
||||
MAINTENANCE_WINDOWS_OPTIONS(readOnly),
|
||||
...BROWSER_ADVANCED(readOnly),
|
||||
],
|
||||
},
|
||||
|
@ -297,6 +315,10 @@ export const FORM_CONFIG = (readOnly: boolean): FieldConfig => ({
|
|||
FIELD(readOnly)[ConfigKey.MAX_ATTEMPTS],
|
||||
FIELD(readOnly)[AlertConfigKey.STATUS_ENABLED],
|
||||
],
|
||||
advanced: [DEFAULT_DATA_OPTIONS(readOnly), ICMP_ADVANCED(readOnly).requestConfig],
|
||||
advanced: [
|
||||
DEFAULT_DATA_OPTIONS(readOnly),
|
||||
MAINTENANCE_WINDOWS_OPTIONS(readOnly),
|
||||
ICMP_ADVANCED(readOnly).requestConfig,
|
||||
],
|
||||
},
|
||||
});
|
||||
|
|
|
@ -77,9 +77,10 @@ export interface FieldMeta<TFieldKey extends keyof FormConfig> {
|
|||
fieldKey: keyof FormConfig;
|
||||
component: React.ComponentType<any>;
|
||||
label?: string | React.ReactNode;
|
||||
labelAppend?: React.ReactNode;
|
||||
ariaLabel?: string;
|
||||
helpText?: string | React.ReactNode;
|
||||
hidden?: (depenencies: unknown[]) => boolean;
|
||||
hidden?: (dependencies: unknown[]) => boolean;
|
||||
props?: (params: {
|
||||
field?: ControllerRenderProps<FormConfig, TFieldKey>;
|
||||
formState: FormState<FormConfig>;
|
||||
|
@ -166,4 +167,5 @@ export interface FieldMap {
|
|||
[ConfigKey.IPV4]: FieldMeta<ConfigKey.IPV4>;
|
||||
[ConfigKey.MAX_ATTEMPTS]: FieldMeta<ConfigKey.MAX_ATTEMPTS>;
|
||||
[ConfigKey.LABELS]: FieldMeta<ConfigKey.LABELS>;
|
||||
[ConfigKey.MAINTENANCE_WINDOWS]: FieldMeta<ConfigKey.MAINTENANCE_WINDOWS>;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import React from 'react';
|
|||
import { EuiTitle, EuiPanel, EuiFlexGroup, EuiFlexItem, EuiText, EuiSpacer } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { LoadWhenInView } from '@kbn/observability-shared-plugin/public';
|
||||
import { MonitorMWsCallout } from '../../common/mws_callout/monitor_mws_callout';
|
||||
import { SummaryPanel } from './summary_panel';
|
||||
|
||||
import { useMonitorDetailsPage } from '../use_monitor_details_page';
|
||||
|
@ -34,6 +35,7 @@ export const MonitorSummary = () => {
|
|||
|
||||
return (
|
||||
<MonitorPendingWrapper>
|
||||
<MonitorMWsCallout />
|
||||
<SummaryPanel dateLabel={dateLabel} from={from} to={to} />
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexGroup gutterSize="m" wrap={true} responsive={false}>
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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 { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { useFetchActiveMaintenanceWindows } from '@kbn/alerts-ui-shared';
|
||||
import { OverviewStatusMetaData } from '../../../../../../common/runtime_types';
|
||||
import { ClientPluginsStart } from '../../../../../plugin';
|
||||
|
||||
export const useMonitorMWs = (monitor: OverviewStatusMetaData) => {
|
||||
const services = useKibana<ClientPluginsStart>().services;
|
||||
const { data } = useFetchActiveMaintenanceWindows(services, {
|
||||
enabled: true,
|
||||
});
|
||||
|
||||
const monitorMWs = monitor.maintenanceWindows;
|
||||
|
||||
return { activeMWs: data?.filter((mw) => monitorMWs?.includes(mw.id)) ?? [] };
|
||||
};
|
|
@ -9,6 +9,7 @@ import React from 'react';
|
|||
import { Redirect } from 'react-router-dom';
|
||||
import { useTrackPageview } from '@kbn/observability-shared-plugin/public';
|
||||
|
||||
import { MonitorsMWsCallout } from '../common/mws_callout/monitors_mws_callout';
|
||||
import { DisabledCallout } from './management/disabled_callout';
|
||||
import { useOverviewStatus } from './hooks/use_overview_status';
|
||||
import { GETTING_STARTED_ROUTE } from '../../../../../common/constants';
|
||||
|
@ -54,6 +55,7 @@ export const MonitorManagementPage: React.FC = () => {
|
|||
errorBody={labels.ERROR_HEADING_BODY}
|
||||
>
|
||||
<DisabledCallout total={absoluteTotal} />
|
||||
<MonitorsMWsCallout />
|
||||
<MonitorListContainer isEnabled={isEnabled} monitorListProps={monitorListProps} />
|
||||
</Loader>
|
||||
{showEmptyState && <EnablementEmptyState />}
|
||||
|
|
|
@ -20,11 +20,13 @@ import {
|
|||
EuiLink,
|
||||
EuiSpacer,
|
||||
EuiSkeletonText,
|
||||
EuiIcon,
|
||||
} from '@elastic/eui';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import styled from '@emotion/styled';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { useMonitorMWs } from '../../../hooks/use_monitor_mws';
|
||||
import { MetricErrorIcon } from './metric_error_icon';
|
||||
import { OverviewStatusMetaData } from '../../../../../../../../common/runtime_types';
|
||||
import { isTestRunning, manualTestRunSelector } from '../../../../../state/manual_test_runs';
|
||||
|
@ -61,6 +63,7 @@ export const MetricItemIcon = ({
|
|||
});
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const { activeMWs } = useMonitorMWs(monitor);
|
||||
|
||||
const inProgress = isTestRunning(testNowRun);
|
||||
|
||||
|
@ -83,6 +86,23 @@ export const MetricItemIcon = ({
|
|||
);
|
||||
}
|
||||
|
||||
if (activeMWs.length) {
|
||||
return (
|
||||
<Container>
|
||||
<EuiToolTip
|
||||
content={i18n.translate(
|
||||
'xpack.synthetics.metricItemIcon.euiButtonIcon.maintenanceWindowActive',
|
||||
{
|
||||
defaultMessage: 'Monitor is stopped while maintenance windows are running.',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<EuiIcon color="warning" data-test-subj="syntheticsMetricItemIconButton" type="pause" />
|
||||
</EuiToolTip>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
const closePopover = () => {
|
||||
dispatch(toggleErrorPopoverOpen(null));
|
||||
};
|
||||
|
|
|
@ -12,6 +12,7 @@ import { Provider as ReduxProvider } from 'react-redux';
|
|||
import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app';
|
||||
import { Subject } from 'rxjs';
|
||||
import { Store } from 'redux';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { SyntheticsRefreshContextProvider } from './synthetics_refresh_context';
|
||||
import { SyntheticsDataViewContextProvider } from './synthetics_data_view_context';
|
||||
import { SyntheticsAppProps } from './synthetics_settings_context';
|
||||
|
@ -20,6 +21,8 @@ import { storage, store } from '../state';
|
|||
export const SyntheticsSharedContext: React.FC<
|
||||
React.PropsWithChildren<SyntheticsAppProps & { reload$?: Subject<boolean>; reduxStore?: Store }>
|
||||
> = ({ reduxStore, coreStart, setupPlugins, startPlugins, children, darkMode, reload$ }) => {
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
return (
|
||||
<KibanaContextProvider
|
||||
services={{
|
||||
|
@ -46,20 +49,22 @@ export const SyntheticsSharedContext: React.FC<
|
|||
>
|
||||
<EuiThemeProvider darkMode={darkMode}>
|
||||
<ReduxProvider store={reduxStore ?? store}>
|
||||
<SyntheticsRefreshContextProvider reload$={reload$}>
|
||||
<SyntheticsDataViewContextProvider dataViews={startPlugins.dataViews}>
|
||||
<RedirectAppLinks
|
||||
coreStart={{
|
||||
application: coreStart.application,
|
||||
}}
|
||||
style={{
|
||||
height: '100%',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</RedirectAppLinks>
|
||||
</SyntheticsDataViewContextProvider>
|
||||
</SyntheticsRefreshContextProvider>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<SyntheticsRefreshContextProvider reload$={reload$}>
|
||||
<SyntheticsDataViewContextProvider dataViews={startPlugins.dataViews}>
|
||||
<RedirectAppLinks
|
||||
coreStart={{
|
||||
application: coreStart.application,
|
||||
}}
|
||||
style={{
|
||||
height: '100%',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</RedirectAppLinks>
|
||||
</SyntheticsDataViewContextProvider>
|
||||
</SyntheticsRefreshContextProvider>
|
||||
</QueryClientProvider>
|
||||
</ReduxProvider>
|
||||
</EuiThemeProvider>
|
||||
</KibanaContextProvider>
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* 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 type { FindMaintenanceWindowsResult } from '@kbn/alerting-plugin/server/application/maintenance_window/methods/find/types';
|
||||
import { createAsyncAction } from '../utils/actions';
|
||||
|
||||
export const getMaintenanceWindowsAction = createAsyncAction<void, FindMaintenanceWindowsResult>(
|
||||
'GET MAINTENANCE WINDOWS'
|
||||
);
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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 type { FindMaintenanceWindowsResult } from '@kbn/alerting-plugin/server/application/maintenance_window/methods/find/types';
|
||||
import { INITIAL_REST_VERSION } from '../../../../../common/constants';
|
||||
import { apiService } from '../../../../utils/api_service/api_service';
|
||||
|
||||
export const getMaintenanceWindows = async (): Promise<FindMaintenanceWindowsResult> => {
|
||||
return apiService.get<FindMaintenanceWindowsResult>(
|
||||
'/internal/alerting/rules/maintenance_window/_find',
|
||||
{
|
||||
version: INITIAL_REST_VERSION,
|
||||
}
|
||||
);
|
||||
};
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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 { takeLeading } from 'redux-saga/effects';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { getMaintenanceWindows } from './api';
|
||||
import { getMaintenanceWindowsAction } from './actions';
|
||||
import { fetchEffectFactory } from '../utils/fetch_effect';
|
||||
|
||||
export function* getMaintenanceWindowsEffect() {
|
||||
yield takeLeading(
|
||||
getMaintenanceWindowsAction.get,
|
||||
fetchEffectFactory(
|
||||
getMaintenanceWindows,
|
||||
getMaintenanceWindowsAction.success,
|
||||
getMaintenanceWindowsAction.fail,
|
||||
undefined,
|
||||
getFailMessage
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const getFailMessage = i18n.translate('xpack.synthetics.settings.mws.failed', {
|
||||
defaultMessage: 'Failed to fetch maintenance windows.',
|
||||
});
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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 { createReducer } from '@reduxjs/toolkit';
|
||||
import type { FindMaintenanceWindowsResult } from '@kbn/alerting-plugin/server/application/maintenance_window/methods/find/types';
|
||||
import { getMaintenanceWindowsAction } from './actions';
|
||||
|
||||
export interface MaintenanceWindowsState {
|
||||
isLoading?: boolean;
|
||||
data?: FindMaintenanceWindowsResult;
|
||||
}
|
||||
|
||||
const initialState: MaintenanceWindowsState = {
|
||||
isLoading: false,
|
||||
};
|
||||
|
||||
export const maintenanceWindowsReducer = createReducer(initialState, (builder) => {
|
||||
builder
|
||||
.addCase(getMaintenanceWindowsAction.get, (state) => {
|
||||
state.isLoading = true;
|
||||
})
|
||||
.addCase(getMaintenanceWindowsAction.success, (state, action) => {
|
||||
state.isLoading = false;
|
||||
state.data = action.payload;
|
||||
})
|
||||
.addCase(getMaintenanceWindowsAction.fail, (state, action) => {
|
||||
state.isLoading = false;
|
||||
});
|
||||
});
|
||||
|
||||
export * from './actions';
|
||||
export * from './effects';
|
||||
export * from './selectors';
|
||||
export * from './api';
|
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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 { AppState } from '..';
|
||||
|
||||
export const selectMaintenanceWindowsState = (state: AppState) => state.maintenanceWindows;
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { all, fork } from 'redux-saga/effects';
|
||||
import { getMaintenanceWindowsEffect } from './maintenance_windows';
|
||||
import { getCertsListEffect } from './certs';
|
||||
import {
|
||||
addGlobalParamEffect,
|
||||
|
@ -82,6 +83,7 @@ export const rootEffect = function* root(): Generator {
|
|||
fork(refreshOverviewTrendStats),
|
||||
fork(inspectStatusRuleEffect),
|
||||
fork(inspectTLSRuleEffect),
|
||||
fork(getMaintenanceWindowsEffect),
|
||||
...privateLocationsEffects.map((effect) => fork(effect)),
|
||||
]);
|
||||
};
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import { combineReducers } from '@reduxjs/toolkit';
|
||||
|
||||
import { maintenanceWindowsReducer, MaintenanceWindowsState } from './maintenance_windows';
|
||||
import { certsListReducer, CertsListState } from './certs';
|
||||
import { certificatesReducer, CertificatesState } from './certificates/certificates';
|
||||
import { globalParamsReducer, GlobalParamsState } from './global_params';
|
||||
|
@ -48,6 +49,7 @@ export interface SyntheticsAppState {
|
|||
serviceLocations: ServiceLocationsState;
|
||||
syntheticsEnablement: SyntheticsEnablementState;
|
||||
ui: UiState;
|
||||
maintenanceWindows: MaintenanceWindowsState;
|
||||
}
|
||||
|
||||
export const rootReducer = combineReducers<SyntheticsAppState>({
|
||||
|
@ -70,4 +72,5 @@ export const rootReducer = combineReducers<SyntheticsAppState>({
|
|||
serviceLocations: serviceLocationsReducer,
|
||||
syntheticsEnablement: syntheticsEnablementReducer,
|
||||
ui: uiReducer,
|
||||
maintenanceWindows: maintenanceWindowsReducer,
|
||||
});
|
||||
|
|
|
@ -163,6 +163,7 @@ export const mockState: SyntheticsAppState = {
|
|||
loading: false,
|
||||
error: null,
|
||||
},
|
||||
maintenanceWindows: {},
|
||||
};
|
||||
|
||||
function getBrowserJourneyMockSlice() {
|
||||
|
|
|
@ -108,6 +108,7 @@ describe('AddNewMonitorsPublicAPI', () => {
|
|||
'url.port': null,
|
||||
urls: '',
|
||||
labels: {},
|
||||
maintenance_windows: [],
|
||||
});
|
||||
});
|
||||
it('should normalize icmp', async () => {
|
||||
|
@ -145,6 +146,7 @@ describe('AddNewMonitorsPublicAPI', () => {
|
|||
type: 'icmp',
|
||||
wait: '1',
|
||||
labels: {},
|
||||
maintenance_windows: [],
|
||||
});
|
||||
});
|
||||
it('should normalize http', async () => {
|
||||
|
@ -204,6 +206,7 @@ describe('AddNewMonitorsPublicAPI', () => {
|
|||
urls: '',
|
||||
username: '',
|
||||
labels: {},
|
||||
maintenance_windows: [],
|
||||
});
|
||||
});
|
||||
it('should normalize browser', async () => {
|
||||
|
@ -259,6 +262,7 @@ describe('AddNewMonitorsPublicAPI', () => {
|
|||
'url.port': null,
|
||||
urls: '',
|
||||
labels: {},
|
||||
maintenance_windows: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -96,6 +96,7 @@ describe('syncEditedMonitor', () => {
|
|||
const syntheticsMonitorClient = new SyntheticsMonitorClient(syntheticsService, serverMock);
|
||||
|
||||
syntheticsService.editConfig = jest.fn();
|
||||
syntheticsService.getMaintenanceWindows = jest.fn();
|
||||
|
||||
it('includes the isEdit flag', async () => {
|
||||
await syncEditedMonitor({
|
||||
|
@ -117,7 +118,9 @@ describe('syncEditedMonitor', () => {
|
|||
expect.objectContaining({
|
||||
configId: '7af7e2f0-d5dc-11ec-87ac-bdfdb894c53d',
|
||||
}),
|
||||
])
|
||||
]),
|
||||
true,
|
||||
undefined
|
||||
);
|
||||
|
||||
expect(serverMock.authSavedObjectsClient?.update).toHaveBeenCalledWith(
|
||||
|
|
|
@ -182,6 +182,7 @@ describe('current status route', () => {
|
|||
"isStatusAlertEnabled": false,
|
||||
"locationId": "europe_germany",
|
||||
"locationLabel": "Europe - Germany",
|
||||
"maintenanceWindows": undefined,
|
||||
"monitorQueryId": "id2",
|
||||
"name": "test monitor 2",
|
||||
"projectId": "project-id",
|
||||
|
@ -213,6 +214,7 @@ describe('current status route', () => {
|
|||
"isStatusAlertEnabled": false,
|
||||
"locationId": "asia_japan",
|
||||
"locationLabel": "Asia/Pacific - Japan",
|
||||
"maintenanceWindows": undefined,
|
||||
"monitorQueryId": "id1",
|
||||
"name": "test monitor 1",
|
||||
"projectId": "project-id",
|
||||
|
@ -234,6 +236,7 @@ describe('current status route', () => {
|
|||
"isStatusAlertEnabled": false,
|
||||
"locationId": "asia_japan",
|
||||
"locationLabel": "Asia/Pacific - Japan",
|
||||
"maintenanceWindows": undefined,
|
||||
"monitorQueryId": "id2",
|
||||
"name": "test monitor 2",
|
||||
"projectId": "project-id",
|
||||
|
@ -344,6 +347,7 @@ describe('current status route', () => {
|
|||
"isStatusAlertEnabled": false,
|
||||
"locationId": "europe_germany",
|
||||
"locationLabel": "Europe - Germany",
|
||||
"maintenanceWindows": undefined,
|
||||
"monitorQueryId": "id2",
|
||||
"name": "test monitor 2",
|
||||
"projectId": "project-id",
|
||||
|
@ -375,6 +379,7 @@ describe('current status route', () => {
|
|||
"isStatusAlertEnabled": false,
|
||||
"locationId": "asia_japan",
|
||||
"locationLabel": "Asia/Pacific - Japan",
|
||||
"maintenanceWindows": undefined,
|
||||
"monitorQueryId": "id1",
|
||||
"name": "test monitor 1",
|
||||
"projectId": "project-id",
|
||||
|
@ -396,6 +401,7 @@ describe('current status route', () => {
|
|||
"isStatusAlertEnabled": false,
|
||||
"locationId": "asia_japan",
|
||||
"locationLabel": "Asia/Pacific - Japan",
|
||||
"maintenanceWindows": undefined,
|
||||
"monitorQueryId": "id2",
|
||||
"name": "test monitor 2",
|
||||
"projectId": "project-id",
|
||||
|
@ -456,6 +462,7 @@ describe('current status route', () => {
|
|||
"isStatusAlertEnabled": false,
|
||||
"locationId": "asia_japan",
|
||||
"locationLabel": "Asia/Pacific - Japan",
|
||||
"maintenanceWindows": undefined,
|
||||
"monitorQueryId": "id1",
|
||||
"name": "test monitor 1",
|
||||
"projectId": "project-id",
|
||||
|
@ -477,6 +484,7 @@ describe('current status route', () => {
|
|||
"isStatusAlertEnabled": false,
|
||||
"locationId": "asia_japan",
|
||||
"locationLabel": "Asia/Pacific - Japan",
|
||||
"maintenanceWindows": undefined,
|
||||
"monitorQueryId": "id2",
|
||||
"name": "test monitor 2",
|
||||
"projectId": "project-id",
|
||||
|
@ -498,6 +506,7 @@ describe('current status route', () => {
|
|||
"isStatusAlertEnabled": false,
|
||||
"locationId": "europe_germany",
|
||||
"locationLabel": "Europe - Germany",
|
||||
"maintenanceWindows": undefined,
|
||||
"monitorQueryId": "id2",
|
||||
"name": "test monitor 2",
|
||||
"projectId": "project-id",
|
||||
|
|
|
@ -350,6 +350,7 @@ export class OverviewStatusService {
|
|||
ConfigKey.PROJECT_ID,
|
||||
ConfigKey.ALERT_CONFIG,
|
||||
ConfigKey.URLS,
|
||||
ConfigKey.MAINTENANCE_WINDOWS,
|
||||
],
|
||||
});
|
||||
}
|
||||
|
@ -371,6 +372,7 @@ export class OverviewStatusService {
|
|||
updated_at: monitor.updated_at,
|
||||
spaceId: monitor.namespaces?.[0],
|
||||
urls: monitor.attributes[ConfigKey.URLS],
|
||||
maintenanceWindows: monitor.attributes[ConfigKey.MAINTENANCE_WINDOWS]?.map((mw) => mw),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -164,6 +164,7 @@ describe('Monitor migrations v8.7.0 -> v8.8.0', () => {
|
|||
'url.port': null,
|
||||
urls: 'https://elastic.co',
|
||||
labels: {},
|
||||
maintenance_windows: [],
|
||||
},
|
||||
coreMigrationVersion: '8.8.0',
|
||||
created_at: '2023-03-31T20:31:24.177Z',
|
||||
|
|
|
@ -246,6 +246,9 @@ export const getSyntheticsMonitorSavedObjectType = (
|
|||
},
|
||||
},
|
||||
},
|
||||
maintenance_windows: {
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
},
|
||||
management: {
|
||||
|
@ -269,6 +272,16 @@ export const getSyntheticsMonitorSavedObjectType = (
|
|||
},
|
||||
],
|
||||
},
|
||||
'2': {
|
||||
changes: [
|
||||
{
|
||||
type: 'mappings_addition',
|
||||
addedMappings: {
|
||||
maintenance_windows: { type: 'keyword' },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import { Logger } from '@kbn/logging';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { MaintenanceWindow } from '@kbn/alerting-plugin/server/application/maintenance_window/types';
|
||||
import { ConfigKey, MonitorFields } from '../../../common/runtime_types';
|
||||
import { ParsedVars, replaceVarsWithParams } from './lightweight_param_formatter';
|
||||
import variableParser from './variable_parser';
|
||||
|
@ -83,3 +84,41 @@ export const secondsToCronFormatter: FormatterFn = (fields, key) => {
|
|||
export const maxAttemptsFormatter: FormatterFn = (fields, key) => {
|
||||
return (fields[key] as number) ?? 2;
|
||||
};
|
||||
|
||||
enum Frequency {
|
||||
YEARLY,
|
||||
MONTHLY,
|
||||
WEEKLY,
|
||||
DAILY,
|
||||
HOURLY,
|
||||
MINUTELY,
|
||||
SECONDLY,
|
||||
}
|
||||
|
||||
function frequencyToString(value?: number): string | undefined {
|
||||
if (value === undefined || value === null) {
|
||||
return;
|
||||
}
|
||||
const name = Frequency[value];
|
||||
return name ? name.toLowerCase() : 'unknown';
|
||||
}
|
||||
|
||||
export const formatMWs = (mws?: MaintenanceWindow[], strRes = true) => {
|
||||
if (!mws) {
|
||||
return;
|
||||
}
|
||||
const formatted = mws.map((mw) => {
|
||||
const mwRule = mw?.rRule;
|
||||
if (mw && mwRule) {
|
||||
return {
|
||||
...mwRule,
|
||||
freq: frequencyToString(mwRule.freq),
|
||||
duration: `${mw.duration}ms`,
|
||||
};
|
||||
}
|
||||
});
|
||||
if (!strRes) {
|
||||
return formatted;
|
||||
}
|
||||
return JSON.stringify(formatted);
|
||||
};
|
||||
|
|
|
@ -38,6 +38,7 @@ export const commonFormatters: CommonFormatMap = {
|
|||
[ConfigKey.MONITOR_QUERY_ID]: stringToJsonFormatter,
|
||||
[ConfigKey.PARAMS]: null,
|
||||
[ConfigKey.MAX_ATTEMPTS]: null,
|
||||
[ConfigKey.MAINTENANCE_WINDOWS]: null,
|
||||
retest_on_failure: null,
|
||||
[ConfigKey.SCHEDULE]: (fields) =>
|
||||
JSON.stringify(
|
||||
|
|
|
@ -7,15 +7,38 @@
|
|||
import { ConfigKey, MonitorTypeEnum } from '../../../../common/runtime_types';
|
||||
import { formatSyntheticsPolicy } from './format_synthetics_policy';
|
||||
import { PROFILE_VALUES_ENUM, PROFILES_MAP } from '../../../../common/constants/monitor_defaults';
|
||||
import { MaintenanceWindow } from '@kbn/alerting-plugin/server/application/maintenance_window/types';
|
||||
|
||||
const gParams = { proxyUrl: 'https://proxy.com' };
|
||||
const testMW = [
|
||||
{
|
||||
id: '190bd51b-985a-4553-9fba-57222ddde6b7',
|
||||
title: 'test',
|
||||
enabled: true,
|
||||
duration: 1800000,
|
||||
expirationDate: '2026-06-10T11:43:25.175Z',
|
||||
events: [{ gte: '2025-06-10T11:40:29.124Z', lte: '2025-06-10T12:10:29.124Z' }],
|
||||
rRule: { dtstart: '2025-06-10T11:40:29.124Z', tzid: 'Europe/Berlin', freq: 0, count: 1 },
|
||||
createdBy: 'elastic',
|
||||
updatedBy: 'elastic',
|
||||
createdAt: '2025-06-10T11:43:25.176Z',
|
||||
updatedAt: '2025-06-10T11:43:25.176Z',
|
||||
eventStartTime: '2025-06-10T11:40:29.124Z',
|
||||
eventEndTime: '2025-06-10T12:10:29.124Z',
|
||||
status: 'running',
|
||||
categoryIds: null,
|
||||
scopedQuery: null,
|
||||
},
|
||||
] as MaintenanceWindow[];
|
||||
|
||||
describe('formatSyntheticsPolicy', () => {
|
||||
it('formats browser policy', () => {
|
||||
const { formattedPolicy } = formatSyntheticsPolicy(
|
||||
testNewPolicy,
|
||||
MonitorTypeEnum.BROWSER,
|
||||
browserConfig,
|
||||
gParams
|
||||
gParams,
|
||||
testMW
|
||||
);
|
||||
|
||||
expect(formattedPolicy).toEqual({
|
||||
|
@ -142,6 +165,9 @@ describe('formatSyntheticsPolicy', () => {
|
|||
username: {
|
||||
type: 'text',
|
||||
},
|
||||
maintenance_windows: {
|
||||
type: 'yaml',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -242,6 +268,7 @@ describe('formatSyntheticsPolicy', () => {
|
|||
type: 'text',
|
||||
value: 'tcp',
|
||||
},
|
||||
maintenance_windows: { type: 'yaml' },
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -315,6 +342,7 @@ describe('formatSyntheticsPolicy', () => {
|
|||
type: 'text',
|
||||
value: '1s',
|
||||
},
|
||||
maintenance_windows: { type: 'yaml' },
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -432,6 +460,11 @@ describe('formatSyntheticsPolicy', () => {
|
|||
type: 'text',
|
||||
value: 'browser',
|
||||
},
|
||||
maintenance_windows: {
|
||||
type: 'yaml',
|
||||
value:
|
||||
'[{"dtstart":"2025-06-10T11:40:29.124Z","tzid":"Europe/Berlin","freq":"yearly","count":1,"duration":"1800000ms"}]',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -473,7 +506,8 @@ describe('formatSyntheticsPolicy', () => {
|
|||
...httpPolicy,
|
||||
[ConfigKey.METADATA]: { is_tls_enabled: isTLSEnabled },
|
||||
},
|
||||
gParams
|
||||
gParams,
|
||||
[]
|
||||
);
|
||||
|
||||
expect(formattedPolicy).toEqual({
|
||||
|
@ -628,6 +662,7 @@ describe('formatSyntheticsPolicy', () => {
|
|||
type: 'text',
|
||||
value: '"admin"',
|
||||
},
|
||||
maintenance_windows: { type: 'yaml' },
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -728,6 +763,7 @@ describe('formatSyntheticsPolicy', () => {
|
|||
type: 'text',
|
||||
value: 'tcp',
|
||||
},
|
||||
maintenance_windows: { type: 'yaml' },
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -801,6 +837,7 @@ describe('formatSyntheticsPolicy', () => {
|
|||
type: 'text',
|
||||
value: '1s',
|
||||
},
|
||||
maintenance_windows: { type: 'yaml' },
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -897,6 +934,7 @@ describe('formatSyntheticsPolicy', () => {
|
|||
type: 'text',
|
||||
value: 'browser',
|
||||
},
|
||||
maintenance_windows: { type: 'yaml' },
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -987,6 +1025,7 @@ const testNewPolicy = {
|
|||
origin: { type: 'text' },
|
||||
'monitor.project.id': { type: 'text' },
|
||||
'monitor.project.name': { type: 'text' },
|
||||
maintenance_windows: { type: 'yaml' },
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -1026,6 +1065,7 @@ const testNewPolicy = {
|
|||
origin: { type: 'text' },
|
||||
'monitor.project.id': { type: 'text' },
|
||||
'monitor.project.name': { type: 'text' },
|
||||
maintenance_windows: { type: 'yaml' },
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -1056,6 +1096,7 @@ const testNewPolicy = {
|
|||
origin: { type: 'text' },
|
||||
'monitor.project.id': { type: 'text' },
|
||||
'monitor.project.name': { type: 'text' },
|
||||
maintenance_windows: { type: 'yaml' },
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -1094,6 +1135,7 @@ const testNewPolicy = {
|
|||
origin: { type: 'text' },
|
||||
'monitor.project.id': { type: 'text' },
|
||||
'monitor.project.name': { type: 'text' },
|
||||
maintenance_windows: { type: 'yaml' },
|
||||
},
|
||||
},
|
||||
{ enabled: true, data_stream: { type: 'synthetics', dataset: 'browser.network' } },
|
||||
|
@ -1157,6 +1199,7 @@ const browserConfig: any = {
|
|||
fields: { config_id: '00bb3ceb-a242-4c7a-8405-8da963661374' },
|
||||
fields_under_root: true,
|
||||
location_name: 'Test private location 0',
|
||||
maintenance_windows: ['190bd51b-985a-4553-9fba-57222ddde6b7'],
|
||||
};
|
||||
|
||||
const httpPolicy: any = {
|
||||
|
|
|
@ -7,11 +7,12 @@
|
|||
|
||||
import { NewPackagePolicy } from '@kbn/fleet-plugin/common';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { MaintenanceWindow } from '@kbn/alerting-plugin/server/application/maintenance_window/types';
|
||||
import { processorsFormatter } from './processors_formatter';
|
||||
import { LegacyConfigKey } from '../../../../common/constants/monitor_management';
|
||||
import { ConfigKey, MonitorTypeEnum, MonitorFields } from '../../../../common/runtime_types';
|
||||
import { throttlingFormatter } from './browser_formatters';
|
||||
import { replaceStringWithParams } from '../formatting_utils';
|
||||
import { formatMWs, replaceStringWithParams } from '../formatting_utils';
|
||||
import { syntheticsPolicyFormatters } from './formatters';
|
||||
import { PARAMS_KEYS_TO_SKIP } from '../common';
|
||||
|
||||
|
@ -31,6 +32,7 @@ export const formatSyntheticsPolicy = (
|
|||
monitorType: MonitorTypeEnum,
|
||||
config: Partial<MonitorFields & ProcessorFields>,
|
||||
params: Record<string, string>,
|
||||
mws: MaintenanceWindow[],
|
||||
isLegacy?: boolean
|
||||
) => {
|
||||
const configKeys = Object.keys(config) as ConfigKey[];
|
||||
|
@ -74,6 +76,22 @@ export const formatSyntheticsPolicy = (
|
|||
processorItem.value = processorsFormatter(config as MonitorFields & ProcessorFields);
|
||||
}
|
||||
|
||||
const mwItem = dataStream?.vars?.[ConfigKey.MAINTENANCE_WINDOWS];
|
||||
if (config[ConfigKey.MAINTENANCE_WINDOWS]?.length && mwItem) {
|
||||
const maintenanceWindows = config[ConfigKey.MAINTENANCE_WINDOWS];
|
||||
const formattedVal = formatMWs(
|
||||
maintenanceWindows.map((window) => {
|
||||
if (typeof window === 'string') {
|
||||
return mws.find((m) => m.id === window);
|
||||
}
|
||||
return window;
|
||||
}) as MaintenanceWindow[]
|
||||
);
|
||||
if (formattedVal) {
|
||||
mwItem.value = formattedVal;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove this once we remove legacy support
|
||||
const throttling = dataStream?.vars?.[LegacyConfigKey.THROTTLING_CONFIG];
|
||||
if (throttling) {
|
||||
|
|
|
@ -56,4 +56,5 @@ export const commonFormatters: CommonFormatMap = {
|
|||
`@every ${fields[ConfigKey.SCHEDULE]?.number}${fields[ConfigKey.SCHEDULE]?.unit}`,
|
||||
[ConfigKey.TAGS]: arrayFormatter,
|
||||
[ConfigKey.LABELS]: null,
|
||||
[ConfigKey.MAINTENANCE_WINDOWS]: null,
|
||||
};
|
||||
|
|
|
@ -104,7 +104,8 @@ describe('formatMonitorConfig', () => {
|
|||
Object.keys(testHTTPConfig) as ConfigKey[],
|
||||
testHTTPConfig,
|
||||
logger,
|
||||
{ proxyUrl: 'https://www.google.com' }
|
||||
{ proxyUrl: 'https://www.google.com' },
|
||||
[]
|
||||
);
|
||||
|
||||
expect(yamlConfig).toEqual({
|
||||
|
@ -145,7 +146,8 @@ describe('formatMonitorConfig', () => {
|
|||
[ConfigKey.METADATA]: { is_tls_enabled: isTLSEnabled },
|
||||
},
|
||||
logger,
|
||||
{ proxyUrl: 'https://www.google.com' }
|
||||
{ proxyUrl: 'https://www.google.com' },
|
||||
[]
|
||||
);
|
||||
|
||||
expect(yamlConfig).toEqual({
|
||||
|
@ -218,7 +220,8 @@ describe('browser fields', () => {
|
|||
Object.keys(testBrowserConfig) as ConfigKey[],
|
||||
testBrowserConfig,
|
||||
logger,
|
||||
{ proxyUrl: 'https://www.google.com' }
|
||||
{ proxyUrl: 'https://www.google.com' },
|
||||
[]
|
||||
);
|
||||
|
||||
expect(yamlConfig).toEqual(formattedBrowserConfig);
|
||||
|
@ -233,7 +236,8 @@ describe('browser fields', () => {
|
|||
params: '',
|
||||
},
|
||||
logger,
|
||||
{ proxyUrl: 'https://www.google.com' }
|
||||
{ proxyUrl: 'https://www.google.com' },
|
||||
[]
|
||||
);
|
||||
|
||||
expect(yamlConfig).toEqual(omit(formattedBrowserConfig, ['params', 'playwright_options']));
|
||||
|
@ -251,7 +255,8 @@ describe('browser fields', () => {
|
|||
},
|
||||
},
|
||||
logger,
|
||||
{ proxyUrl: 'https://www.google.com' }
|
||||
{ proxyUrl: 'https://www.google.com' },
|
||||
[]
|
||||
);
|
||||
|
||||
const expected = {
|
||||
|
@ -269,7 +274,8 @@ describe('browser fields', () => {
|
|||
Object.keys(testBrowserConfig) as ConfigKey[],
|
||||
testBrowserConfig,
|
||||
logger,
|
||||
{ proxyUrl: 'https://www.google.com' }
|
||||
{ proxyUrl: 'https://www.google.com' },
|
||||
[]
|
||||
);
|
||||
|
||||
const expected = {
|
||||
|
@ -287,7 +293,8 @@ describe('browser fields', () => {
|
|||
Object.keys(testBrowserConfig) as ConfigKey[],
|
||||
testBrowserConfig,
|
||||
logger,
|
||||
{ proxyUrl: 'https://www.google.com' }
|
||||
{ proxyUrl: 'https://www.google.com' },
|
||||
[]
|
||||
);
|
||||
|
||||
const expected = { ...formattedConfig, enabled: false };
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
|
||||
import { isEmpty, isNil, omitBy } from 'lodash';
|
||||
import { Logger } from '@kbn/logging';
|
||||
import { replaceStringWithParams } from '../formatting_utils';
|
||||
import { MaintenanceWindow } from '@kbn/alerting-plugin/server/application/maintenance_window/types';
|
||||
import { formatMWs, replaceStringWithParams } from '../formatting_utils';
|
||||
import { PARAMS_KEYS_TO_SKIP } from '../common';
|
||||
import {
|
||||
BrowserFields,
|
||||
|
@ -37,7 +38,8 @@ export const formatMonitorConfigFields = (
|
|||
configKeys: ConfigKey[],
|
||||
config: Partial<MonitorFields>,
|
||||
logger: Logger,
|
||||
params: Record<string, string>
|
||||
params: Record<string, string>,
|
||||
mws: MaintenanceWindow[]
|
||||
) => {
|
||||
const formattedMonitor = {} as Record<ConfigKey, any>;
|
||||
|
||||
|
@ -76,6 +78,19 @@ export const formatMonitorConfigFields = (
|
|||
sslKeys.forEach((key) => (formattedMonitor[key] = null));
|
||||
}
|
||||
|
||||
if (config[ConfigKey.MAINTENANCE_WINDOWS]) {
|
||||
const maintenanceWindows = config[ConfigKey.MAINTENANCE_WINDOWS];
|
||||
formattedMonitor[ConfigKey.MAINTENANCE_WINDOWS] = formatMWs(
|
||||
maintenanceWindows.map((window) => {
|
||||
if (typeof window === 'string') {
|
||||
return mws.find((m) => m.id === window);
|
||||
}
|
||||
return window;
|
||||
}) as MaintenanceWindow[],
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
return omitBy(formattedMonitor, isNil) as Partial<MonitorFields>;
|
||||
};
|
||||
|
||||
|
|
|
@ -138,7 +138,8 @@ describe('SyntheticsPrivateLocation', () => {
|
|||
await syntheticsPrivateLocation.editMonitors(
|
||||
[{ config: testConfig, globalParams: {} }],
|
||||
[mockPrivateLocation],
|
||||
'test-space'
|
||||
'test-space',
|
||||
[]
|
||||
);
|
||||
} catch (e) {
|
||||
expect(e).toEqual(new Error(error));
|
||||
|
@ -183,7 +184,8 @@ describe('SyntheticsPrivateLocation', () => {
|
|||
testMonitorPolicy,
|
||||
MonitorTypeEnum.BROWSER,
|
||||
dummyBrowserConfig,
|
||||
{}
|
||||
{},
|
||||
[]
|
||||
);
|
||||
|
||||
expect(test.formattedPolicy.inputs[3].streams[1]).toStrictEqual({
|
||||
|
|
|
@ -8,6 +8,7 @@ import { NewPackagePolicy } from '@kbn/fleet-plugin/common';
|
|||
import { NewPackagePolicyWithId } from '@kbn/fleet-plugin/server/services/package_policy';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { SavedObjectError } from '@kbn/core-saved-objects-common';
|
||||
import { MaintenanceWindow } from '@kbn/alerting-plugin/server/application/maintenance_window/types';
|
||||
import { DEFAULT_NAMESPACE_STRING } from '../../../common/constants/monitor_defaults';
|
||||
import {
|
||||
BROWSER_TEST_NOW_RUN,
|
||||
|
@ -75,6 +76,7 @@ export class SyntheticsPrivateLocation {
|
|||
newPolicyTemplate: NewPackagePolicy,
|
||||
spaceId: string,
|
||||
globalParams: Record<string, string>,
|
||||
maintenanceWindows: MaintenanceWindow[],
|
||||
testRunId?: string,
|
||||
runOnce?: boolean
|
||||
): Promise<NewPackagePolicy | null> {
|
||||
|
@ -122,7 +124,8 @@ export class SyntheticsPrivateLocation {
|
|||
: {}),
|
||||
...(runOnce ? { run_once: runOnce } : {}),
|
||||
},
|
||||
globalParams
|
||||
globalParams,
|
||||
maintenanceWindows
|
||||
);
|
||||
|
||||
return formattedPolicy;
|
||||
|
@ -165,6 +168,7 @@ export class SyntheticsPrivateLocation {
|
|||
newPolicyTemplate,
|
||||
spaceId,
|
||||
globalParams,
|
||||
[],
|
||||
testRunId,
|
||||
runOnce
|
||||
);
|
||||
|
@ -214,10 +218,12 @@ export class SyntheticsPrivateLocation {
|
|||
privateConfig,
|
||||
spaceId,
|
||||
allPrivateLocations,
|
||||
maintenanceWindows,
|
||||
}: {
|
||||
privateConfig?: PrivateConfig;
|
||||
allPrivateLocations: PrivateLocationAttributes[];
|
||||
spaceId: string;
|
||||
maintenanceWindows: MaintenanceWindow[];
|
||||
}) {
|
||||
if (!privateConfig) {
|
||||
return null;
|
||||
|
@ -238,7 +244,8 @@ export class SyntheticsPrivateLocation {
|
|||
location,
|
||||
newPolicyTemplate,
|
||||
spaceId,
|
||||
globalParams
|
||||
globalParams,
|
||||
maintenanceWindows
|
||||
);
|
||||
|
||||
const pkgPolicy = {
|
||||
|
@ -256,7 +263,8 @@ export class SyntheticsPrivateLocation {
|
|||
async editMonitors(
|
||||
configs: Array<{ config: HeartbeatConfig; globalParams: Record<string, string> }>,
|
||||
allPrivateLocations: SyntheticsPrivateLocations,
|
||||
spaceId: string
|
||||
spaceId: string,
|
||||
maintenanceWindows: MaintenanceWindow[]
|
||||
) {
|
||||
if (configs.length === 0) {
|
||||
return {};
|
||||
|
@ -291,7 +299,8 @@ export class SyntheticsPrivateLocation {
|
|||
privateLocation,
|
||||
newPolicyTemplate,
|
||||
spaceId,
|
||||
globalParams
|
||||
globalParams,
|
||||
maintenanceWindows
|
||||
);
|
||||
|
||||
if (!newPolicy) {
|
||||
|
|
|
@ -175,6 +175,7 @@ describe('getNormalizeCommonFields', () => {
|
|||
params: '',
|
||||
max_attempts: 2,
|
||||
labels: {},
|
||||
maintenance_windows: [],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -241,6 +242,7 @@ describe('getNormalizeCommonFields', () => {
|
|||
params: '',
|
||||
max_attempts: 2,
|
||||
labels: {},
|
||||
maintenance_windows: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
@ -101,6 +101,8 @@ export const getNormalizeCommonFields = ({
|
|||
// picking out keys specifically, so users can't add arbitrary fields
|
||||
[ConfigKey.ALERT_CONFIG]: getAlertConfig(monitor),
|
||||
[ConfigKey.LABELS]: monitor.fields || defaultFields[ConfigKey.LABELS],
|
||||
[ConfigKey.MAINTENANCE_WINDOWS]:
|
||||
monitor.maintenanceWindows || defaultFields[ConfigKey.MAINTENANCE_WINDOWS],
|
||||
...(monitor[ConfigKey.APM_SERVICE_NAME] && {
|
||||
[ConfigKey.APM_SERVICE_NAME]: monitor[ConfigKey.APM_SERVICE_NAME],
|
||||
}),
|
||||
|
|
|
@ -130,6 +130,7 @@ describe('ProjectMonitorFormatter', () => {
|
|||
syntheticsService.addConfigs = jest.fn();
|
||||
syntheticsService.editConfig = jest.fn();
|
||||
syntheticsService.deleteConfigs = jest.fn();
|
||||
syntheticsService.getMaintenanceWindows = jest.fn();
|
||||
|
||||
const encryptedSavedObjectsClient = encryptedSavedObjectsMock.createStart().getClient();
|
||||
|
||||
|
|
|
@ -67,6 +67,7 @@ describe('SyntheticsMonitorClient', () => {
|
|||
syntheticsService.addConfigs = jest.fn();
|
||||
syntheticsService.editConfig = jest.fn();
|
||||
syntheticsService.deleteConfigs = jest.fn();
|
||||
syntheticsService.getMaintenanceWindows = jest.fn();
|
||||
|
||||
const locations = times(3).map((n) => {
|
||||
return {
|
||||
|
@ -179,16 +180,20 @@ describe('SyntheticsMonitorClient', () => {
|
|||
);
|
||||
|
||||
expect(syntheticsService.editConfig).toHaveBeenCalledTimes(1);
|
||||
expect(syntheticsService.editConfig).toHaveBeenCalledWith([
|
||||
{
|
||||
monitor,
|
||||
configId: id,
|
||||
params: {
|
||||
username: 'elastic',
|
||||
expect(syntheticsService.editConfig).toHaveBeenCalledWith(
|
||||
[
|
||||
{
|
||||
monitor,
|
||||
configId: id,
|
||||
params: {
|
||||
username: 'elastic',
|
||||
},
|
||||
spaceId: 'test-space',
|
||||
},
|
||||
spaceId: 'test-space',
|
||||
},
|
||||
]);
|
||||
],
|
||||
true,
|
||||
undefined
|
||||
);
|
||||
expect(syntheticsService.deleteConfigs).toHaveBeenCalledTimes(1);
|
||||
expect(client.privateLocationAPI.editMonitors).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
|
|
@ -53,6 +53,7 @@ export class SyntheticsMonitorClient {
|
|||
const publicConfigs: ConfigData[] = [];
|
||||
|
||||
const paramsBySpace = await this.syntheticsService.getSyntheticsParams({ spaceId });
|
||||
const maintenanceWindows = await this.syntheticsService.getMaintenanceWindows();
|
||||
|
||||
for (const monitorObj of monitors) {
|
||||
const { formattedConfig, params, config } = await this.formatConfigWithParams(
|
||||
|
@ -77,7 +78,7 @@ export class SyntheticsMonitorClient {
|
|||
spaceId
|
||||
);
|
||||
|
||||
const syncErrors = this.syntheticsService.addConfigs(publicConfigs);
|
||||
const syncErrors = this.syntheticsService.addConfigs(publicConfigs, maintenanceWindows);
|
||||
|
||||
return await Promise.all([newPolicies, syncErrors]);
|
||||
}
|
||||
|
@ -98,6 +99,7 @@ export class SyntheticsMonitorClient {
|
|||
const deletedPublicConfigs: ConfigData[] = [];
|
||||
|
||||
const paramsBySpace = await this.syntheticsService.getSyntheticsParams({ spaceId });
|
||||
const maintenanceWindows = await this.syntheticsService.getMaintenanceWindows();
|
||||
|
||||
for (const editedMonitor of monitors) {
|
||||
const { str: paramsString, params } = mixParamsWithGlobalParams(
|
||||
|
@ -105,7 +107,7 @@ export class SyntheticsMonitorClient {
|
|||
editedMonitor.monitor
|
||||
);
|
||||
|
||||
const configData = {
|
||||
const configData: ConfigData = {
|
||||
spaceId,
|
||||
params: paramsBySpace[spaceId],
|
||||
monitor: editedMonitor.monitor,
|
||||
|
@ -146,10 +148,15 @@ export class SyntheticsMonitorClient {
|
|||
const privateEditPromise = this.privateLocationAPI.editMonitors(
|
||||
privateConfigs,
|
||||
allPrivateLocations,
|
||||
spaceId
|
||||
spaceId,
|
||||
maintenanceWindows
|
||||
);
|
||||
|
||||
const publicConfigsPromise = this.syntheticsService.editConfig(publicConfigs);
|
||||
const publicConfigsPromise = this.syntheticsService.editConfig(
|
||||
publicConfigs,
|
||||
true,
|
||||
maintenanceWindows
|
||||
);
|
||||
|
||||
const [publicSyncErrors, privateEditResponse] = await Promise.all([
|
||||
publicConfigsPromise,
|
||||
|
@ -298,6 +305,7 @@ export class SyntheticsMonitorClient {
|
|||
canSave,
|
||||
hideParams,
|
||||
});
|
||||
const maintenanceWindows = await this.syntheticsService.getMaintenanceWindows();
|
||||
|
||||
const { formattedConfig, params, config } = await this.formatConfigWithParams(
|
||||
monitorObj,
|
||||
|
@ -316,11 +324,13 @@ export class SyntheticsMonitorClient {
|
|||
}
|
||||
|
||||
const publicPromise = this.syntheticsService.inspectConfig(
|
||||
publicLocations.length > 0 ? config : undefined
|
||||
publicLocations.length > 0 ? config : null,
|
||||
maintenanceWindows
|
||||
);
|
||||
const privatePromise = this.privateLocationAPI.inspectPackagePolicy({
|
||||
privateConfig: privateConfigs?.[0],
|
||||
allPrivateLocations,
|
||||
maintenanceWindows,
|
||||
spaceId,
|
||||
});
|
||||
|
||||
|
|
|
@ -145,6 +145,8 @@ describe('SyntheticsService', () => {
|
|||
jest.spyOn(service, 'getOutput').mockResolvedValue({ hosts: ['es'], api_key: 'i:k' });
|
||||
jest.spyOn(service, 'getSyntheticsParams').mockResolvedValue({});
|
||||
|
||||
service.getMaintenanceWindows = jest.fn();
|
||||
|
||||
return { service, locations };
|
||||
};
|
||||
|
||||
|
@ -223,7 +225,7 @@ describe('SyntheticsService', () => {
|
|||
|
||||
(axios as jest.MockedFunction<typeof axios>).mockResolvedValue({} as AxiosResponse);
|
||||
|
||||
await service.addConfigs({ monitor: payload } as any);
|
||||
await service.addConfigs({ monitor: payload } as any, []);
|
||||
|
||||
expect(axios).toHaveBeenCalledTimes(1);
|
||||
expect(axios).toHaveBeenCalledWith(
|
||||
|
@ -289,7 +291,7 @@ describe('SyntheticsService', () => {
|
|||
|
||||
const payload = getFakePayload([locations[0]]);
|
||||
|
||||
await service.editConfig({ monitor: payload } as any);
|
||||
await service.editConfig({ monitor: payload } as any, true, []);
|
||||
|
||||
expect(axios).toHaveBeenCalledTimes(1);
|
||||
expect(axios).toHaveBeenCalledWith(
|
||||
|
@ -306,7 +308,7 @@ describe('SyntheticsService', () => {
|
|||
|
||||
const payload = getFakePayload([locations[0]]);
|
||||
|
||||
await service.editConfig({ monitor: payload } as any);
|
||||
await service.editConfig({ monitor: payload } as any, true, []);
|
||||
|
||||
expect(axios).toHaveBeenCalledTimes(1);
|
||||
expect(axios).toHaveBeenCalledWith(
|
||||
|
@ -323,7 +325,7 @@ describe('SyntheticsService', () => {
|
|||
|
||||
const payload = getFakePayload([locations[0]]);
|
||||
|
||||
await service.addConfigs({ monitor: payload } as any);
|
||||
await service.addConfigs({ monitor: payload } as any, []);
|
||||
|
||||
expect(axios).toHaveBeenCalledTimes(1);
|
||||
expect(axios).toHaveBeenCalledWith(
|
||||
|
@ -533,6 +535,8 @@ describe('SyntheticsService', () => {
|
|||
jest.spyOn(service, 'getOutput').mockResolvedValue({ hosts: ['es'], api_key: 'i:k' });
|
||||
jest.spyOn(service, 'getSyntheticsParams').mockResolvedValue({});
|
||||
|
||||
service.getMaintenanceWindows = jest.fn();
|
||||
|
||||
it('paginates the results', async () => {
|
||||
serverMock.config = mockConfig;
|
||||
|
||||
|
|
|
@ -18,6 +18,10 @@ import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common';
|
|||
import { ALL_SPACES_ID } from '@kbn/spaces-plugin/common/constants';
|
||||
import pMap from 'p-map';
|
||||
import moment from 'moment';
|
||||
import { MaintenanceWindowClient } from '@kbn/alerting-plugin/server/maintenance_window_client';
|
||||
import { MaintenanceWindow } from '@kbn/alerting-plugin/server/application/maintenance_window/types';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { MAINTENANCE_WINDOW_SAVED_OBJECT_TYPE } from '@kbn/alerting-plugin/common';
|
||||
import { registerCleanUpTask } from './private_location/clean_up_task';
|
||||
import { SyntheticsServerSetup } from '../types';
|
||||
import { syntheticsMonitorType, syntheticsParamType } from '../../common/types/saved_objects';
|
||||
|
@ -326,11 +330,11 @@ export class SyntheticsService {
|
|||
};
|
||||
}
|
||||
|
||||
async inspectConfig(config?: ConfigData) {
|
||||
if (!config) {
|
||||
async inspectConfig(config: ConfigData | null, mws: MaintenanceWindow[]) {
|
||||
if (!config || isEmpty(config)) {
|
||||
return null;
|
||||
}
|
||||
const monitors = this.formatConfigs(config);
|
||||
const monitors = this.formatConfigs(config, mws);
|
||||
const license = await this.getLicense();
|
||||
|
||||
const output = await this.getOutput({ inspect: true });
|
||||
|
@ -344,13 +348,13 @@ export class SyntheticsService {
|
|||
return null;
|
||||
}
|
||||
|
||||
async addConfigs(configs: ConfigData[]) {
|
||||
async addConfigs(configs: ConfigData[], mws: MaintenanceWindow[]) {
|
||||
try {
|
||||
if (configs.length === 0 || !this.isAllowed) {
|
||||
return;
|
||||
}
|
||||
|
||||
const monitors = this.formatConfigs(configs);
|
||||
const monitors = this.formatConfigs(configs, mws);
|
||||
const license = await this.getLicense();
|
||||
|
||||
const output = await this.getOutput();
|
||||
|
@ -376,13 +380,13 @@ export class SyntheticsService {
|
|||
}
|
||||
}
|
||||
|
||||
async editConfig(monitorConfig: ConfigData[], isEdit = true) {
|
||||
async editConfig(monitorConfig: ConfigData[], isEdit = true, mws: MaintenanceWindow[]) {
|
||||
try {
|
||||
if (monitorConfig.length === 0 || !this.isAllowed) {
|
||||
return;
|
||||
}
|
||||
const license = await this.getLicense();
|
||||
const monitors = this.formatConfigs(monitorConfig);
|
||||
const monitors = this.formatConfigs(monitorConfig, mws);
|
||||
|
||||
const output = await this.getOutput();
|
||||
if (output) {
|
||||
|
@ -411,6 +415,7 @@ export class SyntheticsService {
|
|||
let output: ServiceData['output'] | null = null;
|
||||
|
||||
const paramsBySpace = await this.getSyntheticsParams();
|
||||
const maintenanceWindows = await this.getMaintenanceWindows();
|
||||
const finder = await this.getSOClientFinder({ pageSize: PER_PAGE });
|
||||
|
||||
const bucketsByLocation: Record<string, MonitorFields[]> = {};
|
||||
|
@ -462,7 +467,11 @@ export class SyntheticsService {
|
|||
}
|
||||
|
||||
const monitors = result.saved_objects.filter(({ error }) => !error);
|
||||
const formattedConfigs = this.normalizeConfigs(monitors, paramsBySpace);
|
||||
const formattedConfigs = this.normalizeConfigs(
|
||||
monitors,
|
||||
paramsBySpace,
|
||||
maintenanceWindows
|
||||
);
|
||||
|
||||
this.logger.debug(
|
||||
`${formattedConfigs.length} monitors will be pushed to synthetics service.`
|
||||
|
@ -504,7 +513,7 @@ export class SyntheticsService {
|
|||
if (!configs) {
|
||||
return;
|
||||
}
|
||||
const monitors = this.formatConfigs(configs);
|
||||
const monitors = this.formatConfigs(configs, []);
|
||||
if (monitors.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
@ -545,7 +554,7 @@ export class SyntheticsService {
|
|||
|
||||
const data = {
|
||||
output,
|
||||
monitors: this.formatConfigs(configs),
|
||||
monitors: this.formatConfigs(configs, []),
|
||||
license,
|
||||
};
|
||||
return await this.apiClient.delete(data);
|
||||
|
@ -557,7 +566,6 @@ export class SyntheticsService {
|
|||
|
||||
async deleteAllConfigs() {
|
||||
const license = await this.getLicense();
|
||||
const paramsBySpace = await this.getSyntheticsParams();
|
||||
const finder = await this.getSOClientFinder({ pageSize: 100 });
|
||||
const output = await this.getOutput();
|
||||
if (!output) {
|
||||
|
@ -565,7 +573,7 @@ export class SyntheticsService {
|
|||
}
|
||||
|
||||
for await (const result of finder.find()) {
|
||||
const monitors = this.normalizeConfigs(result.saved_objects, paramsBySpace);
|
||||
const monitors = this.normalizeConfigs(result.saved_objects, {}, []);
|
||||
const hasPublicLocations = monitors.some((config) =>
|
||||
config.locations.some(({ isServiceManaged }) => isServiceManaged)
|
||||
);
|
||||
|
@ -636,7 +644,24 @@ export class SyntheticsService {
|
|||
return paramsBySpace;
|
||||
}
|
||||
|
||||
formatConfigs(configData: ConfigData[] | ConfigData) {
|
||||
async getMaintenanceWindows() {
|
||||
const { savedObjects } = this.server.coreStart;
|
||||
const soClient = savedObjects.createInternalRepository([MAINTENANCE_WINDOW_SAVED_OBJECT_TYPE]);
|
||||
|
||||
const maintenanceWindowClient = new MaintenanceWindowClient({
|
||||
savedObjectsClient: soClient,
|
||||
getUserName: async () => '',
|
||||
uiSettings: this.server.coreStart.uiSettings.asScopedToClient(soClient),
|
||||
logger: this.logger,
|
||||
});
|
||||
const mws = await maintenanceWindowClient.find({
|
||||
page: 0,
|
||||
perPage: 1000,
|
||||
});
|
||||
return mws.data;
|
||||
}
|
||||
|
||||
formatConfigs(configData: ConfigData[] | ConfigData, mws: MaintenanceWindow[]) {
|
||||
const configDataList = Array.isArray(configData) ? configData : [configData];
|
||||
|
||||
return configDataList.map((config) => {
|
||||
|
@ -651,20 +676,22 @@ export class SyntheticsService {
|
|||
Object.keys(asHeartbeatConfig) as ConfigKey[],
|
||||
asHeartbeatConfig as Partial<MonitorFields>,
|
||||
this.logger,
|
||||
params ?? {}
|
||||
params ?? {},
|
||||
mws
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
normalizeConfigs(
|
||||
monitors: Array<SavedObject<SyntheticsMonitorWithSecretsAttributes>>,
|
||||
paramsBySpace: Record<string, Record<string, string>>
|
||||
paramsBySpace: Record<string, Record<string, string>>,
|
||||
mws: MaintenanceWindow[]
|
||||
) {
|
||||
const configDataList = (monitors ?? []).map((monitor) => {
|
||||
const attributes = monitor.attributes as unknown as MonitorFields;
|
||||
const monitorSpace = monitor.namespaces?.[0] ?? DEFAULT_SPACE_ID;
|
||||
|
||||
const params = paramsBySpace[monitorSpace];
|
||||
const params = paramsBySpace[monitorSpace] ?? {};
|
||||
|
||||
return {
|
||||
params: { ...params, ...(paramsBySpace?.[ALL_SPACES_ID] ?? {}) },
|
||||
|
@ -675,7 +702,7 @@ export class SyntheticsService {
|
|||
};
|
||||
});
|
||||
|
||||
return this.formatConfigs(configDataList) as MonitorFields[];
|
||||
return this.formatConfigs(configDataList, mws) as MonitorFields[];
|
||||
}
|
||||
checkMissingSchedule(state: Record<string, string>) {
|
||||
try {
|
||||
|
@ -685,7 +712,7 @@ export class SyntheticsService {
|
|||
if (lastRunAt) {
|
||||
// log if it has missed last schedule
|
||||
const diff = moment(current).diff(lastRunAt, 'minutes');
|
||||
const syncInterval = Number((this.config.syncInterval ?? '5m').split('m')[0]);
|
||||
const syncInterval = Number((this.config.syncInterval ?? '5m').split('m')[0]) + 5;
|
||||
if (diff > syncInterval) {
|
||||
const message = `Synthetics monitor sync task has missed its schedule, it last ran ${diff} minutes ago.`;
|
||||
this.logger.warn(message);
|
||||
|
|
|
@ -0,0 +1,466 @@
|
|||
/*
|
||||
* 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 { TaskManagerSetupContract } from '@kbn/task-manager-plugin/server/plugin';
|
||||
import {
|
||||
SyncPrivateLocationMonitorsTask,
|
||||
runSynPrivateLocationMonitorsTaskSoon,
|
||||
CustomTaskInstance,
|
||||
} from './sync_private_locations_monitors_task';
|
||||
import { SyntheticsServerSetup } from '../types';
|
||||
import { SyntheticsMonitorClient } from '../synthetics_service/synthetics_monitor/synthetics_monitor_client';
|
||||
import * as getPrivateLocationsModule from '../synthetics_service/get_private_locations';
|
||||
import { coreMock } from '@kbn/core/server/mocks';
|
||||
import { CoreStart } from '@kbn/core-lifecycle-server';
|
||||
import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks';
|
||||
import { loggerMock } from '@kbn/logging-mocks';
|
||||
import { TaskStatus } from '@kbn/task-manager-plugin/server';
|
||||
import { mockEncryptedSO } from '../synthetics_service/utils/mocks';
|
||||
|
||||
const mockTaskManagerStart = taskManagerMock.createStart();
|
||||
const mockTaskManager = taskManagerMock.createSetup();
|
||||
|
||||
const mockSoClient = {
|
||||
find: jest.fn(),
|
||||
createInternalRepository: jest.fn(),
|
||||
};
|
||||
|
||||
const mockEncryptedSoClient = mockEncryptedSO();
|
||||
|
||||
const mockSyntheticsMonitorClient = {
|
||||
privateLocationAPI: {
|
||||
editMonitors: jest.fn(),
|
||||
},
|
||||
syntheticsService: {
|
||||
getSyntheticsParams: jest.fn(),
|
||||
getMaintenanceWindows: jest.fn(),
|
||||
},
|
||||
};
|
||||
const mockLogger = loggerMock.create();
|
||||
|
||||
const mockServerSetup: jest.Mocked<SyntheticsServerSetup> = {
|
||||
coreStart: coreMock.createStart() as CoreStart,
|
||||
pluginsStart: {
|
||||
taskManager: mockTaskManagerStart,
|
||||
} as any,
|
||||
encryptedSavedObjects: mockEncryptedSoClient as any,
|
||||
logger: mockLogger,
|
||||
} as any;
|
||||
|
||||
const getMockTaskInstance = (state: Record<string, any> = {}): CustomTaskInstance => {
|
||||
return {
|
||||
id: 'test-task',
|
||||
taskType: 'Test:Task',
|
||||
startedAt: new Date(),
|
||||
scheduledAt: new Date(),
|
||||
status: TaskStatus.Running,
|
||||
runAt: new Date(),
|
||||
attempts: 1,
|
||||
ownerId: 'test-owner',
|
||||
retryAt: null,
|
||||
state: {
|
||||
lastStartedAt: '2023-01-01T12:00:00.000Z',
|
||||
lastTotalParams: 1,
|
||||
lastTotalMWs: 1,
|
||||
attempts: 1,
|
||||
...state,
|
||||
},
|
||||
params: {},
|
||||
};
|
||||
};
|
||||
|
||||
describe('SyncPrivateLocationMonitorsTask', () => {
|
||||
let task: SyncPrivateLocationMonitorsTask;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
task = new SyncPrivateLocationMonitorsTask(
|
||||
mockServerSetup as any,
|
||||
mockTaskManager as unknown as TaskManagerSetupContract,
|
||||
mockSyntheticsMonitorClient as unknown as SyntheticsMonitorClient
|
||||
);
|
||||
mockSoClient.createInternalRepository.mockReturnValue(mockSoClient as any);
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should register task definitions correctly', () => {
|
||||
expect(mockTaskManager.registerTaskDefinitions).toHaveBeenCalledWith({
|
||||
'Synthetics:Sync-Private-Location-Monitors': expect.objectContaining({
|
||||
title: 'Synthetics Sync Global Params Task',
|
||||
description:
|
||||
'This task is executed so that we can sync private location monitors for example when global params are updated',
|
||||
timeout: '3m',
|
||||
maxAttempts: 3,
|
||||
createTaskRunner: expect.any(Function),
|
||||
}),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('start', () => {
|
||||
it('should schedule the task correctly', async () => {
|
||||
await task.start();
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Scheduling private location task');
|
||||
expect(mockTaskManagerStart.ensureScheduled).toHaveBeenCalledWith({
|
||||
id: 'Synthetics:Sync-Private-Location-Monitors-single-instance',
|
||||
state: {},
|
||||
schedule: {
|
||||
interval: '5m',
|
||||
},
|
||||
taskType: 'Synthetics:Sync-Private-Location-Monitors',
|
||||
params: {},
|
||||
});
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith(
|
||||
'Sync private location monitors task scheduled successfully'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('runTask', () => {
|
||||
it('should skip sync if no data has changed', async () => {
|
||||
const taskInstance = getMockTaskInstance();
|
||||
jest.spyOn(task, 'hasAnyDataChanged').mockResolvedValue({
|
||||
hasDataChanged: false,
|
||||
totalParams: 1,
|
||||
totalMWs: 1,
|
||||
});
|
||||
jest.spyOn(getPrivateLocationsModule, 'getPrivateLocations').mockResolvedValue([
|
||||
{
|
||||
id: 'pl-1',
|
||||
label: 'Private Location 1',
|
||||
isServiceManaged: false,
|
||||
agentPolicyId: 'policy-1',
|
||||
},
|
||||
]);
|
||||
|
||||
const result = await task.runTask({ taskInstance });
|
||||
|
||||
expect(task.hasAnyDataChanged).toHaveBeenCalled();
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith(
|
||||
expect.stringContaining('No data has changed since last run')
|
||||
);
|
||||
expect(mockSyntheticsMonitorClient.privateLocationAPI.editMonitors).not.toHaveBeenCalled();
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.state).toEqual({
|
||||
lastStartedAt: taskInstance.startedAt?.toISOString(),
|
||||
lastTotalParams: 1,
|
||||
lastTotalMWs: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('should run sync if data has changed', async () => {
|
||||
const taskInstance = getMockTaskInstance();
|
||||
jest.spyOn(task, 'hasAnyDataChanged').mockResolvedValue({
|
||||
hasDataChanged: true,
|
||||
totalParams: 2,
|
||||
totalMWs: 1,
|
||||
});
|
||||
jest.spyOn(getPrivateLocationsModule, 'getPrivateLocations').mockResolvedValue([
|
||||
{
|
||||
id: 'pl-1',
|
||||
label: 'Private Location 1',
|
||||
isServiceManaged: false,
|
||||
agentPolicyId: 'policy-1',
|
||||
},
|
||||
]);
|
||||
jest.spyOn(task, 'syncGlobalParams').mockResolvedValue(undefined);
|
||||
|
||||
const result = await task.runTask({ taskInstance });
|
||||
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith(
|
||||
'Syncing private location monitors because data has changed'
|
||||
);
|
||||
expect(task.syncGlobalParams).toHaveBeenCalled();
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Sync of private location monitors succeeded');
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.state).toEqual({
|
||||
lastStartedAt: taskInstance.startedAt?.toISOString(),
|
||||
lastTotalParams: 2,
|
||||
lastTotalMWs: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('should not sync if data changed but no private locations exist', async () => {
|
||||
const taskInstance = getMockTaskInstance();
|
||||
jest.spyOn(task, 'hasAnyDataChanged').mockResolvedValue({
|
||||
hasDataChanged: true,
|
||||
totalParams: 2,
|
||||
totalMWs: 1,
|
||||
});
|
||||
jest.spyOn(getPrivateLocationsModule, 'getPrivateLocations').mockResolvedValue([]);
|
||||
jest.spyOn(task, 'syncGlobalParams');
|
||||
|
||||
await task.runTask({ taskInstance });
|
||||
|
||||
expect(getPrivateLocationsModule.getPrivateLocations).toHaveBeenCalled();
|
||||
expect(task.syncGlobalParams).not.toHaveBeenCalled();
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('Sync of private location monitors succeeded');
|
||||
});
|
||||
|
||||
it('should handle errors during the run', async () => {
|
||||
const taskInstance = getMockTaskInstance();
|
||||
const error = new Error('Sync failed');
|
||||
jest.spyOn(task, 'hasAnyDataChanged').mockRejectedValue(error);
|
||||
|
||||
const result = await task.runTask({ taskInstance });
|
||||
|
||||
expect(mockLogger.error).toHaveBeenCalledWith(
|
||||
`Sync of private location monitors failed: ${error.message}`
|
||||
);
|
||||
expect(result.error).toBe(error);
|
||||
expect(result.state).toEqual({
|
||||
lastStartedAt: taskInstance.startedAt?.toISOString(),
|
||||
lastTotalParams: 1,
|
||||
lastTotalMWs: 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasAnyDataChanged', () => {
|
||||
it('should return true if params changed', async () => {
|
||||
jest
|
||||
.spyOn(task, 'hasAnyParamChanged')
|
||||
.mockResolvedValue({ hasParamsChanges: true, totalParams: 2 } as any);
|
||||
jest
|
||||
.spyOn(task, 'hasMWsChanged')
|
||||
.mockResolvedValue({ hasMWsChanged: false, totalMWs: 1 } as any);
|
||||
|
||||
const res = await task.hasAnyDataChanged({
|
||||
taskInstance: getMockTaskInstance(),
|
||||
soClient: mockSoClient as any,
|
||||
});
|
||||
|
||||
expect(res.hasDataChanged).toBe(true);
|
||||
expect(res.totalParams).toBe(2);
|
||||
expect(res.totalMWs).toBe(1);
|
||||
});
|
||||
|
||||
it('should return true if maintenance windows changed', async () => {
|
||||
jest
|
||||
.spyOn(task, 'hasAnyParamChanged')
|
||||
.mockResolvedValue({ hasParamsChanges: false, totalParams: 1 } as any);
|
||||
jest
|
||||
.spyOn(task, 'hasMWsChanged')
|
||||
.mockResolvedValue({ hasMWsChanged: true, totalMWs: 2 } as any);
|
||||
|
||||
const res = await task.hasAnyDataChanged({
|
||||
taskInstance: getMockTaskInstance(),
|
||||
soClient: mockSoClient as any,
|
||||
});
|
||||
|
||||
expect(res.hasDataChanged).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if nothing changed', async () => {
|
||||
jest
|
||||
.spyOn(task, 'hasAnyParamChanged')
|
||||
.mockResolvedValue({ hasParamsChanges: false, totalParams: 1 } as any);
|
||||
jest
|
||||
.spyOn(task, 'hasMWsChanged')
|
||||
.mockResolvedValue({ hasMWsChanged: false, totalMWs: 1 } as any);
|
||||
|
||||
const res = await task.hasAnyDataChanged({
|
||||
taskInstance: getMockTaskInstance(),
|
||||
soClient: mockSoClient as any,
|
||||
});
|
||||
|
||||
expect(res.hasDataChanged).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasAnyParamChanged', () => {
|
||||
it('returns true if updated params are found', async () => {
|
||||
mockSoClient.find
|
||||
.mockResolvedValueOnce({ total: 1 }) // updated
|
||||
.mockResolvedValueOnce({ total: 10 }); // total
|
||||
const { hasParamsChanges } = await task.hasAnyParamChanged({
|
||||
soClient: mockSoClient as any,
|
||||
lastStartedAt: '...',
|
||||
lastTotalParams: 10,
|
||||
});
|
||||
expect(hasParamsChanges).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true if total number of params changed', async () => {
|
||||
mockSoClient.find
|
||||
.mockResolvedValueOnce({ total: 0 }) // updated
|
||||
.mockResolvedValueOnce({ total: 11 }); // total
|
||||
const { hasParamsChanges } = await task.hasAnyParamChanged({
|
||||
soClient: mockSoClient as any,
|
||||
lastStartedAt: '...',
|
||||
lastTotalParams: 10,
|
||||
});
|
||||
expect(hasParamsChanges).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false if no changes are detected', async () => {
|
||||
mockSoClient.find
|
||||
.mockResolvedValueOnce({ total: 0 }) // updated
|
||||
.mockResolvedValueOnce({ total: 10 }); // total
|
||||
const { hasParamsChanges } = await task.hasAnyParamChanged({
|
||||
soClient: mockSoClient as any,
|
||||
lastStartedAt: '...',
|
||||
lastTotalParams: 10,
|
||||
});
|
||||
expect(hasParamsChanges).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasMWsChanged', () => {
|
||||
it('returns true if updated MWs are found', async () => {
|
||||
mockSoClient.find
|
||||
.mockResolvedValueOnce({ total: 1 }) // updated
|
||||
.mockResolvedValueOnce({ total: 5 }); // total
|
||||
const { hasMWsChanged } = await task.hasMWsChanged({
|
||||
soClient: mockSoClient as any,
|
||||
lastStartedAt: '...',
|
||||
lastTotalMWs: 5,
|
||||
});
|
||||
expect(hasMWsChanged).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true if total number of MWs changed', async () => {
|
||||
mockSoClient.find
|
||||
.mockResolvedValueOnce({ total: 0 }) // updated
|
||||
.mockResolvedValueOnce({ total: 6 }); // total
|
||||
const { hasMWsChanged } = await task.hasMWsChanged({
|
||||
soClient: mockSoClient as any,
|
||||
lastStartedAt: '...',
|
||||
lastTotalMWs: 5,
|
||||
});
|
||||
expect(hasMWsChanged).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false if no changes are detected', async () => {
|
||||
mockSoClient.find
|
||||
.mockResolvedValueOnce({ total: 0 }) // updated
|
||||
.mockResolvedValueOnce({ total: 5 }); // total
|
||||
const { hasMWsChanged } = await task.hasMWsChanged({
|
||||
soClient: mockSoClient as any,
|
||||
lastStartedAt: '...',
|
||||
lastTotalMWs: 5,
|
||||
});
|
||||
expect(hasMWsChanged).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('syncGlobalParams', () => {
|
||||
it('should fetch all configs and edit monitors on private locations', async () => {
|
||||
const mockAllPrivateLocations = [{ id: 'pl-1', name: 'Private Location 1' }];
|
||||
|
||||
// Mocking the return of getAllMonitorConfigs
|
||||
jest.spyOn(task, 'getAllMonitorConfigs').mockResolvedValue({
|
||||
configsBySpaces: {
|
||||
space1: [{ id: 'm1', locations: [{ name: 'pl-1', isServiceManaged: false }] }],
|
||||
},
|
||||
spaceIds: new Set(['space1']),
|
||||
paramsBySpace: { space1: { global: 'param' } },
|
||||
maintenanceWindows: [],
|
||||
} as any);
|
||||
|
||||
jest
|
||||
.spyOn(task, 'parseLocations')
|
||||
.mockReturnValue({ privateLocations: ['pl-1'], publicLocations: [] } as any);
|
||||
|
||||
await task.syncGlobalParams({
|
||||
allPrivateLocations: mockAllPrivateLocations as any,
|
||||
encryptedSavedObjects: mockEncryptedSoClient as any,
|
||||
soClient: mockSoClient as any,
|
||||
});
|
||||
|
||||
expect(task.getAllMonitorConfigs).toHaveBeenCalled();
|
||||
expect(mockSyntheticsMonitorClient.privateLocationAPI.editMonitors).toHaveBeenCalledWith(
|
||||
expect.any(Array),
|
||||
mockAllPrivateLocations,
|
||||
'space1',
|
||||
[]
|
||||
);
|
||||
});
|
||||
|
||||
it('should not call editMonitors if no monitors are on private locations', async () => {
|
||||
jest.spyOn(task, 'getAllMonitorConfigs').mockResolvedValue({
|
||||
configsBySpaces: {
|
||||
space1: [{ id: 'm1', locations: [] }],
|
||||
},
|
||||
spaceIds: new Set(['space1']),
|
||||
paramsBySpace: {},
|
||||
maintenanceWindows: [],
|
||||
} as any);
|
||||
|
||||
// This monitor has no private locations
|
||||
jest
|
||||
.spyOn(task, 'parseLocations')
|
||||
.mockReturnValue({ privateLocations: [], publicLocations: [] } as any);
|
||||
|
||||
await task.syncGlobalParams({
|
||||
allPrivateLocations: [],
|
||||
soClient: mockSoClient as any,
|
||||
encryptedSavedObjects: mockEncryptedSoClient as any,
|
||||
});
|
||||
|
||||
expect(mockSyntheticsMonitorClient.privateLocationAPI.editMonitors).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseLocations', () => {
|
||||
it('separates private and public locations correctly', () => {
|
||||
const config = {
|
||||
locations: [
|
||||
{ name: 'private1', isServiceManaged: false },
|
||||
{ name: 'public1', isServiceManaged: true },
|
||||
{ name: 'private2', isServiceManaged: false },
|
||||
],
|
||||
};
|
||||
const { privateLocations, publicLocations } = task.parseLocations(config as any);
|
||||
expect(privateLocations).toHaveLength(2);
|
||||
expect(publicLocations).toHaveLength(1);
|
||||
expect(privateLocations[0]).toEqual({ name: 'private1', isServiceManaged: false });
|
||||
expect(publicLocations[0]).toEqual({ name: 'public1', isServiceManaged: true });
|
||||
});
|
||||
|
||||
it('handles empty locations array', () => {
|
||||
const config = { locations: [] };
|
||||
const { privateLocations, publicLocations } = task.parseLocations(config as any);
|
||||
expect(privateLocations).toHaveLength(0);
|
||||
expect(publicLocations).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('runSynPrivateLocationMonitorsTaskSoon', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should schedule the task to run soon successfully', async () => {
|
||||
await runSynPrivateLocationMonitorsTaskSoon({ server: mockServerSetup as any });
|
||||
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith(
|
||||
'Scheduling Synthetics sync private location monitors task soon'
|
||||
);
|
||||
expect(mockTaskManagerStart.runSoon).toHaveBeenCalledWith(
|
||||
'Synthetics:Sync-Private-Location-Monitors-single-instance'
|
||||
);
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith(
|
||||
'Synthetics sync private location task scheduled successfully'
|
||||
);
|
||||
});
|
||||
|
||||
it('should log an error if scheduling fails', async () => {
|
||||
const error = new Error('Failed to run soon');
|
||||
mockTaskManagerStart.runSoon.mockRejectedValue(error);
|
||||
|
||||
await runSynPrivateLocationMonitorsTaskSoon({ server: mockServerSetup as any });
|
||||
|
||||
expect(mockLogger.error).toHaveBeenCalledWith(
|
||||
`Error scheduling Synthetics sync private location monitors task: ${error.message}`,
|
||||
{
|
||||
error,
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
|
@ -14,6 +14,7 @@ import { EncryptedSavedObjectsPluginStart } from '@kbn/encrypted-saved-objects-p
|
|||
import { ALL_SPACES_ID } from '@kbn/spaces-plugin/common/constants';
|
||||
import { ConcreteTaskInstance } from '@kbn/task-manager-plugin/server';
|
||||
import moment from 'moment';
|
||||
import { MAINTENANCE_WINDOW_SAVED_OBJECT_TYPE } from '@kbn/alerting-plugin/common';
|
||||
import { syntheticsParamType } from '../../common/types/saved_objects';
|
||||
import { normalizeSecrets } from '../synthetics_service/utils';
|
||||
import type { PrivateLocationAttributes } from '../runtime_types/private_locations';
|
||||
|
@ -33,13 +34,17 @@ import {
|
|||
|
||||
const TASK_TYPE = 'Synthetics:Sync-Private-Location-Monitors';
|
||||
const TASK_ID = `${TASK_TYPE}-single-instance`;
|
||||
const TASK_SCHEDULE = '5m';
|
||||
|
||||
interface TaskState extends Record<string, unknown> {
|
||||
lastStartedAt: string;
|
||||
lastTotalParams: number;
|
||||
lastTotalMWs: number;
|
||||
}
|
||||
|
||||
type CustomTaskInstance = Omit<ConcreteTaskInstance, 'state'> & { state: Partial<TaskState> };
|
||||
export type CustomTaskInstance = Omit<ConcreteTaskInstance, 'state'> & {
|
||||
state: Partial<TaskState>;
|
||||
};
|
||||
|
||||
export class SyncPrivateLocationMonitorsTask {
|
||||
constructor(
|
||||
|
@ -79,18 +84,23 @@ export class SyncPrivateLocationMonitorsTask {
|
|||
taskInstance.state.lastStartedAt || moment().subtract(10, 'minute').toISOString();
|
||||
const startedAt = taskInstance.startedAt || new Date();
|
||||
let lastTotalParams = taskInstance.state.lastTotalParams || 0;
|
||||
let lastTotalMWs = taskInstance.state.lastTotalMWs || 0;
|
||||
try {
|
||||
logger.debug(
|
||||
`Syncing private location monitors, last total params ${lastTotalParams}, last run ${lastStartedAt}`
|
||||
);
|
||||
const soClient = savedObjects.createInternalRepository();
|
||||
const soClient = savedObjects.createInternalRepository([
|
||||
MAINTENANCE_WINDOW_SAVED_OBJECT_TYPE,
|
||||
]);
|
||||
const allPrivateLocations = await getPrivateLocations(soClient);
|
||||
const { updatedParams, totalParams } = await this.hasAnyParamChanged(soClient, lastStartedAt);
|
||||
if (updatedParams > 0 || totalParams !== lastTotalParams) {
|
||||
lastTotalParams = totalParams;
|
||||
logger.debug(
|
||||
`Syncing private location monitors because params changed, updated params ${updatedParams}, total params ${totalParams}`
|
||||
);
|
||||
const { totalMWs, totalParams, hasDataChanged } = await this.hasAnyDataChanged({
|
||||
soClient,
|
||||
taskInstance,
|
||||
});
|
||||
lastTotalParams = totalParams;
|
||||
lastTotalMWs = totalMWs;
|
||||
if (hasDataChanged) {
|
||||
logger.debug(`Syncing private location monitors because data has changed`);
|
||||
|
||||
if (allPrivateLocations.length > 0) {
|
||||
await this.syncGlobalParams({
|
||||
|
@ -101,9 +111,8 @@ export class SyncPrivateLocationMonitorsTask {
|
|||
}
|
||||
logger.debug(`Sync of private location monitors succeeded`);
|
||||
} else {
|
||||
lastTotalParams = totalParams;
|
||||
logger.debug(
|
||||
`No params changed since last run ${lastStartedAt}, skipping sync of private location monitors`
|
||||
`No data has changed since last run ${lastStartedAt}, skipping sync of private location monitors`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
|
@ -113,6 +122,7 @@ export class SyncPrivateLocationMonitorsTask {
|
|||
state: {
|
||||
lastStartedAt: startedAt.toISOString(),
|
||||
lastTotalParams,
|
||||
lastTotalMWs,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -120,6 +130,7 @@ export class SyncPrivateLocationMonitorsTask {
|
|||
state: {
|
||||
lastStartedAt: startedAt.toISOString(),
|
||||
lastTotalParams,
|
||||
lastTotalMWs,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -134,7 +145,7 @@ export class SyncPrivateLocationMonitorsTask {
|
|||
id: TASK_ID,
|
||||
state: {},
|
||||
schedule: {
|
||||
interval: '10m',
|
||||
interval: TASK_SCHEDULE,
|
||||
},
|
||||
taskType: TASK_TYPE,
|
||||
params: {},
|
||||
|
@ -142,6 +153,32 @@ export class SyncPrivateLocationMonitorsTask {
|
|||
logger.debug(`Sync private location monitors task scheduled successfully`);
|
||||
};
|
||||
|
||||
hasAnyDataChanged = async ({
|
||||
taskInstance,
|
||||
soClient,
|
||||
}: {
|
||||
taskInstance: CustomTaskInstance;
|
||||
soClient: SavedObjectsClientContract;
|
||||
}) => {
|
||||
const lastStartedAt =
|
||||
taskInstance.state.lastStartedAt || moment().subtract(10, 'minute').toISOString();
|
||||
const lastTotalParams = taskInstance.state.lastTotalParams || 0;
|
||||
const lastTotalMWs = taskInstance.state.lastTotalMWs || 0;
|
||||
|
||||
const { totalParams, hasParamsChanges } = await this.hasAnyParamChanged({
|
||||
soClient,
|
||||
lastStartedAt,
|
||||
lastTotalParams,
|
||||
});
|
||||
const { totalMWs, hasMWsChanged } = await this.hasMWsChanged({
|
||||
soClient,
|
||||
lastStartedAt,
|
||||
lastTotalMWs,
|
||||
});
|
||||
const hasDataChanged = hasMWsChanged || hasParamsChanges;
|
||||
return { hasDataChanged, totalParams, totalMWs };
|
||||
};
|
||||
|
||||
async syncGlobalParams({
|
||||
allPrivateLocations,
|
||||
encryptedSavedObjects,
|
||||
|
@ -155,10 +192,11 @@ export class SyncPrivateLocationMonitorsTask {
|
|||
const privateConfigs: Array<{ config: HeartbeatConfig; globalParams: Record<string, string> }> =
|
||||
[];
|
||||
|
||||
const { configsBySpaces, paramsBySpace, spaceIds } = await this.getAllMonitorConfigs({
|
||||
encryptedSavedObjects,
|
||||
soClient,
|
||||
});
|
||||
const { configsBySpaces, paramsBySpace, spaceIds, maintenanceWindows } =
|
||||
await this.getAllMonitorConfigs({
|
||||
encryptedSavedObjects,
|
||||
soClient,
|
||||
});
|
||||
|
||||
for (const spaceId of spaceIds) {
|
||||
const monitors = configsBySpaces[spaceId];
|
||||
|
@ -173,7 +211,12 @@ export class SyncPrivateLocationMonitorsTask {
|
|||
}
|
||||
}
|
||||
if (privateConfigs.length > 0) {
|
||||
await privateLocationAPI.editMonitors(privateConfigs, allPrivateLocations, spaceId);
|
||||
await privateLocationAPI.editMonitors(
|
||||
privateConfigs,
|
||||
allPrivateLocations,
|
||||
spaceId,
|
||||
maintenanceWindows
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -187,6 +230,7 @@ export class SyncPrivateLocationMonitorsTask {
|
|||
}) {
|
||||
const { syntheticsService } = this.syntheticsMonitorClient;
|
||||
const paramsBySpacePromise = syntheticsService.getSyntheticsParams({ spaceId: ALL_SPACES_ID });
|
||||
const maintenanceWindowsPromise = syntheticsService.getMaintenanceWindows();
|
||||
const monitorConfigRepository = new MonitorConfigRepository(
|
||||
soClient,
|
||||
encryptedSavedObjects.getClient()
|
||||
|
@ -196,11 +240,16 @@ export class SyncPrivateLocationMonitorsTask {
|
|||
spaceId: ALL_SPACES_ID,
|
||||
});
|
||||
|
||||
const [paramsBySpace, monitors] = await Promise.all([paramsBySpacePromise, monitorsPromise]);
|
||||
const [paramsBySpace, monitors, maintenanceWindows] = await Promise.all([
|
||||
paramsBySpacePromise,
|
||||
monitorsPromise,
|
||||
maintenanceWindowsPromise,
|
||||
]);
|
||||
|
||||
return {
|
||||
...this.mixParamsWithMonitors(monitors, paramsBySpace),
|
||||
paramsBySpace,
|
||||
maintenanceWindows,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -251,7 +300,15 @@ export class SyncPrivateLocationMonitorsTask {
|
|||
return { configsBySpaces, spaceIds };
|
||||
}
|
||||
|
||||
async hasAnyParamChanged(soClient: SavedObjectsClientContract, lastStartedAt: string) {
|
||||
async hasAnyParamChanged({
|
||||
soClient,
|
||||
lastStartedAt,
|
||||
lastTotalParams,
|
||||
}: {
|
||||
soClient: SavedObjectsClientContract;
|
||||
lastStartedAt: string;
|
||||
lastTotalParams: number;
|
||||
}) {
|
||||
const { logger } = this.serverSetup;
|
||||
const [editedParams, totalParams] = await Promise.all([
|
||||
soClient.find({
|
||||
|
@ -268,10 +325,59 @@ export class SyncPrivateLocationMonitorsTask {
|
|||
fields: [],
|
||||
}),
|
||||
]);
|
||||
logger.debug(`Found ${editedParams.total} params and ${totalParams.total} total params`);
|
||||
logger.debug(
|
||||
`Found ${editedParams.total} params updated and ${totalParams.total} total params`
|
||||
);
|
||||
const updatedParams = editedParams.total;
|
||||
const noOfParams = totalParams.total;
|
||||
|
||||
const hasParamsChanges = updatedParams > 0 || noOfParams !== lastTotalParams;
|
||||
|
||||
return {
|
||||
hasParamsChanges,
|
||||
updatedParams: editedParams.total,
|
||||
totalParams: totalParams.total,
|
||||
totalParams: noOfParams,
|
||||
};
|
||||
}
|
||||
|
||||
async hasMWsChanged({
|
||||
soClient,
|
||||
lastStartedAt,
|
||||
lastTotalMWs,
|
||||
}: {
|
||||
soClient: SavedObjectsClientContract;
|
||||
lastStartedAt: string;
|
||||
lastTotalMWs: number;
|
||||
}) {
|
||||
const { logger } = this.serverSetup;
|
||||
|
||||
const [editedMWs, totalMWs] = await Promise.all([
|
||||
soClient.find({
|
||||
type: MAINTENANCE_WINDOW_SAVED_OBJECT_TYPE,
|
||||
perPage: 0,
|
||||
namespaces: [ALL_SPACES_ID],
|
||||
filter: `${MAINTENANCE_WINDOW_SAVED_OBJECT_TYPE}.updated_at > "${lastStartedAt}"`,
|
||||
fields: [],
|
||||
}),
|
||||
soClient.find({
|
||||
type: MAINTENANCE_WINDOW_SAVED_OBJECT_TYPE,
|
||||
perPage: 0,
|
||||
namespaces: [ALL_SPACES_ID],
|
||||
fields: [],
|
||||
}),
|
||||
]);
|
||||
logger.debug(
|
||||
`Found ${editedMWs.total} maintenance windows updated and ${totalMWs.total} total maintenance windows`
|
||||
);
|
||||
const updatedMWs = editedMWs.total;
|
||||
const noOfMWs = totalMWs.total;
|
||||
|
||||
const hasMWsChanged = updatedMWs > 0 || noOfMWs !== lastTotalMWs;
|
||||
|
||||
return {
|
||||
hasMWsChanged,
|
||||
updatedMWs,
|
||||
totalMWs: noOfMWs,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -112,7 +112,9 @@
|
|||
"@kbn/charts-plugin",
|
||||
"@kbn/response-ops-rule-params",
|
||||
"@kbn/response-ops-rule-form",
|
||||
"@kbn/fields-metadata-plugin"
|
||||
"@kbn/fields-metadata-plugin",
|
||||
"@kbn/alerts-ui-shared",
|
||||
"@kbn/core-lifecycle-server"
|
||||
],
|
||||
"exclude": ["target/**/*"]
|
||||
}
|
||||
|
|
|
@ -229,6 +229,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
|
|||
updated_at: decryptedCreatedMonitor.rawBody.updated_at,
|
||||
created_at: decryptedCreatedMonitor.rawBody.created_at,
|
||||
labels: {},
|
||||
maintenance_windows: [],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -357,6 +358,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
|
|||
ipv4: true,
|
||||
max_attempts: 2,
|
||||
labels: {},
|
||||
maintenance_windows: [],
|
||||
updated_at: decryptedCreatedMonitor.updated_at,
|
||||
created_at: decryptedCreatedMonitor.created_at,
|
||||
});
|
||||
|
@ -473,6 +475,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
|
|||
params: '',
|
||||
max_attempts: 2,
|
||||
labels: {},
|
||||
maintenance_windows: [],
|
||||
updated_at: decryptedCreatedMonitor.updated_at,
|
||||
created_at: decryptedCreatedMonitor.created_at,
|
||||
});
|
||||
|
@ -578,6 +581,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
|
|||
updated_at: decryptedCreatedMonitor.updated_at,
|
||||
created_at: decryptedCreatedMonitor.created_at,
|
||||
labels: {},
|
||||
maintenance_windows: [],
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
|
|
|
@ -301,6 +301,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
|
|||
updated_at: decryptedCreatedMonitor.rawBody.updated_at,
|
||||
created_at: decryptedCreatedMonitor.rawBody.created_at,
|
||||
labels: {},
|
||||
maintenance_windows: [],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -482,6 +483,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
|
|||
ipv4: true,
|
||||
max_attempts: 2,
|
||||
labels: {},
|
||||
maintenance_windows: [],
|
||||
updated_at: decryptedCreatedMonitor.updated_at,
|
||||
created_at: decryptedCreatedMonitor.created_at,
|
||||
});
|
||||
|
@ -601,6 +603,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
|
|||
params: '',
|
||||
max_attempts: 2,
|
||||
labels: {},
|
||||
maintenance_windows: [],
|
||||
updated_at: decryptedCreatedMonitor.updated_at,
|
||||
created_at: decryptedCreatedMonitor.created_at,
|
||||
});
|
||||
|
@ -709,6 +712,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
|
|||
updated_at: decryptedCreatedMonitor.updated_at,
|
||||
created_at: decryptedCreatedMonitor.created_at,
|
||||
labels: {},
|
||||
maintenance_windows: [],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -56,5 +56,6 @@
|
|||
"ssl.verification_mode": "full",
|
||||
"revision": 1,
|
||||
"max_attempts": 2,
|
||||
"labels": {}
|
||||
"labels": {},
|
||||
"maintenance_windows": []
|
||||
}
|
||||
|
|
|
@ -79,5 +79,6 @@
|
|||
"ipv4": true,
|
||||
"ipv6": true,
|
||||
"params": "",
|
||||
"labels": {}
|
||||
"labels": {},
|
||||
"maintenance_windows": []
|
||||
}
|
||||
|
|
|
@ -31,5 +31,6 @@
|
|||
"ipv4": true,
|
||||
"ipv6": true,
|
||||
"params": "",
|
||||
"max_attempts": 2
|
||||
"max_attempts": 2,
|
||||
"maintenance_windows": []
|
||||
}
|
||||
|
|
|
@ -39,5 +39,6 @@
|
|||
"ipv4": true,
|
||||
"ipv6": true,
|
||||
"params": "",
|
||||
"max_attempts": 2
|
||||
"max_attempts": 2,
|
||||
"maintenance_windows": []
|
||||
}
|
||||
|
|
|
@ -322,6 +322,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
|
|||
journey_id: '',
|
||||
max_attempts: 2,
|
||||
labels: {},
|
||||
maintenance_windows: [],
|
||||
},
|
||||
['config_id', 'id', 'form_monitor_type']
|
||||
)
|
||||
|
|
|
@ -240,6 +240,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
|
|||
},
|
||||
},
|
||||
],
|
||||
maintenance_windows: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -313,6 +313,7 @@ export const getHttpInput = ({
|
|||
},
|
||||
},
|
||||
],
|
||||
maintenance_windows: null,
|
||||
};
|
||||
|
||||
return {
|
||||
|
|
|
@ -16,6 +16,7 @@ export const commonVars = {
|
|||
},
|
||||
maintenance_windows: {
|
||||
type: 'yaml',
|
||||
value: [],
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -295,6 +296,7 @@ export const getTestProjectSyntheticsPolicyLightweight = (
|
|||
},
|
||||
},
|
||||
],
|
||||
maintenance_windows: null,
|
||||
},
|
||||
id: `synthetics/http-http-4b6abc6c-118b-4d93-a489-1135500d09f1-${projectId}-default-d70a46e0-22ea-11ed-8c6b-09a2d21dfbc3`,
|
||||
},
|
||||
|
@ -601,6 +603,9 @@ export const getTestProjectSyntheticsPolicy = (
|
|||
ipv4: { type: 'bool', value: true },
|
||||
ipv6: { type: 'bool', value: true },
|
||||
mode: { type: 'text' },
|
||||
maintenance_windows: {
|
||||
type: 'yaml',
|
||||
},
|
||||
},
|
||||
id: `synthetics/http-http-4b6abc6c-118b-4d93-a489-1135500d09f1-${projectId}-default-d70a46e0-22ea-11ed-8c6b-09a2d21dfbc3`,
|
||||
},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue