mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51: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",
|
||||||
"locations.id",
|
"locations.id",
|
||||||
"locations.label",
|
"locations.label",
|
||||||
|
"maintenance_windows",
|
||||||
"name",
|
"name",
|
||||||
"origin",
|
"origin",
|
||||||
"project_id",
|
"project_id",
|
||||||
|
|
|
@ -3757,6 +3757,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"maintenance_windows": {
|
||||||
|
"type": "keyword"
|
||||||
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"fields": {
|
"fields": {
|
||||||
"keyword": {
|
"keyword": {
|
||||||
|
|
|
@ -176,7 +176,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
|
||||||
"space": "953a72d8962d829e7ea465849297c5e44d8e9a2d",
|
"space": "953a72d8962d829e7ea465849297c5e44d8e9a2d",
|
||||||
"spaces-usage-stats": "3abca98713c52af8b30300e386c7779b3025a20e",
|
"spaces-usage-stats": "3abca98713c52af8b30300e386c7779b3025a20e",
|
||||||
"synthetics-dynamic-settings": "4b40a93eb3e222619bf4e7fe34a9b9e7ab91a0a7",
|
"synthetics-dynamic-settings": "4b40a93eb3e222619bf4e7fe34a9b9e7ab91a0a7",
|
||||||
"synthetics-monitor": "5ceb25b6249bd26902c9b34273c71c3dce06dbea",
|
"synthetics-monitor": "078401644f1a6cecdd4294093df20f8ff4063405",
|
||||||
"synthetics-param": "3ebb744e5571de678b1312d5c418c8188002cf5e",
|
"synthetics-param": "3ebb744e5571de678b1312d5c418c8188002cf5e",
|
||||||
"synthetics-private-location": "8cecc9e4f39637d2f8244eb7985c0690ceab24be",
|
"synthetics-private-location": "8cecc9e4f39637d2f8244eb7985c0690ceab24be",
|
||||||
"synthetics-privates-locations": "f53d799d5c9bc8454aaa32c6abc99a899b025d5c",
|
"synthetics-privates-locations": "f53d799d5c9bc8454aaa32c6abc99a899b025d5c",
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
export { AlertLifecycleStatusBadge } from './src/alert_lifecycle_status_badge';
|
export { AlertLifecycleStatusBadge } from './src/alert_lifecycle_status_badge';
|
||||||
export type { AlertLifecycleStatusBadgeProps } from './src/alert_lifecycle_status_badge';
|
export type { AlertLifecycleStatusBadgeProps } from './src/alert_lifecycle_status_badge';
|
||||||
export { MaintenanceWindowCallout } from './src/maintenance_window_callout';
|
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 { AddMessageVariables } from './src/add_message_variables';
|
||||||
|
|
||||||
export * from './src/common/hooks';
|
export * from './src/common/hooks';
|
||||||
|
|
|
@ -154,6 +154,7 @@ export const DEFAULT_COMMON_FIELDS: CommonFields = {
|
||||||
[ConfigKey.PARAMS]: '',
|
[ConfigKey.PARAMS]: '',
|
||||||
[ConfigKey.LABELS]: {},
|
[ConfigKey.LABELS]: {},
|
||||||
[ConfigKey.MAX_ATTEMPTS]: 2,
|
[ConfigKey.MAX_ATTEMPTS]: 2,
|
||||||
|
[ConfigKey.MAINTENANCE_WINDOWS]: [],
|
||||||
revision: 1,
|
revision: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -78,6 +78,7 @@ export enum ConfigKey {
|
||||||
WAIT = 'wait',
|
WAIT = 'wait',
|
||||||
MONITOR_QUERY_ID = 'id',
|
MONITOR_QUERY_ID = 'id',
|
||||||
MAX_ATTEMPTS = 'max_attempts',
|
MAX_ATTEMPTS = 'max_attempts',
|
||||||
|
MAINTENANCE_WINDOWS = 'maintenance_windows',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const secretKeys = [
|
export const secretKeys = [
|
||||||
|
|
|
@ -46,6 +46,7 @@ export enum SYNTHETICS_API_URLS {
|
||||||
CERTS = '/internal/synthetics/certs',
|
CERTS = '/internal/synthetics/certs',
|
||||||
|
|
||||||
SUGGESTIONS = `/internal/synthetics/suggestions`,
|
SUGGESTIONS = `/internal/synthetics/suggestions`,
|
||||||
|
MAINTENANCE_WINDOWS = `/internal/synthetics/monitors/maintenance_windows`,
|
||||||
|
|
||||||
// Project monitor public endpoint
|
// Project monitor public endpoint
|
||||||
SYNTHETICS_MONITORS_PROJECT = '/api/synthetics/project/{projectName}/monitors',
|
SYNTHETICS_MONITORS_PROJECT = '/api/synthetics/project/{projectName}/monitors',
|
||||||
|
|
|
@ -87,6 +87,7 @@ export const CommonFieldsCodec = t.intersection([
|
||||||
[ConfigKey.ALERT_CONFIG]: AlertConfigsCodec,
|
[ConfigKey.ALERT_CONFIG]: AlertConfigsCodec,
|
||||||
[ConfigKey.PARAMS]: t.string,
|
[ConfigKey.PARAMS]: t.string,
|
||||||
[ConfigKey.LABELS]: t.record(t.string, t.string),
|
[ConfigKey.LABELS]: t.record(t.string, t.string),
|
||||||
|
[ConfigKey.MAINTENANCE_WINDOWS]: t.array(t.string),
|
||||||
retest_on_failure: t.boolean,
|
retest_on_failure: t.boolean,
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -64,6 +64,7 @@ export const ProjectMonitorCodec = t.intersection([
|
||||||
retestOnFailure: t.boolean,
|
retestOnFailure: t.boolean,
|
||||||
fields: t.record(t.string, t.string),
|
fields: t.record(t.string, t.string),
|
||||||
'service.name': t.string,
|
'service.name': t.string,
|
||||||
|
maintenanceWindows: t.array(t.string),
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
@ -54,6 +54,7 @@ export const OverviewStatusMetaDataCodec = t.intersection([
|
||||||
timestamp: t.string,
|
timestamp: t.string,
|
||||||
spaceId: t.string,
|
spaceId: t.string,
|
||||||
urls: 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,
|
component: Component,
|
||||||
helpText,
|
helpText,
|
||||||
label,
|
label,
|
||||||
|
labelAppend,
|
||||||
ariaLabel,
|
ariaLabel,
|
||||||
props,
|
props,
|
||||||
fieldKey,
|
fieldKey,
|
||||||
|
@ -57,6 +58,7 @@ export const Field = memo<Props>(
|
||||||
'aria-label': ariaLabel,
|
'aria-label': ariaLabel,
|
||||||
helpText,
|
helpText,
|
||||||
fullWidth: true,
|
fullWidth: true,
|
||||||
|
labelAppend,
|
||||||
};
|
};
|
||||||
|
|
||||||
return controlled ? (
|
return controlled ? (
|
||||||
|
|
|
@ -30,6 +30,8 @@ import {
|
||||||
EuiBadge,
|
EuiBadge,
|
||||||
EuiToolTip,
|
EuiToolTip,
|
||||||
} from '@elastic/eui';
|
} 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 { kibanaService } from '../../../../../utils/kibana_service';
|
||||||
import {
|
import {
|
||||||
PROFILE_OPTIONS,
|
PROFILE_OPTIONS,
|
||||||
|
@ -60,6 +62,7 @@ import {
|
||||||
KeyValuePairsField,
|
KeyValuePairsField,
|
||||||
TextArea,
|
TextArea,
|
||||||
ThrottlingWrapper,
|
ThrottlingWrapper,
|
||||||
|
MaintenanceWindowsFieldWrapper,
|
||||||
} from './field_wrappers';
|
} from './field_wrappers';
|
||||||
import { useMonitorName } from '../../../hooks/use_monitor_name';
|
import { useMonitorName } from '../../../hooks/use_monitor_name';
|
||||||
import {
|
import {
|
||||||
|
@ -1676,4 +1679,26 @@ export const FIELD = (readOnly?: boolean): FieldMap => ({
|
||||||
'data-test-subj': 'syntheticsEnableAttemptSwitch',
|
'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,
|
EuiTextArea,
|
||||||
EuiTextAreaProps,
|
EuiTextAreaProps,
|
||||||
} from '@elastic/eui';
|
} from '@elastic/eui';
|
||||||
|
import {
|
||||||
|
MaintenanceWindowsField,
|
||||||
|
MaintenanceWindowsFieldProps,
|
||||||
|
} from '../fields/maintenance_windows/maintenance_windows';
|
||||||
import {
|
import {
|
||||||
ThrottlingConfigField,
|
ThrottlingConfigField,
|
||||||
ThrottlingConfigFieldProps,
|
ThrottlingConfigFieldProps,
|
||||||
|
@ -154,3 +158,8 @@ export const ResponseBodyIndexField = React.forwardRef<unknown, DefaultResponseB
|
||||||
export const ThrottlingWrapper = React.forwardRef<unknown, ThrottlingConfigFieldProps>(
|
export const ThrottlingWrapper = React.forwardRef<unknown, ThrottlingConfigFieldProps>(
|
||||||
(props, _ref) => <ThrottlingConfigField {...props} />
|
(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) => ({
|
const HTTP_ADVANCED = (readOnly: boolean) => ({
|
||||||
requestConfig: {
|
requestConfig: {
|
||||||
title: i18n.translate('xpack.synthetics.monitorConfig.section.requestConfiguration.title', {
|
title: i18n.translate('xpack.synthetics.monitorConfig.section.requestConfiguration.title', {
|
||||||
|
@ -206,6 +220,7 @@ export const FORM_CONFIG = (readOnly: boolean): FieldConfig => ({
|
||||||
],
|
],
|
||||||
advanced: [
|
advanced: [
|
||||||
DEFAULT_DATA_OPTIONS(readOnly),
|
DEFAULT_DATA_OPTIONS(readOnly),
|
||||||
|
MAINTENANCE_WINDOWS_OPTIONS(readOnly),
|
||||||
HTTP_ADVANCED(readOnly).requestConfig,
|
HTTP_ADVANCED(readOnly).requestConfig,
|
||||||
HTTP_ADVANCED(readOnly).responseConfig,
|
HTTP_ADVANCED(readOnly).responseConfig,
|
||||||
HTTP_ADVANCED(readOnly).responseChecks,
|
HTTP_ADVANCED(readOnly).responseChecks,
|
||||||
|
@ -227,6 +242,7 @@ export const FORM_CONFIG = (readOnly: boolean): FieldConfig => ({
|
||||||
],
|
],
|
||||||
advanced: [
|
advanced: [
|
||||||
DEFAULT_DATA_OPTIONS(readOnly),
|
DEFAULT_DATA_OPTIONS(readOnly),
|
||||||
|
MAINTENANCE_WINDOWS_OPTIONS(readOnly),
|
||||||
TCP_ADVANCED(readOnly).requestConfig,
|
TCP_ADVANCED(readOnly).requestConfig,
|
||||||
TCP_ADVANCED(readOnly).responseChecks,
|
TCP_ADVANCED(readOnly).responseChecks,
|
||||||
TLS_OPTIONS(readOnly),
|
TLS_OPTIONS(readOnly),
|
||||||
|
@ -255,6 +271,7 @@ export const FORM_CONFIG = (readOnly: boolean): FieldConfig => ({
|
||||||
FIELD(readOnly)[ConfigKey.NAMESPACE],
|
FIELD(readOnly)[ConfigKey.NAMESPACE],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
MAINTENANCE_WINDOWS_OPTIONS(readOnly),
|
||||||
...BROWSER_ADVANCED(readOnly),
|
...BROWSER_ADVANCED(readOnly),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -281,6 +298,7 @@ export const FORM_CONFIG = (readOnly: boolean): FieldConfig => ({
|
||||||
FIELD(readOnly)[ConfigKey.NAMESPACE],
|
FIELD(readOnly)[ConfigKey.NAMESPACE],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
MAINTENANCE_WINDOWS_OPTIONS(readOnly),
|
||||||
...BROWSER_ADVANCED(readOnly),
|
...BROWSER_ADVANCED(readOnly),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -297,6 +315,10 @@ export const FORM_CONFIG = (readOnly: boolean): FieldConfig => ({
|
||||||
FIELD(readOnly)[ConfigKey.MAX_ATTEMPTS],
|
FIELD(readOnly)[ConfigKey.MAX_ATTEMPTS],
|
||||||
FIELD(readOnly)[AlertConfigKey.STATUS_ENABLED],
|
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;
|
fieldKey: keyof FormConfig;
|
||||||
component: React.ComponentType<any>;
|
component: React.ComponentType<any>;
|
||||||
label?: string | React.ReactNode;
|
label?: string | React.ReactNode;
|
||||||
|
labelAppend?: React.ReactNode;
|
||||||
ariaLabel?: string;
|
ariaLabel?: string;
|
||||||
helpText?: string | React.ReactNode;
|
helpText?: string | React.ReactNode;
|
||||||
hidden?: (depenencies: unknown[]) => boolean;
|
hidden?: (dependencies: unknown[]) => boolean;
|
||||||
props?: (params: {
|
props?: (params: {
|
||||||
field?: ControllerRenderProps<FormConfig, TFieldKey>;
|
field?: ControllerRenderProps<FormConfig, TFieldKey>;
|
||||||
formState: FormState<FormConfig>;
|
formState: FormState<FormConfig>;
|
||||||
|
@ -166,4 +167,5 @@ export interface FieldMap {
|
||||||
[ConfigKey.IPV4]: FieldMeta<ConfigKey.IPV4>;
|
[ConfigKey.IPV4]: FieldMeta<ConfigKey.IPV4>;
|
||||||
[ConfigKey.MAX_ATTEMPTS]: FieldMeta<ConfigKey.MAX_ATTEMPTS>;
|
[ConfigKey.MAX_ATTEMPTS]: FieldMeta<ConfigKey.MAX_ATTEMPTS>;
|
||||||
[ConfigKey.LABELS]: FieldMeta<ConfigKey.LABELS>;
|
[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 { EuiTitle, EuiPanel, EuiFlexGroup, EuiFlexItem, EuiText, EuiSpacer } from '@elastic/eui';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { LoadWhenInView } from '@kbn/observability-shared-plugin/public';
|
import { LoadWhenInView } from '@kbn/observability-shared-plugin/public';
|
||||||
|
import { MonitorMWsCallout } from '../../common/mws_callout/monitor_mws_callout';
|
||||||
import { SummaryPanel } from './summary_panel';
|
import { SummaryPanel } from './summary_panel';
|
||||||
|
|
||||||
import { useMonitorDetailsPage } from '../use_monitor_details_page';
|
import { useMonitorDetailsPage } from '../use_monitor_details_page';
|
||||||
|
@ -34,6 +35,7 @@ export const MonitorSummary = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MonitorPendingWrapper>
|
<MonitorPendingWrapper>
|
||||||
|
<MonitorMWsCallout />
|
||||||
<SummaryPanel dateLabel={dateLabel} from={from} to={to} />
|
<SummaryPanel dateLabel={dateLabel} from={from} to={to} />
|
||||||
<EuiSpacer size="m" />
|
<EuiSpacer size="m" />
|
||||||
<EuiFlexGroup gutterSize="m" wrap={true} responsive={false}>
|
<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 { Redirect } from 'react-router-dom';
|
||||||
import { useTrackPageview } from '@kbn/observability-shared-plugin/public';
|
import { useTrackPageview } from '@kbn/observability-shared-plugin/public';
|
||||||
|
|
||||||
|
import { MonitorsMWsCallout } from '../common/mws_callout/monitors_mws_callout';
|
||||||
import { DisabledCallout } from './management/disabled_callout';
|
import { DisabledCallout } from './management/disabled_callout';
|
||||||
import { useOverviewStatus } from './hooks/use_overview_status';
|
import { useOverviewStatus } from './hooks/use_overview_status';
|
||||||
import { GETTING_STARTED_ROUTE } from '../../../../../common/constants';
|
import { GETTING_STARTED_ROUTE } from '../../../../../common/constants';
|
||||||
|
@ -54,6 +55,7 @@ export const MonitorManagementPage: React.FC = () => {
|
||||||
errorBody={labels.ERROR_HEADING_BODY}
|
errorBody={labels.ERROR_HEADING_BODY}
|
||||||
>
|
>
|
||||||
<DisabledCallout total={absoluteTotal} />
|
<DisabledCallout total={absoluteTotal} />
|
||||||
|
<MonitorsMWsCallout />
|
||||||
<MonitorListContainer isEnabled={isEnabled} monitorListProps={monitorListProps} />
|
<MonitorListContainer isEnabled={isEnabled} monitorListProps={monitorListProps} />
|
||||||
</Loader>
|
</Loader>
|
||||||
{showEmptyState && <EnablementEmptyState />}
|
{showEmptyState && <EnablementEmptyState />}
|
||||||
|
|
|
@ -20,11 +20,13 @@ import {
|
||||||
EuiLink,
|
EuiLink,
|
||||||
EuiSpacer,
|
EuiSpacer,
|
||||||
EuiSkeletonText,
|
EuiSkeletonText,
|
||||||
|
EuiIcon,
|
||||||
} from '@elastic/eui';
|
} from '@elastic/eui';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
|
|
||||||
|
import { useMonitorMWs } from '../../../hooks/use_monitor_mws';
|
||||||
import { MetricErrorIcon } from './metric_error_icon';
|
import { MetricErrorIcon } from './metric_error_icon';
|
||||||
import { OverviewStatusMetaData } from '../../../../../../../../common/runtime_types';
|
import { OverviewStatusMetaData } from '../../../../../../../../common/runtime_types';
|
||||||
import { isTestRunning, manualTestRunSelector } from '../../../../../state/manual_test_runs';
|
import { isTestRunning, manualTestRunSelector } from '../../../../../state/manual_test_runs';
|
||||||
|
@ -61,6 +63,7 @@ export const MetricItemIcon = ({
|
||||||
});
|
});
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const { activeMWs } = useMonitorMWs(monitor);
|
||||||
|
|
||||||
const inProgress = isTestRunning(testNowRun);
|
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 = () => {
|
const closePopover = () => {
|
||||||
dispatch(toggleErrorPopoverOpen(null));
|
dispatch(toggleErrorPopoverOpen(null));
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { Provider as ReduxProvider } from 'react-redux';
|
||||||
import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app';
|
import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { Store } from 'redux';
|
import { Store } from 'redux';
|
||||||
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
import { SyntheticsRefreshContextProvider } from './synthetics_refresh_context';
|
import { SyntheticsRefreshContextProvider } from './synthetics_refresh_context';
|
||||||
import { SyntheticsDataViewContextProvider } from './synthetics_data_view_context';
|
import { SyntheticsDataViewContextProvider } from './synthetics_data_view_context';
|
||||||
import { SyntheticsAppProps } from './synthetics_settings_context';
|
import { SyntheticsAppProps } from './synthetics_settings_context';
|
||||||
|
@ -20,6 +21,8 @@ import { storage, store } from '../state';
|
||||||
export const SyntheticsSharedContext: React.FC<
|
export const SyntheticsSharedContext: React.FC<
|
||||||
React.PropsWithChildren<SyntheticsAppProps & { reload$?: Subject<boolean>; reduxStore?: Store }>
|
React.PropsWithChildren<SyntheticsAppProps & { reload$?: Subject<boolean>; reduxStore?: Store }>
|
||||||
> = ({ reduxStore, coreStart, setupPlugins, startPlugins, children, darkMode, reload$ }) => {
|
> = ({ reduxStore, coreStart, setupPlugins, startPlugins, children, darkMode, reload$ }) => {
|
||||||
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<KibanaContextProvider
|
<KibanaContextProvider
|
||||||
services={{
|
services={{
|
||||||
|
@ -46,20 +49,22 @@ export const SyntheticsSharedContext: React.FC<
|
||||||
>
|
>
|
||||||
<EuiThemeProvider darkMode={darkMode}>
|
<EuiThemeProvider darkMode={darkMode}>
|
||||||
<ReduxProvider store={reduxStore ?? store}>
|
<ReduxProvider store={reduxStore ?? store}>
|
||||||
<SyntheticsRefreshContextProvider reload$={reload$}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<SyntheticsDataViewContextProvider dataViews={startPlugins.dataViews}>
|
<SyntheticsRefreshContextProvider reload$={reload$}>
|
||||||
<RedirectAppLinks
|
<SyntheticsDataViewContextProvider dataViews={startPlugins.dataViews}>
|
||||||
coreStart={{
|
<RedirectAppLinks
|
||||||
application: coreStart.application,
|
coreStart={{
|
||||||
}}
|
application: coreStart.application,
|
||||||
style={{
|
}}
|
||||||
height: '100%',
|
style={{
|
||||||
}}
|
height: '100%',
|
||||||
>
|
}}
|
||||||
{children}
|
>
|
||||||
</RedirectAppLinks>
|
{children}
|
||||||
</SyntheticsDataViewContextProvider>
|
</RedirectAppLinks>
|
||||||
</SyntheticsRefreshContextProvider>
|
</SyntheticsDataViewContextProvider>
|
||||||
|
</SyntheticsRefreshContextProvider>
|
||||||
|
</QueryClientProvider>
|
||||||
</ReduxProvider>
|
</ReduxProvider>
|
||||||
</EuiThemeProvider>
|
</EuiThemeProvider>
|
||||||
</KibanaContextProvider>
|
</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 { all, fork } from 'redux-saga/effects';
|
||||||
|
import { getMaintenanceWindowsEffect } from './maintenance_windows';
|
||||||
import { getCertsListEffect } from './certs';
|
import { getCertsListEffect } from './certs';
|
||||||
import {
|
import {
|
||||||
addGlobalParamEffect,
|
addGlobalParamEffect,
|
||||||
|
@ -82,6 +83,7 @@ export const rootEffect = function* root(): Generator {
|
||||||
fork(refreshOverviewTrendStats),
|
fork(refreshOverviewTrendStats),
|
||||||
fork(inspectStatusRuleEffect),
|
fork(inspectStatusRuleEffect),
|
||||||
fork(inspectTLSRuleEffect),
|
fork(inspectTLSRuleEffect),
|
||||||
|
fork(getMaintenanceWindowsEffect),
|
||||||
...privateLocationsEffects.map((effect) => fork(effect)),
|
...privateLocationsEffects.map((effect) => fork(effect)),
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
import { combineReducers } from '@reduxjs/toolkit';
|
import { combineReducers } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
import { maintenanceWindowsReducer, MaintenanceWindowsState } from './maintenance_windows';
|
||||||
import { certsListReducer, CertsListState } from './certs';
|
import { certsListReducer, CertsListState } from './certs';
|
||||||
import { certificatesReducer, CertificatesState } from './certificates/certificates';
|
import { certificatesReducer, CertificatesState } from './certificates/certificates';
|
||||||
import { globalParamsReducer, GlobalParamsState } from './global_params';
|
import { globalParamsReducer, GlobalParamsState } from './global_params';
|
||||||
|
@ -48,6 +49,7 @@ export interface SyntheticsAppState {
|
||||||
serviceLocations: ServiceLocationsState;
|
serviceLocations: ServiceLocationsState;
|
||||||
syntheticsEnablement: SyntheticsEnablementState;
|
syntheticsEnablement: SyntheticsEnablementState;
|
||||||
ui: UiState;
|
ui: UiState;
|
||||||
|
maintenanceWindows: MaintenanceWindowsState;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const rootReducer = combineReducers<SyntheticsAppState>({
|
export const rootReducer = combineReducers<SyntheticsAppState>({
|
||||||
|
@ -70,4 +72,5 @@ export const rootReducer = combineReducers<SyntheticsAppState>({
|
||||||
serviceLocations: serviceLocationsReducer,
|
serviceLocations: serviceLocationsReducer,
|
||||||
syntheticsEnablement: syntheticsEnablementReducer,
|
syntheticsEnablement: syntheticsEnablementReducer,
|
||||||
ui: uiReducer,
|
ui: uiReducer,
|
||||||
|
maintenanceWindows: maintenanceWindowsReducer,
|
||||||
});
|
});
|
||||||
|
|
|
@ -163,6 +163,7 @@ export const mockState: SyntheticsAppState = {
|
||||||
loading: false,
|
loading: false,
|
||||||
error: null,
|
error: null,
|
||||||
},
|
},
|
||||||
|
maintenanceWindows: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
function getBrowserJourneyMockSlice() {
|
function getBrowserJourneyMockSlice() {
|
||||||
|
|
|
@ -108,6 +108,7 @@ describe('AddNewMonitorsPublicAPI', () => {
|
||||||
'url.port': null,
|
'url.port': null,
|
||||||
urls: '',
|
urls: '',
|
||||||
labels: {},
|
labels: {},
|
||||||
|
maintenance_windows: [],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('should normalize icmp', async () => {
|
it('should normalize icmp', async () => {
|
||||||
|
@ -145,6 +146,7 @@ describe('AddNewMonitorsPublicAPI', () => {
|
||||||
type: 'icmp',
|
type: 'icmp',
|
||||||
wait: '1',
|
wait: '1',
|
||||||
labels: {},
|
labels: {},
|
||||||
|
maintenance_windows: [],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('should normalize http', async () => {
|
it('should normalize http', async () => {
|
||||||
|
@ -204,6 +206,7 @@ describe('AddNewMonitorsPublicAPI', () => {
|
||||||
urls: '',
|
urls: '',
|
||||||
username: '',
|
username: '',
|
||||||
labels: {},
|
labels: {},
|
||||||
|
maintenance_windows: [],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('should normalize browser', async () => {
|
it('should normalize browser', async () => {
|
||||||
|
@ -259,6 +262,7 @@ describe('AddNewMonitorsPublicAPI', () => {
|
||||||
'url.port': null,
|
'url.port': null,
|
||||||
urls: '',
|
urls: '',
|
||||||
labels: {},
|
labels: {},
|
||||||
|
maintenance_windows: [],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -96,6 +96,7 @@ describe('syncEditedMonitor', () => {
|
||||||
const syntheticsMonitorClient = new SyntheticsMonitorClient(syntheticsService, serverMock);
|
const syntheticsMonitorClient = new SyntheticsMonitorClient(syntheticsService, serverMock);
|
||||||
|
|
||||||
syntheticsService.editConfig = jest.fn();
|
syntheticsService.editConfig = jest.fn();
|
||||||
|
syntheticsService.getMaintenanceWindows = jest.fn();
|
||||||
|
|
||||||
it('includes the isEdit flag', async () => {
|
it('includes the isEdit flag', async () => {
|
||||||
await syncEditedMonitor({
|
await syncEditedMonitor({
|
||||||
|
@ -117,7 +118,9 @@ describe('syncEditedMonitor', () => {
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
configId: '7af7e2f0-d5dc-11ec-87ac-bdfdb894c53d',
|
configId: '7af7e2f0-d5dc-11ec-87ac-bdfdb894c53d',
|
||||||
}),
|
}),
|
||||||
])
|
]),
|
||||||
|
true,
|
||||||
|
undefined
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(serverMock.authSavedObjectsClient?.update).toHaveBeenCalledWith(
|
expect(serverMock.authSavedObjectsClient?.update).toHaveBeenCalledWith(
|
||||||
|
|
|
@ -182,6 +182,7 @@ describe('current status route', () => {
|
||||||
"isStatusAlertEnabled": false,
|
"isStatusAlertEnabled": false,
|
||||||
"locationId": "europe_germany",
|
"locationId": "europe_germany",
|
||||||
"locationLabel": "Europe - Germany",
|
"locationLabel": "Europe - Germany",
|
||||||
|
"maintenanceWindows": undefined,
|
||||||
"monitorQueryId": "id2",
|
"monitorQueryId": "id2",
|
||||||
"name": "test monitor 2",
|
"name": "test monitor 2",
|
||||||
"projectId": "project-id",
|
"projectId": "project-id",
|
||||||
|
@ -213,6 +214,7 @@ describe('current status route', () => {
|
||||||
"isStatusAlertEnabled": false,
|
"isStatusAlertEnabled": false,
|
||||||
"locationId": "asia_japan",
|
"locationId": "asia_japan",
|
||||||
"locationLabel": "Asia/Pacific - Japan",
|
"locationLabel": "Asia/Pacific - Japan",
|
||||||
|
"maintenanceWindows": undefined,
|
||||||
"monitorQueryId": "id1",
|
"monitorQueryId": "id1",
|
||||||
"name": "test monitor 1",
|
"name": "test monitor 1",
|
||||||
"projectId": "project-id",
|
"projectId": "project-id",
|
||||||
|
@ -234,6 +236,7 @@ describe('current status route', () => {
|
||||||
"isStatusAlertEnabled": false,
|
"isStatusAlertEnabled": false,
|
||||||
"locationId": "asia_japan",
|
"locationId": "asia_japan",
|
||||||
"locationLabel": "Asia/Pacific - Japan",
|
"locationLabel": "Asia/Pacific - Japan",
|
||||||
|
"maintenanceWindows": undefined,
|
||||||
"monitorQueryId": "id2",
|
"monitorQueryId": "id2",
|
||||||
"name": "test monitor 2",
|
"name": "test monitor 2",
|
||||||
"projectId": "project-id",
|
"projectId": "project-id",
|
||||||
|
@ -344,6 +347,7 @@ describe('current status route', () => {
|
||||||
"isStatusAlertEnabled": false,
|
"isStatusAlertEnabled": false,
|
||||||
"locationId": "europe_germany",
|
"locationId": "europe_germany",
|
||||||
"locationLabel": "Europe - Germany",
|
"locationLabel": "Europe - Germany",
|
||||||
|
"maintenanceWindows": undefined,
|
||||||
"monitorQueryId": "id2",
|
"monitorQueryId": "id2",
|
||||||
"name": "test monitor 2",
|
"name": "test monitor 2",
|
||||||
"projectId": "project-id",
|
"projectId": "project-id",
|
||||||
|
@ -375,6 +379,7 @@ describe('current status route', () => {
|
||||||
"isStatusAlertEnabled": false,
|
"isStatusAlertEnabled": false,
|
||||||
"locationId": "asia_japan",
|
"locationId": "asia_japan",
|
||||||
"locationLabel": "Asia/Pacific - Japan",
|
"locationLabel": "Asia/Pacific - Japan",
|
||||||
|
"maintenanceWindows": undefined,
|
||||||
"monitorQueryId": "id1",
|
"monitorQueryId": "id1",
|
||||||
"name": "test monitor 1",
|
"name": "test monitor 1",
|
||||||
"projectId": "project-id",
|
"projectId": "project-id",
|
||||||
|
@ -396,6 +401,7 @@ describe('current status route', () => {
|
||||||
"isStatusAlertEnabled": false,
|
"isStatusAlertEnabled": false,
|
||||||
"locationId": "asia_japan",
|
"locationId": "asia_japan",
|
||||||
"locationLabel": "Asia/Pacific - Japan",
|
"locationLabel": "Asia/Pacific - Japan",
|
||||||
|
"maintenanceWindows": undefined,
|
||||||
"monitorQueryId": "id2",
|
"monitorQueryId": "id2",
|
||||||
"name": "test monitor 2",
|
"name": "test monitor 2",
|
||||||
"projectId": "project-id",
|
"projectId": "project-id",
|
||||||
|
@ -456,6 +462,7 @@ describe('current status route', () => {
|
||||||
"isStatusAlertEnabled": false,
|
"isStatusAlertEnabled": false,
|
||||||
"locationId": "asia_japan",
|
"locationId": "asia_japan",
|
||||||
"locationLabel": "Asia/Pacific - Japan",
|
"locationLabel": "Asia/Pacific - Japan",
|
||||||
|
"maintenanceWindows": undefined,
|
||||||
"monitorQueryId": "id1",
|
"monitorQueryId": "id1",
|
||||||
"name": "test monitor 1",
|
"name": "test monitor 1",
|
||||||
"projectId": "project-id",
|
"projectId": "project-id",
|
||||||
|
@ -477,6 +484,7 @@ describe('current status route', () => {
|
||||||
"isStatusAlertEnabled": false,
|
"isStatusAlertEnabled": false,
|
||||||
"locationId": "asia_japan",
|
"locationId": "asia_japan",
|
||||||
"locationLabel": "Asia/Pacific - Japan",
|
"locationLabel": "Asia/Pacific - Japan",
|
||||||
|
"maintenanceWindows": undefined,
|
||||||
"monitorQueryId": "id2",
|
"monitorQueryId": "id2",
|
||||||
"name": "test monitor 2",
|
"name": "test monitor 2",
|
||||||
"projectId": "project-id",
|
"projectId": "project-id",
|
||||||
|
@ -498,6 +506,7 @@ describe('current status route', () => {
|
||||||
"isStatusAlertEnabled": false,
|
"isStatusAlertEnabled": false,
|
||||||
"locationId": "europe_germany",
|
"locationId": "europe_germany",
|
||||||
"locationLabel": "Europe - Germany",
|
"locationLabel": "Europe - Germany",
|
||||||
|
"maintenanceWindows": undefined,
|
||||||
"monitorQueryId": "id2",
|
"monitorQueryId": "id2",
|
||||||
"name": "test monitor 2",
|
"name": "test monitor 2",
|
||||||
"projectId": "project-id",
|
"projectId": "project-id",
|
||||||
|
|
|
@ -350,6 +350,7 @@ export class OverviewStatusService {
|
||||||
ConfigKey.PROJECT_ID,
|
ConfigKey.PROJECT_ID,
|
||||||
ConfigKey.ALERT_CONFIG,
|
ConfigKey.ALERT_CONFIG,
|
||||||
ConfigKey.URLS,
|
ConfigKey.URLS,
|
||||||
|
ConfigKey.MAINTENANCE_WINDOWS,
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -371,6 +372,7 @@ export class OverviewStatusService {
|
||||||
updated_at: monitor.updated_at,
|
updated_at: monitor.updated_at,
|
||||||
spaceId: monitor.namespaces?.[0],
|
spaceId: monitor.namespaces?.[0],
|
||||||
urls: monitor.attributes[ConfigKey.URLS],
|
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,
|
'url.port': null,
|
||||||
urls: 'https://elastic.co',
|
urls: 'https://elastic.co',
|
||||||
labels: {},
|
labels: {},
|
||||||
|
maintenance_windows: [],
|
||||||
},
|
},
|
||||||
coreMigrationVersion: '8.8.0',
|
coreMigrationVersion: '8.8.0',
|
||||||
created_at: '2023-03-31T20:31:24.177Z',
|
created_at: '2023-03-31T20:31:24.177Z',
|
||||||
|
|
|
@ -246,6 +246,9 @@ export const getSyntheticsMonitorSavedObjectType = (
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
maintenance_windows: {
|
||||||
|
type: 'keyword',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
management: {
|
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 { Logger } from '@kbn/logging';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
|
import { MaintenanceWindow } from '@kbn/alerting-plugin/server/application/maintenance_window/types';
|
||||||
import { ConfigKey, MonitorFields } from '../../../common/runtime_types';
|
import { ConfigKey, MonitorFields } from '../../../common/runtime_types';
|
||||||
import { ParsedVars, replaceVarsWithParams } from './lightweight_param_formatter';
|
import { ParsedVars, replaceVarsWithParams } from './lightweight_param_formatter';
|
||||||
import variableParser from './variable_parser';
|
import variableParser from './variable_parser';
|
||||||
|
@ -83,3 +84,41 @@ export const secondsToCronFormatter: FormatterFn = (fields, key) => {
|
||||||
export const maxAttemptsFormatter: FormatterFn = (fields, key) => {
|
export const maxAttemptsFormatter: FormatterFn = (fields, key) => {
|
||||||
return (fields[key] as number) ?? 2;
|
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.MONITOR_QUERY_ID]: stringToJsonFormatter,
|
||||||
[ConfigKey.PARAMS]: null,
|
[ConfigKey.PARAMS]: null,
|
||||||
[ConfigKey.MAX_ATTEMPTS]: null,
|
[ConfigKey.MAX_ATTEMPTS]: null,
|
||||||
|
[ConfigKey.MAINTENANCE_WINDOWS]: null,
|
||||||
retest_on_failure: null,
|
retest_on_failure: null,
|
||||||
[ConfigKey.SCHEDULE]: (fields) =>
|
[ConfigKey.SCHEDULE]: (fields) =>
|
||||||
JSON.stringify(
|
JSON.stringify(
|
||||||
|
|
|
@ -7,15 +7,38 @@
|
||||||
import { ConfigKey, MonitorTypeEnum } from '../../../../common/runtime_types';
|
import { ConfigKey, MonitorTypeEnum } from '../../../../common/runtime_types';
|
||||||
import { formatSyntheticsPolicy } from './format_synthetics_policy';
|
import { formatSyntheticsPolicy } from './format_synthetics_policy';
|
||||||
import { PROFILE_VALUES_ENUM, PROFILES_MAP } from '../../../../common/constants/monitor_defaults';
|
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 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', () => {
|
describe('formatSyntheticsPolicy', () => {
|
||||||
it('formats browser policy', () => {
|
it('formats browser policy', () => {
|
||||||
const { formattedPolicy } = formatSyntheticsPolicy(
|
const { formattedPolicy } = formatSyntheticsPolicy(
|
||||||
testNewPolicy,
|
testNewPolicy,
|
||||||
MonitorTypeEnum.BROWSER,
|
MonitorTypeEnum.BROWSER,
|
||||||
browserConfig,
|
browserConfig,
|
||||||
gParams
|
gParams,
|
||||||
|
testMW
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(formattedPolicy).toEqual({
|
expect(formattedPolicy).toEqual({
|
||||||
|
@ -142,6 +165,9 @@ describe('formatSyntheticsPolicy', () => {
|
||||||
username: {
|
username: {
|
||||||
type: 'text',
|
type: 'text',
|
||||||
},
|
},
|
||||||
|
maintenance_windows: {
|
||||||
|
type: 'yaml',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -242,6 +268,7 @@ describe('formatSyntheticsPolicy', () => {
|
||||||
type: 'text',
|
type: 'text',
|
||||||
value: 'tcp',
|
value: 'tcp',
|
||||||
},
|
},
|
||||||
|
maintenance_windows: { type: 'yaml' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -315,6 +342,7 @@ describe('formatSyntheticsPolicy', () => {
|
||||||
type: 'text',
|
type: 'text',
|
||||||
value: '1s',
|
value: '1s',
|
||||||
},
|
},
|
||||||
|
maintenance_windows: { type: 'yaml' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -432,6 +460,11 @@ describe('formatSyntheticsPolicy', () => {
|
||||||
type: 'text',
|
type: 'text',
|
||||||
value: 'browser',
|
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,
|
...httpPolicy,
|
||||||
[ConfigKey.METADATA]: { is_tls_enabled: isTLSEnabled },
|
[ConfigKey.METADATA]: { is_tls_enabled: isTLSEnabled },
|
||||||
},
|
},
|
||||||
gParams
|
gParams,
|
||||||
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(formattedPolicy).toEqual({
|
expect(formattedPolicy).toEqual({
|
||||||
|
@ -628,6 +662,7 @@ describe('formatSyntheticsPolicy', () => {
|
||||||
type: 'text',
|
type: 'text',
|
||||||
value: '"admin"',
|
value: '"admin"',
|
||||||
},
|
},
|
||||||
|
maintenance_windows: { type: 'yaml' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -728,6 +763,7 @@ describe('formatSyntheticsPolicy', () => {
|
||||||
type: 'text',
|
type: 'text',
|
||||||
value: 'tcp',
|
value: 'tcp',
|
||||||
},
|
},
|
||||||
|
maintenance_windows: { type: 'yaml' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -801,6 +837,7 @@ describe('formatSyntheticsPolicy', () => {
|
||||||
type: 'text',
|
type: 'text',
|
||||||
value: '1s',
|
value: '1s',
|
||||||
},
|
},
|
||||||
|
maintenance_windows: { type: 'yaml' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -897,6 +934,7 @@ describe('formatSyntheticsPolicy', () => {
|
||||||
type: 'text',
|
type: 'text',
|
||||||
value: 'browser',
|
value: 'browser',
|
||||||
},
|
},
|
||||||
|
maintenance_windows: { type: 'yaml' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -987,6 +1025,7 @@ const testNewPolicy = {
|
||||||
origin: { type: 'text' },
|
origin: { type: 'text' },
|
||||||
'monitor.project.id': { type: 'text' },
|
'monitor.project.id': { type: 'text' },
|
||||||
'monitor.project.name': { type: 'text' },
|
'monitor.project.name': { type: 'text' },
|
||||||
|
maintenance_windows: { type: 'yaml' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -1026,6 +1065,7 @@ const testNewPolicy = {
|
||||||
origin: { type: 'text' },
|
origin: { type: 'text' },
|
||||||
'monitor.project.id': { type: 'text' },
|
'monitor.project.id': { type: 'text' },
|
||||||
'monitor.project.name': { type: 'text' },
|
'monitor.project.name': { type: 'text' },
|
||||||
|
maintenance_windows: { type: 'yaml' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -1056,6 +1096,7 @@ const testNewPolicy = {
|
||||||
origin: { type: 'text' },
|
origin: { type: 'text' },
|
||||||
'monitor.project.id': { type: 'text' },
|
'monitor.project.id': { type: 'text' },
|
||||||
'monitor.project.name': { type: 'text' },
|
'monitor.project.name': { type: 'text' },
|
||||||
|
maintenance_windows: { type: 'yaml' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -1094,6 +1135,7 @@ const testNewPolicy = {
|
||||||
origin: { type: 'text' },
|
origin: { type: 'text' },
|
||||||
'monitor.project.id': { type: 'text' },
|
'monitor.project.id': { type: 'text' },
|
||||||
'monitor.project.name': { type: 'text' },
|
'monitor.project.name': { type: 'text' },
|
||||||
|
maintenance_windows: { type: 'yaml' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ enabled: true, data_stream: { type: 'synthetics', dataset: 'browser.network' } },
|
{ 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: { config_id: '00bb3ceb-a242-4c7a-8405-8da963661374' },
|
||||||
fields_under_root: true,
|
fields_under_root: true,
|
||||||
location_name: 'Test private location 0',
|
location_name: 'Test private location 0',
|
||||||
|
maintenance_windows: ['190bd51b-985a-4553-9fba-57222ddde6b7'],
|
||||||
};
|
};
|
||||||
|
|
||||||
const httpPolicy: any = {
|
const httpPolicy: any = {
|
||||||
|
|
|
@ -7,11 +7,12 @@
|
||||||
|
|
||||||
import { NewPackagePolicy } from '@kbn/fleet-plugin/common';
|
import { NewPackagePolicy } from '@kbn/fleet-plugin/common';
|
||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep } from 'lodash';
|
||||||
|
import { MaintenanceWindow } from '@kbn/alerting-plugin/server/application/maintenance_window/types';
|
||||||
import { processorsFormatter } from './processors_formatter';
|
import { processorsFormatter } from './processors_formatter';
|
||||||
import { LegacyConfigKey } from '../../../../common/constants/monitor_management';
|
import { LegacyConfigKey } from '../../../../common/constants/monitor_management';
|
||||||
import { ConfigKey, MonitorTypeEnum, MonitorFields } from '../../../../common/runtime_types';
|
import { ConfigKey, MonitorTypeEnum, MonitorFields } from '../../../../common/runtime_types';
|
||||||
import { throttlingFormatter } from './browser_formatters';
|
import { throttlingFormatter } from './browser_formatters';
|
||||||
import { replaceStringWithParams } from '../formatting_utils';
|
import { formatMWs, replaceStringWithParams } from '../formatting_utils';
|
||||||
import { syntheticsPolicyFormatters } from './formatters';
|
import { syntheticsPolicyFormatters } from './formatters';
|
||||||
import { PARAMS_KEYS_TO_SKIP } from '../common';
|
import { PARAMS_KEYS_TO_SKIP } from '../common';
|
||||||
|
|
||||||
|
@ -31,6 +32,7 @@ export const formatSyntheticsPolicy = (
|
||||||
monitorType: MonitorTypeEnum,
|
monitorType: MonitorTypeEnum,
|
||||||
config: Partial<MonitorFields & ProcessorFields>,
|
config: Partial<MonitorFields & ProcessorFields>,
|
||||||
params: Record<string, string>,
|
params: Record<string, string>,
|
||||||
|
mws: MaintenanceWindow[],
|
||||||
isLegacy?: boolean
|
isLegacy?: boolean
|
||||||
) => {
|
) => {
|
||||||
const configKeys = Object.keys(config) as ConfigKey[];
|
const configKeys = Object.keys(config) as ConfigKey[];
|
||||||
|
@ -74,6 +76,22 @@ export const formatSyntheticsPolicy = (
|
||||||
processorItem.value = processorsFormatter(config as MonitorFields & ProcessorFields);
|
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
|
// TODO: remove this once we remove legacy support
|
||||||
const throttling = dataStream?.vars?.[LegacyConfigKey.THROTTLING_CONFIG];
|
const throttling = dataStream?.vars?.[LegacyConfigKey.THROTTLING_CONFIG];
|
||||||
if (throttling) {
|
if (throttling) {
|
||||||
|
|
|
@ -56,4 +56,5 @@ export const commonFormatters: CommonFormatMap = {
|
||||||
`@every ${fields[ConfigKey.SCHEDULE]?.number}${fields[ConfigKey.SCHEDULE]?.unit}`,
|
`@every ${fields[ConfigKey.SCHEDULE]?.number}${fields[ConfigKey.SCHEDULE]?.unit}`,
|
||||||
[ConfigKey.TAGS]: arrayFormatter,
|
[ConfigKey.TAGS]: arrayFormatter,
|
||||||
[ConfigKey.LABELS]: null,
|
[ConfigKey.LABELS]: null,
|
||||||
|
[ConfigKey.MAINTENANCE_WINDOWS]: null,
|
||||||
};
|
};
|
||||||
|
|
|
@ -104,7 +104,8 @@ describe('formatMonitorConfig', () => {
|
||||||
Object.keys(testHTTPConfig) as ConfigKey[],
|
Object.keys(testHTTPConfig) as ConfigKey[],
|
||||||
testHTTPConfig,
|
testHTTPConfig,
|
||||||
logger,
|
logger,
|
||||||
{ proxyUrl: 'https://www.google.com' }
|
{ proxyUrl: 'https://www.google.com' },
|
||||||
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(yamlConfig).toEqual({
|
expect(yamlConfig).toEqual({
|
||||||
|
@ -145,7 +146,8 @@ describe('formatMonitorConfig', () => {
|
||||||
[ConfigKey.METADATA]: { is_tls_enabled: isTLSEnabled },
|
[ConfigKey.METADATA]: { is_tls_enabled: isTLSEnabled },
|
||||||
},
|
},
|
||||||
logger,
|
logger,
|
||||||
{ proxyUrl: 'https://www.google.com' }
|
{ proxyUrl: 'https://www.google.com' },
|
||||||
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(yamlConfig).toEqual({
|
expect(yamlConfig).toEqual({
|
||||||
|
@ -218,7 +220,8 @@ describe('browser fields', () => {
|
||||||
Object.keys(testBrowserConfig) as ConfigKey[],
|
Object.keys(testBrowserConfig) as ConfigKey[],
|
||||||
testBrowserConfig,
|
testBrowserConfig,
|
||||||
logger,
|
logger,
|
||||||
{ proxyUrl: 'https://www.google.com' }
|
{ proxyUrl: 'https://www.google.com' },
|
||||||
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(yamlConfig).toEqual(formattedBrowserConfig);
|
expect(yamlConfig).toEqual(formattedBrowserConfig);
|
||||||
|
@ -233,7 +236,8 @@ describe('browser fields', () => {
|
||||||
params: '',
|
params: '',
|
||||||
},
|
},
|
||||||
logger,
|
logger,
|
||||||
{ proxyUrl: 'https://www.google.com' }
|
{ proxyUrl: 'https://www.google.com' },
|
||||||
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(yamlConfig).toEqual(omit(formattedBrowserConfig, ['params', 'playwright_options']));
|
expect(yamlConfig).toEqual(omit(formattedBrowserConfig, ['params', 'playwright_options']));
|
||||||
|
@ -251,7 +255,8 @@ describe('browser fields', () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
logger,
|
logger,
|
||||||
{ proxyUrl: 'https://www.google.com' }
|
{ proxyUrl: 'https://www.google.com' },
|
||||||
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
const expected = {
|
const expected = {
|
||||||
|
@ -269,7 +274,8 @@ describe('browser fields', () => {
|
||||||
Object.keys(testBrowserConfig) as ConfigKey[],
|
Object.keys(testBrowserConfig) as ConfigKey[],
|
||||||
testBrowserConfig,
|
testBrowserConfig,
|
||||||
logger,
|
logger,
|
||||||
{ proxyUrl: 'https://www.google.com' }
|
{ proxyUrl: 'https://www.google.com' },
|
||||||
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
const expected = {
|
const expected = {
|
||||||
|
@ -287,7 +293,8 @@ describe('browser fields', () => {
|
||||||
Object.keys(testBrowserConfig) as ConfigKey[],
|
Object.keys(testBrowserConfig) as ConfigKey[],
|
||||||
testBrowserConfig,
|
testBrowserConfig,
|
||||||
logger,
|
logger,
|
||||||
{ proxyUrl: 'https://www.google.com' }
|
{ proxyUrl: 'https://www.google.com' },
|
||||||
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
const expected = { ...formattedConfig, enabled: false };
|
const expected = { ...formattedConfig, enabled: false };
|
||||||
|
|
|
@ -7,7 +7,8 @@
|
||||||
|
|
||||||
import { isEmpty, isNil, omitBy } from 'lodash';
|
import { isEmpty, isNil, omitBy } from 'lodash';
|
||||||
import { Logger } from '@kbn/logging';
|
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 { PARAMS_KEYS_TO_SKIP } from '../common';
|
||||||
import {
|
import {
|
||||||
BrowserFields,
|
BrowserFields,
|
||||||
|
@ -37,7 +38,8 @@ export const formatMonitorConfigFields = (
|
||||||
configKeys: ConfigKey[],
|
configKeys: ConfigKey[],
|
||||||
config: Partial<MonitorFields>,
|
config: Partial<MonitorFields>,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
params: Record<string, string>
|
params: Record<string, string>,
|
||||||
|
mws: MaintenanceWindow[]
|
||||||
) => {
|
) => {
|
||||||
const formattedMonitor = {} as Record<ConfigKey, any>;
|
const formattedMonitor = {} as Record<ConfigKey, any>;
|
||||||
|
|
||||||
|
@ -76,6 +78,19 @@ export const formatMonitorConfigFields = (
|
||||||
sslKeys.forEach((key) => (formattedMonitor[key] = null));
|
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>;
|
return omitBy(formattedMonitor, isNil) as Partial<MonitorFields>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -138,7 +138,8 @@ describe('SyntheticsPrivateLocation', () => {
|
||||||
await syntheticsPrivateLocation.editMonitors(
|
await syntheticsPrivateLocation.editMonitors(
|
||||||
[{ config: testConfig, globalParams: {} }],
|
[{ config: testConfig, globalParams: {} }],
|
||||||
[mockPrivateLocation],
|
[mockPrivateLocation],
|
||||||
'test-space'
|
'test-space',
|
||||||
|
[]
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
expect(e).toEqual(new Error(error));
|
expect(e).toEqual(new Error(error));
|
||||||
|
@ -183,7 +184,8 @@ describe('SyntheticsPrivateLocation', () => {
|
||||||
testMonitorPolicy,
|
testMonitorPolicy,
|
||||||
MonitorTypeEnum.BROWSER,
|
MonitorTypeEnum.BROWSER,
|
||||||
dummyBrowserConfig,
|
dummyBrowserConfig,
|
||||||
{}
|
{},
|
||||||
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(test.formattedPolicy.inputs[3].streams[1]).toStrictEqual({
|
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 { NewPackagePolicyWithId } from '@kbn/fleet-plugin/server/services/package_policy';
|
||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep } from 'lodash';
|
||||||
import { SavedObjectError } from '@kbn/core-saved-objects-common';
|
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 { DEFAULT_NAMESPACE_STRING } from '../../../common/constants/monitor_defaults';
|
||||||
import {
|
import {
|
||||||
BROWSER_TEST_NOW_RUN,
|
BROWSER_TEST_NOW_RUN,
|
||||||
|
@ -75,6 +76,7 @@ export class SyntheticsPrivateLocation {
|
||||||
newPolicyTemplate: NewPackagePolicy,
|
newPolicyTemplate: NewPackagePolicy,
|
||||||
spaceId: string,
|
spaceId: string,
|
||||||
globalParams: Record<string, string>,
|
globalParams: Record<string, string>,
|
||||||
|
maintenanceWindows: MaintenanceWindow[],
|
||||||
testRunId?: string,
|
testRunId?: string,
|
||||||
runOnce?: boolean
|
runOnce?: boolean
|
||||||
): Promise<NewPackagePolicy | null> {
|
): Promise<NewPackagePolicy | null> {
|
||||||
|
@ -122,7 +124,8 @@ export class SyntheticsPrivateLocation {
|
||||||
: {}),
|
: {}),
|
||||||
...(runOnce ? { run_once: runOnce } : {}),
|
...(runOnce ? { run_once: runOnce } : {}),
|
||||||
},
|
},
|
||||||
globalParams
|
globalParams,
|
||||||
|
maintenanceWindows
|
||||||
);
|
);
|
||||||
|
|
||||||
return formattedPolicy;
|
return formattedPolicy;
|
||||||
|
@ -165,6 +168,7 @@ export class SyntheticsPrivateLocation {
|
||||||
newPolicyTemplate,
|
newPolicyTemplate,
|
||||||
spaceId,
|
spaceId,
|
||||||
globalParams,
|
globalParams,
|
||||||
|
[],
|
||||||
testRunId,
|
testRunId,
|
||||||
runOnce
|
runOnce
|
||||||
);
|
);
|
||||||
|
@ -214,10 +218,12 @@ export class SyntheticsPrivateLocation {
|
||||||
privateConfig,
|
privateConfig,
|
||||||
spaceId,
|
spaceId,
|
||||||
allPrivateLocations,
|
allPrivateLocations,
|
||||||
|
maintenanceWindows,
|
||||||
}: {
|
}: {
|
||||||
privateConfig?: PrivateConfig;
|
privateConfig?: PrivateConfig;
|
||||||
allPrivateLocations: PrivateLocationAttributes[];
|
allPrivateLocations: PrivateLocationAttributes[];
|
||||||
spaceId: string;
|
spaceId: string;
|
||||||
|
maintenanceWindows: MaintenanceWindow[];
|
||||||
}) {
|
}) {
|
||||||
if (!privateConfig) {
|
if (!privateConfig) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -238,7 +244,8 @@ export class SyntheticsPrivateLocation {
|
||||||
location,
|
location,
|
||||||
newPolicyTemplate,
|
newPolicyTemplate,
|
||||||
spaceId,
|
spaceId,
|
||||||
globalParams
|
globalParams,
|
||||||
|
maintenanceWindows
|
||||||
);
|
);
|
||||||
|
|
||||||
const pkgPolicy = {
|
const pkgPolicy = {
|
||||||
|
@ -256,7 +263,8 @@ export class SyntheticsPrivateLocation {
|
||||||
async editMonitors(
|
async editMonitors(
|
||||||
configs: Array<{ config: HeartbeatConfig; globalParams: Record<string, string> }>,
|
configs: Array<{ config: HeartbeatConfig; globalParams: Record<string, string> }>,
|
||||||
allPrivateLocations: SyntheticsPrivateLocations,
|
allPrivateLocations: SyntheticsPrivateLocations,
|
||||||
spaceId: string
|
spaceId: string,
|
||||||
|
maintenanceWindows: MaintenanceWindow[]
|
||||||
) {
|
) {
|
||||||
if (configs.length === 0) {
|
if (configs.length === 0) {
|
||||||
return {};
|
return {};
|
||||||
|
@ -291,7 +299,8 @@ export class SyntheticsPrivateLocation {
|
||||||
privateLocation,
|
privateLocation,
|
||||||
newPolicyTemplate,
|
newPolicyTemplate,
|
||||||
spaceId,
|
spaceId,
|
||||||
globalParams
|
globalParams,
|
||||||
|
maintenanceWindows
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!newPolicy) {
|
if (!newPolicy) {
|
||||||
|
|
|
@ -175,6 +175,7 @@ describe('getNormalizeCommonFields', () => {
|
||||||
params: '',
|
params: '',
|
||||||
max_attempts: 2,
|
max_attempts: 2,
|
||||||
labels: {},
|
labels: {},
|
||||||
|
maintenance_windows: [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -241,6 +242,7 @@ describe('getNormalizeCommonFields', () => {
|
||||||
params: '',
|
params: '',
|
||||||
max_attempts: 2,
|
max_attempts: 2,
|
||||||
labels: {},
|
labels: {},
|
||||||
|
maintenance_windows: [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -101,6 +101,8 @@ export const getNormalizeCommonFields = ({
|
||||||
// picking out keys specifically, so users can't add arbitrary fields
|
// picking out keys specifically, so users can't add arbitrary fields
|
||||||
[ConfigKey.ALERT_CONFIG]: getAlertConfig(monitor),
|
[ConfigKey.ALERT_CONFIG]: getAlertConfig(monitor),
|
||||||
[ConfigKey.LABELS]: monitor.fields || defaultFields[ConfigKey.LABELS],
|
[ConfigKey.LABELS]: monitor.fields || defaultFields[ConfigKey.LABELS],
|
||||||
|
[ConfigKey.MAINTENANCE_WINDOWS]:
|
||||||
|
monitor.maintenanceWindows || defaultFields[ConfigKey.MAINTENANCE_WINDOWS],
|
||||||
...(monitor[ConfigKey.APM_SERVICE_NAME] && {
|
...(monitor[ConfigKey.APM_SERVICE_NAME] && {
|
||||||
[ConfigKey.APM_SERVICE_NAME]: 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.addConfigs = jest.fn();
|
||||||
syntheticsService.editConfig = jest.fn();
|
syntheticsService.editConfig = jest.fn();
|
||||||
syntheticsService.deleteConfigs = jest.fn();
|
syntheticsService.deleteConfigs = jest.fn();
|
||||||
|
syntheticsService.getMaintenanceWindows = jest.fn();
|
||||||
|
|
||||||
const encryptedSavedObjectsClient = encryptedSavedObjectsMock.createStart().getClient();
|
const encryptedSavedObjectsClient = encryptedSavedObjectsMock.createStart().getClient();
|
||||||
|
|
||||||
|
|
|
@ -67,6 +67,7 @@ describe('SyntheticsMonitorClient', () => {
|
||||||
syntheticsService.addConfigs = jest.fn();
|
syntheticsService.addConfigs = jest.fn();
|
||||||
syntheticsService.editConfig = jest.fn();
|
syntheticsService.editConfig = jest.fn();
|
||||||
syntheticsService.deleteConfigs = jest.fn();
|
syntheticsService.deleteConfigs = jest.fn();
|
||||||
|
syntheticsService.getMaintenanceWindows = jest.fn();
|
||||||
|
|
||||||
const locations = times(3).map((n) => {
|
const locations = times(3).map((n) => {
|
||||||
return {
|
return {
|
||||||
|
@ -179,16 +180,20 @@ describe('SyntheticsMonitorClient', () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(syntheticsService.editConfig).toHaveBeenCalledTimes(1);
|
expect(syntheticsService.editConfig).toHaveBeenCalledTimes(1);
|
||||||
expect(syntheticsService.editConfig).toHaveBeenCalledWith([
|
expect(syntheticsService.editConfig).toHaveBeenCalledWith(
|
||||||
{
|
[
|
||||||
monitor,
|
{
|
||||||
configId: id,
|
monitor,
|
||||||
params: {
|
configId: id,
|
||||||
username: 'elastic',
|
params: {
|
||||||
|
username: 'elastic',
|
||||||
|
},
|
||||||
|
spaceId: 'test-space',
|
||||||
},
|
},
|
||||||
spaceId: 'test-space',
|
],
|
||||||
},
|
true,
|
||||||
]);
|
undefined
|
||||||
|
);
|
||||||
expect(syntheticsService.deleteConfigs).toHaveBeenCalledTimes(1);
|
expect(syntheticsService.deleteConfigs).toHaveBeenCalledTimes(1);
|
||||||
expect(client.privateLocationAPI.editMonitors).toHaveBeenCalledTimes(1);
|
expect(client.privateLocationAPI.editMonitors).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
|
@ -53,6 +53,7 @@ export class SyntheticsMonitorClient {
|
||||||
const publicConfigs: ConfigData[] = [];
|
const publicConfigs: ConfigData[] = [];
|
||||||
|
|
||||||
const paramsBySpace = await this.syntheticsService.getSyntheticsParams({ spaceId });
|
const paramsBySpace = await this.syntheticsService.getSyntheticsParams({ spaceId });
|
||||||
|
const maintenanceWindows = await this.syntheticsService.getMaintenanceWindows();
|
||||||
|
|
||||||
for (const monitorObj of monitors) {
|
for (const monitorObj of monitors) {
|
||||||
const { formattedConfig, params, config } = await this.formatConfigWithParams(
|
const { formattedConfig, params, config } = await this.formatConfigWithParams(
|
||||||
|
@ -77,7 +78,7 @@ export class SyntheticsMonitorClient {
|
||||||
spaceId
|
spaceId
|
||||||
);
|
);
|
||||||
|
|
||||||
const syncErrors = this.syntheticsService.addConfigs(publicConfigs);
|
const syncErrors = this.syntheticsService.addConfigs(publicConfigs, maintenanceWindows);
|
||||||
|
|
||||||
return await Promise.all([newPolicies, syncErrors]);
|
return await Promise.all([newPolicies, syncErrors]);
|
||||||
}
|
}
|
||||||
|
@ -98,6 +99,7 @@ export class SyntheticsMonitorClient {
|
||||||
const deletedPublicConfigs: ConfigData[] = [];
|
const deletedPublicConfigs: ConfigData[] = [];
|
||||||
|
|
||||||
const paramsBySpace = await this.syntheticsService.getSyntheticsParams({ spaceId });
|
const paramsBySpace = await this.syntheticsService.getSyntheticsParams({ spaceId });
|
||||||
|
const maintenanceWindows = await this.syntheticsService.getMaintenanceWindows();
|
||||||
|
|
||||||
for (const editedMonitor of monitors) {
|
for (const editedMonitor of monitors) {
|
||||||
const { str: paramsString, params } = mixParamsWithGlobalParams(
|
const { str: paramsString, params } = mixParamsWithGlobalParams(
|
||||||
|
@ -105,7 +107,7 @@ export class SyntheticsMonitorClient {
|
||||||
editedMonitor.monitor
|
editedMonitor.monitor
|
||||||
);
|
);
|
||||||
|
|
||||||
const configData = {
|
const configData: ConfigData = {
|
||||||
spaceId,
|
spaceId,
|
||||||
params: paramsBySpace[spaceId],
|
params: paramsBySpace[spaceId],
|
||||||
monitor: editedMonitor.monitor,
|
monitor: editedMonitor.monitor,
|
||||||
|
@ -146,10 +148,15 @@ export class SyntheticsMonitorClient {
|
||||||
const privateEditPromise = this.privateLocationAPI.editMonitors(
|
const privateEditPromise = this.privateLocationAPI.editMonitors(
|
||||||
privateConfigs,
|
privateConfigs,
|
||||||
allPrivateLocations,
|
allPrivateLocations,
|
||||||
spaceId
|
spaceId,
|
||||||
|
maintenanceWindows
|
||||||
);
|
);
|
||||||
|
|
||||||
const publicConfigsPromise = this.syntheticsService.editConfig(publicConfigs);
|
const publicConfigsPromise = this.syntheticsService.editConfig(
|
||||||
|
publicConfigs,
|
||||||
|
true,
|
||||||
|
maintenanceWindows
|
||||||
|
);
|
||||||
|
|
||||||
const [publicSyncErrors, privateEditResponse] = await Promise.all([
|
const [publicSyncErrors, privateEditResponse] = await Promise.all([
|
||||||
publicConfigsPromise,
|
publicConfigsPromise,
|
||||||
|
@ -298,6 +305,7 @@ export class SyntheticsMonitorClient {
|
||||||
canSave,
|
canSave,
|
||||||
hideParams,
|
hideParams,
|
||||||
});
|
});
|
||||||
|
const maintenanceWindows = await this.syntheticsService.getMaintenanceWindows();
|
||||||
|
|
||||||
const { formattedConfig, params, config } = await this.formatConfigWithParams(
|
const { formattedConfig, params, config } = await this.formatConfigWithParams(
|
||||||
monitorObj,
|
monitorObj,
|
||||||
|
@ -316,11 +324,13 @@ export class SyntheticsMonitorClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
const publicPromise = this.syntheticsService.inspectConfig(
|
const publicPromise = this.syntheticsService.inspectConfig(
|
||||||
publicLocations.length > 0 ? config : undefined
|
publicLocations.length > 0 ? config : null,
|
||||||
|
maintenanceWindows
|
||||||
);
|
);
|
||||||
const privatePromise = this.privateLocationAPI.inspectPackagePolicy({
|
const privatePromise = this.privateLocationAPI.inspectPackagePolicy({
|
||||||
privateConfig: privateConfigs?.[0],
|
privateConfig: privateConfigs?.[0],
|
||||||
allPrivateLocations,
|
allPrivateLocations,
|
||||||
|
maintenanceWindows,
|
||||||
spaceId,
|
spaceId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -145,6 +145,8 @@ describe('SyntheticsService', () => {
|
||||||
jest.spyOn(service, 'getOutput').mockResolvedValue({ hosts: ['es'], api_key: 'i:k' });
|
jest.spyOn(service, 'getOutput').mockResolvedValue({ hosts: ['es'], api_key: 'i:k' });
|
||||||
jest.spyOn(service, 'getSyntheticsParams').mockResolvedValue({});
|
jest.spyOn(service, 'getSyntheticsParams').mockResolvedValue({});
|
||||||
|
|
||||||
|
service.getMaintenanceWindows = jest.fn();
|
||||||
|
|
||||||
return { service, locations };
|
return { service, locations };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -223,7 +225,7 @@ describe('SyntheticsService', () => {
|
||||||
|
|
||||||
(axios as jest.MockedFunction<typeof axios>).mockResolvedValue({} as AxiosResponse);
|
(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).toHaveBeenCalledTimes(1);
|
||||||
expect(axios).toHaveBeenCalledWith(
|
expect(axios).toHaveBeenCalledWith(
|
||||||
|
@ -289,7 +291,7 @@ describe('SyntheticsService', () => {
|
||||||
|
|
||||||
const payload = getFakePayload([locations[0]]);
|
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).toHaveBeenCalledTimes(1);
|
||||||
expect(axios).toHaveBeenCalledWith(
|
expect(axios).toHaveBeenCalledWith(
|
||||||
|
@ -306,7 +308,7 @@ describe('SyntheticsService', () => {
|
||||||
|
|
||||||
const payload = getFakePayload([locations[0]]);
|
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).toHaveBeenCalledTimes(1);
|
||||||
expect(axios).toHaveBeenCalledWith(
|
expect(axios).toHaveBeenCalledWith(
|
||||||
|
@ -323,7 +325,7 @@ describe('SyntheticsService', () => {
|
||||||
|
|
||||||
const payload = getFakePayload([locations[0]]);
|
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).toHaveBeenCalledTimes(1);
|
||||||
expect(axios).toHaveBeenCalledWith(
|
expect(axios).toHaveBeenCalledWith(
|
||||||
|
@ -533,6 +535,8 @@ describe('SyntheticsService', () => {
|
||||||
jest.spyOn(service, 'getOutput').mockResolvedValue({ hosts: ['es'], api_key: 'i:k' });
|
jest.spyOn(service, 'getOutput').mockResolvedValue({ hosts: ['es'], api_key: 'i:k' });
|
||||||
jest.spyOn(service, 'getSyntheticsParams').mockResolvedValue({});
|
jest.spyOn(service, 'getSyntheticsParams').mockResolvedValue({});
|
||||||
|
|
||||||
|
service.getMaintenanceWindows = jest.fn();
|
||||||
|
|
||||||
it('paginates the results', async () => {
|
it('paginates the results', async () => {
|
||||||
serverMock.config = mockConfig;
|
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 { ALL_SPACES_ID } from '@kbn/spaces-plugin/common/constants';
|
||||||
import pMap from 'p-map';
|
import pMap from 'p-map';
|
||||||
import moment from 'moment';
|
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 { registerCleanUpTask } from './private_location/clean_up_task';
|
||||||
import { SyntheticsServerSetup } from '../types';
|
import { SyntheticsServerSetup } from '../types';
|
||||||
import { syntheticsMonitorType, syntheticsParamType } from '../../common/types/saved_objects';
|
import { syntheticsMonitorType, syntheticsParamType } from '../../common/types/saved_objects';
|
||||||
|
@ -326,11 +330,11 @@ export class SyntheticsService {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async inspectConfig(config?: ConfigData) {
|
async inspectConfig(config: ConfigData | null, mws: MaintenanceWindow[]) {
|
||||||
if (!config) {
|
if (!config || isEmpty(config)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const monitors = this.formatConfigs(config);
|
const monitors = this.formatConfigs(config, mws);
|
||||||
const license = await this.getLicense();
|
const license = await this.getLicense();
|
||||||
|
|
||||||
const output = await this.getOutput({ inspect: true });
|
const output = await this.getOutput({ inspect: true });
|
||||||
|
@ -344,13 +348,13 @@ export class SyntheticsService {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async addConfigs(configs: ConfigData[]) {
|
async addConfigs(configs: ConfigData[], mws: MaintenanceWindow[]) {
|
||||||
try {
|
try {
|
||||||
if (configs.length === 0 || !this.isAllowed) {
|
if (configs.length === 0 || !this.isAllowed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const monitors = this.formatConfigs(configs);
|
const monitors = this.formatConfigs(configs, mws);
|
||||||
const license = await this.getLicense();
|
const license = await this.getLicense();
|
||||||
|
|
||||||
const output = await this.getOutput();
|
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 {
|
try {
|
||||||
if (monitorConfig.length === 0 || !this.isAllowed) {
|
if (monitorConfig.length === 0 || !this.isAllowed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const license = await this.getLicense();
|
const license = await this.getLicense();
|
||||||
const monitors = this.formatConfigs(monitorConfig);
|
const monitors = this.formatConfigs(monitorConfig, mws);
|
||||||
|
|
||||||
const output = await this.getOutput();
|
const output = await this.getOutput();
|
||||||
if (output) {
|
if (output) {
|
||||||
|
@ -411,6 +415,7 @@ export class SyntheticsService {
|
||||||
let output: ServiceData['output'] | null = null;
|
let output: ServiceData['output'] | null = null;
|
||||||
|
|
||||||
const paramsBySpace = await this.getSyntheticsParams();
|
const paramsBySpace = await this.getSyntheticsParams();
|
||||||
|
const maintenanceWindows = await this.getMaintenanceWindows();
|
||||||
const finder = await this.getSOClientFinder({ pageSize: PER_PAGE });
|
const finder = await this.getSOClientFinder({ pageSize: PER_PAGE });
|
||||||
|
|
||||||
const bucketsByLocation: Record<string, MonitorFields[]> = {};
|
const bucketsByLocation: Record<string, MonitorFields[]> = {};
|
||||||
|
@ -462,7 +467,11 @@ export class SyntheticsService {
|
||||||
}
|
}
|
||||||
|
|
||||||
const monitors = result.saved_objects.filter(({ error }) => !error);
|
const monitors = result.saved_objects.filter(({ error }) => !error);
|
||||||
const formattedConfigs = this.normalizeConfigs(monitors, paramsBySpace);
|
const formattedConfigs = this.normalizeConfigs(
|
||||||
|
monitors,
|
||||||
|
paramsBySpace,
|
||||||
|
maintenanceWindows
|
||||||
|
);
|
||||||
|
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
`${formattedConfigs.length} monitors will be pushed to synthetics service.`
|
`${formattedConfigs.length} monitors will be pushed to synthetics service.`
|
||||||
|
@ -504,7 +513,7 @@ export class SyntheticsService {
|
||||||
if (!configs) {
|
if (!configs) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const monitors = this.formatConfigs(configs);
|
const monitors = this.formatConfigs(configs, []);
|
||||||
if (monitors.length === 0) {
|
if (monitors.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -545,7 +554,7 @@ export class SyntheticsService {
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
output,
|
output,
|
||||||
monitors: this.formatConfigs(configs),
|
monitors: this.formatConfigs(configs, []),
|
||||||
license,
|
license,
|
||||||
};
|
};
|
||||||
return await this.apiClient.delete(data);
|
return await this.apiClient.delete(data);
|
||||||
|
@ -557,7 +566,6 @@ export class SyntheticsService {
|
||||||
|
|
||||||
async deleteAllConfigs() {
|
async deleteAllConfigs() {
|
||||||
const license = await this.getLicense();
|
const license = await this.getLicense();
|
||||||
const paramsBySpace = await this.getSyntheticsParams();
|
|
||||||
const finder = await this.getSOClientFinder({ pageSize: 100 });
|
const finder = await this.getSOClientFinder({ pageSize: 100 });
|
||||||
const output = await this.getOutput();
|
const output = await this.getOutput();
|
||||||
if (!output) {
|
if (!output) {
|
||||||
|
@ -565,7 +573,7 @@ export class SyntheticsService {
|
||||||
}
|
}
|
||||||
|
|
||||||
for await (const result of finder.find()) {
|
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) =>
|
const hasPublicLocations = monitors.some((config) =>
|
||||||
config.locations.some(({ isServiceManaged }) => isServiceManaged)
|
config.locations.some(({ isServiceManaged }) => isServiceManaged)
|
||||||
);
|
);
|
||||||
|
@ -636,7 +644,24 @@ export class SyntheticsService {
|
||||||
return paramsBySpace;
|
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];
|
const configDataList = Array.isArray(configData) ? configData : [configData];
|
||||||
|
|
||||||
return configDataList.map((config) => {
|
return configDataList.map((config) => {
|
||||||
|
@ -651,20 +676,22 @@ export class SyntheticsService {
|
||||||
Object.keys(asHeartbeatConfig) as ConfigKey[],
|
Object.keys(asHeartbeatConfig) as ConfigKey[],
|
||||||
asHeartbeatConfig as Partial<MonitorFields>,
|
asHeartbeatConfig as Partial<MonitorFields>,
|
||||||
this.logger,
|
this.logger,
|
||||||
params ?? {}
|
params ?? {},
|
||||||
|
mws
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
normalizeConfigs(
|
normalizeConfigs(
|
||||||
monitors: Array<SavedObject<SyntheticsMonitorWithSecretsAttributes>>,
|
monitors: Array<SavedObject<SyntheticsMonitorWithSecretsAttributes>>,
|
||||||
paramsBySpace: Record<string, Record<string, string>>
|
paramsBySpace: Record<string, Record<string, string>>,
|
||||||
|
mws: MaintenanceWindow[]
|
||||||
) {
|
) {
|
||||||
const configDataList = (monitors ?? []).map((monitor) => {
|
const configDataList = (monitors ?? []).map((monitor) => {
|
||||||
const attributes = monitor.attributes as unknown as MonitorFields;
|
const attributes = monitor.attributes as unknown as MonitorFields;
|
||||||
const monitorSpace = monitor.namespaces?.[0] ?? DEFAULT_SPACE_ID;
|
const monitorSpace = monitor.namespaces?.[0] ?? DEFAULT_SPACE_ID;
|
||||||
|
|
||||||
const params = paramsBySpace[monitorSpace];
|
const params = paramsBySpace[monitorSpace] ?? {};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
params: { ...params, ...(paramsBySpace?.[ALL_SPACES_ID] ?? {}) },
|
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>) {
|
checkMissingSchedule(state: Record<string, string>) {
|
||||||
try {
|
try {
|
||||||
|
@ -685,7 +712,7 @@ export class SyntheticsService {
|
||||||
if (lastRunAt) {
|
if (lastRunAt) {
|
||||||
// log if it has missed last schedule
|
// log if it has missed last schedule
|
||||||
const diff = moment(current).diff(lastRunAt, 'minutes');
|
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) {
|
if (diff > syncInterval) {
|
||||||
const message = `Synthetics monitor sync task has missed its schedule, it last ran ${diff} minutes ago.`;
|
const message = `Synthetics monitor sync task has missed its schedule, it last ran ${diff} minutes ago.`;
|
||||||
this.logger.warn(message);
|
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 { ALL_SPACES_ID } from '@kbn/spaces-plugin/common/constants';
|
||||||
import { ConcreteTaskInstance } from '@kbn/task-manager-plugin/server';
|
import { ConcreteTaskInstance } from '@kbn/task-manager-plugin/server';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
import { MAINTENANCE_WINDOW_SAVED_OBJECT_TYPE } from '@kbn/alerting-plugin/common';
|
||||||
import { syntheticsParamType } from '../../common/types/saved_objects';
|
import { syntheticsParamType } from '../../common/types/saved_objects';
|
||||||
import { normalizeSecrets } from '../synthetics_service/utils';
|
import { normalizeSecrets } from '../synthetics_service/utils';
|
||||||
import type { PrivateLocationAttributes } from '../runtime_types/private_locations';
|
import type { PrivateLocationAttributes } from '../runtime_types/private_locations';
|
||||||
|
@ -33,13 +34,17 @@ import {
|
||||||
|
|
||||||
const TASK_TYPE = 'Synthetics:Sync-Private-Location-Monitors';
|
const TASK_TYPE = 'Synthetics:Sync-Private-Location-Monitors';
|
||||||
const TASK_ID = `${TASK_TYPE}-single-instance`;
|
const TASK_ID = `${TASK_TYPE}-single-instance`;
|
||||||
|
const TASK_SCHEDULE = '5m';
|
||||||
|
|
||||||
interface TaskState extends Record<string, unknown> {
|
interface TaskState extends Record<string, unknown> {
|
||||||
lastStartedAt: string;
|
lastStartedAt: string;
|
||||||
lastTotalParams: number;
|
lastTotalParams: number;
|
||||||
|
lastTotalMWs: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
type CustomTaskInstance = Omit<ConcreteTaskInstance, 'state'> & { state: Partial<TaskState> };
|
export type CustomTaskInstance = Omit<ConcreteTaskInstance, 'state'> & {
|
||||||
|
state: Partial<TaskState>;
|
||||||
|
};
|
||||||
|
|
||||||
export class SyncPrivateLocationMonitorsTask {
|
export class SyncPrivateLocationMonitorsTask {
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -79,18 +84,23 @@ export class SyncPrivateLocationMonitorsTask {
|
||||||
taskInstance.state.lastStartedAt || moment().subtract(10, 'minute').toISOString();
|
taskInstance.state.lastStartedAt || moment().subtract(10, 'minute').toISOString();
|
||||||
const startedAt = taskInstance.startedAt || new Date();
|
const startedAt = taskInstance.startedAt || new Date();
|
||||||
let lastTotalParams = taskInstance.state.lastTotalParams || 0;
|
let lastTotalParams = taskInstance.state.lastTotalParams || 0;
|
||||||
|
let lastTotalMWs = taskInstance.state.lastTotalMWs || 0;
|
||||||
try {
|
try {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
`Syncing private location monitors, last total params ${lastTotalParams}, last run ${lastStartedAt}`
|
`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 allPrivateLocations = await getPrivateLocations(soClient);
|
||||||
const { updatedParams, totalParams } = await this.hasAnyParamChanged(soClient, lastStartedAt);
|
const { totalMWs, totalParams, hasDataChanged } = await this.hasAnyDataChanged({
|
||||||
if (updatedParams > 0 || totalParams !== lastTotalParams) {
|
soClient,
|
||||||
lastTotalParams = totalParams;
|
taskInstance,
|
||||||
logger.debug(
|
});
|
||||||
`Syncing private location monitors because params changed, updated params ${updatedParams}, total params ${totalParams}`
|
lastTotalParams = totalParams;
|
||||||
);
|
lastTotalMWs = totalMWs;
|
||||||
|
if (hasDataChanged) {
|
||||||
|
logger.debug(`Syncing private location monitors because data has changed`);
|
||||||
|
|
||||||
if (allPrivateLocations.length > 0) {
|
if (allPrivateLocations.length > 0) {
|
||||||
await this.syncGlobalParams({
|
await this.syncGlobalParams({
|
||||||
|
@ -101,9 +111,8 @@ export class SyncPrivateLocationMonitorsTask {
|
||||||
}
|
}
|
||||||
logger.debug(`Sync of private location monitors succeeded`);
|
logger.debug(`Sync of private location monitors succeeded`);
|
||||||
} else {
|
} else {
|
||||||
lastTotalParams = totalParams;
|
|
||||||
logger.debug(
|
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) {
|
} catch (error) {
|
||||||
|
@ -113,6 +122,7 @@ export class SyncPrivateLocationMonitorsTask {
|
||||||
state: {
|
state: {
|
||||||
lastStartedAt: startedAt.toISOString(),
|
lastStartedAt: startedAt.toISOString(),
|
||||||
lastTotalParams,
|
lastTotalParams,
|
||||||
|
lastTotalMWs,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -120,6 +130,7 @@ export class SyncPrivateLocationMonitorsTask {
|
||||||
state: {
|
state: {
|
||||||
lastStartedAt: startedAt.toISOString(),
|
lastStartedAt: startedAt.toISOString(),
|
||||||
lastTotalParams,
|
lastTotalParams,
|
||||||
|
lastTotalMWs,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -134,7 +145,7 @@ export class SyncPrivateLocationMonitorsTask {
|
||||||
id: TASK_ID,
|
id: TASK_ID,
|
||||||
state: {},
|
state: {},
|
||||||
schedule: {
|
schedule: {
|
||||||
interval: '10m',
|
interval: TASK_SCHEDULE,
|
||||||
},
|
},
|
||||||
taskType: TASK_TYPE,
|
taskType: TASK_TYPE,
|
||||||
params: {},
|
params: {},
|
||||||
|
@ -142,6 +153,32 @@ export class SyncPrivateLocationMonitorsTask {
|
||||||
logger.debug(`Sync private location monitors task scheduled successfully`);
|
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({
|
async syncGlobalParams({
|
||||||
allPrivateLocations,
|
allPrivateLocations,
|
||||||
encryptedSavedObjects,
|
encryptedSavedObjects,
|
||||||
|
@ -155,10 +192,11 @@ export class SyncPrivateLocationMonitorsTask {
|
||||||
const privateConfigs: Array<{ config: HeartbeatConfig; globalParams: Record<string, string> }> =
|
const privateConfigs: Array<{ config: HeartbeatConfig; globalParams: Record<string, string> }> =
|
||||||
[];
|
[];
|
||||||
|
|
||||||
const { configsBySpaces, paramsBySpace, spaceIds } = await this.getAllMonitorConfigs({
|
const { configsBySpaces, paramsBySpace, spaceIds, maintenanceWindows } =
|
||||||
encryptedSavedObjects,
|
await this.getAllMonitorConfigs({
|
||||||
soClient,
|
encryptedSavedObjects,
|
||||||
});
|
soClient,
|
||||||
|
});
|
||||||
|
|
||||||
for (const spaceId of spaceIds) {
|
for (const spaceId of spaceIds) {
|
||||||
const monitors = configsBySpaces[spaceId];
|
const monitors = configsBySpaces[spaceId];
|
||||||
|
@ -173,7 +211,12 @@ export class SyncPrivateLocationMonitorsTask {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (privateConfigs.length > 0) {
|
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 { syntheticsService } = this.syntheticsMonitorClient;
|
||||||
const paramsBySpacePromise = syntheticsService.getSyntheticsParams({ spaceId: ALL_SPACES_ID });
|
const paramsBySpacePromise = syntheticsService.getSyntheticsParams({ spaceId: ALL_SPACES_ID });
|
||||||
|
const maintenanceWindowsPromise = syntheticsService.getMaintenanceWindows();
|
||||||
const monitorConfigRepository = new MonitorConfigRepository(
|
const monitorConfigRepository = new MonitorConfigRepository(
|
||||||
soClient,
|
soClient,
|
||||||
encryptedSavedObjects.getClient()
|
encryptedSavedObjects.getClient()
|
||||||
|
@ -196,11 +240,16 @@ export class SyncPrivateLocationMonitorsTask {
|
||||||
spaceId: ALL_SPACES_ID,
|
spaceId: ALL_SPACES_ID,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [paramsBySpace, monitors] = await Promise.all([paramsBySpacePromise, monitorsPromise]);
|
const [paramsBySpace, monitors, maintenanceWindows] = await Promise.all([
|
||||||
|
paramsBySpacePromise,
|
||||||
|
monitorsPromise,
|
||||||
|
maintenanceWindowsPromise,
|
||||||
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...this.mixParamsWithMonitors(monitors, paramsBySpace),
|
...this.mixParamsWithMonitors(monitors, paramsBySpace),
|
||||||
paramsBySpace,
|
paramsBySpace,
|
||||||
|
maintenanceWindows,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,7 +300,15 @@ export class SyncPrivateLocationMonitorsTask {
|
||||||
return { configsBySpaces, spaceIds };
|
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 { logger } = this.serverSetup;
|
||||||
const [editedParams, totalParams] = await Promise.all([
|
const [editedParams, totalParams] = await Promise.all([
|
||||||
soClient.find({
|
soClient.find({
|
||||||
|
@ -268,10 +325,59 @@ export class SyncPrivateLocationMonitorsTask {
|
||||||
fields: [],
|
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 {
|
return {
|
||||||
|
hasParamsChanges,
|
||||||
updatedParams: editedParams.total,
|
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/charts-plugin",
|
||||||
"@kbn/response-ops-rule-params",
|
"@kbn/response-ops-rule-params",
|
||||||
"@kbn/response-ops-rule-form",
|
"@kbn/response-ops-rule-form",
|
||||||
"@kbn/fields-metadata-plugin"
|
"@kbn/fields-metadata-plugin",
|
||||||
|
"@kbn/alerts-ui-shared",
|
||||||
|
"@kbn/core-lifecycle-server"
|
||||||
],
|
],
|
||||||
"exclude": ["target/**/*"]
|
"exclude": ["target/**/*"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -229,6 +229,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
|
||||||
updated_at: decryptedCreatedMonitor.rawBody.updated_at,
|
updated_at: decryptedCreatedMonitor.rawBody.updated_at,
|
||||||
created_at: decryptedCreatedMonitor.rawBody.created_at,
|
created_at: decryptedCreatedMonitor.rawBody.created_at,
|
||||||
labels: {},
|
labels: {},
|
||||||
|
maintenance_windows: [],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -357,6 +358,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
|
||||||
ipv4: true,
|
ipv4: true,
|
||||||
max_attempts: 2,
|
max_attempts: 2,
|
||||||
labels: {},
|
labels: {},
|
||||||
|
maintenance_windows: [],
|
||||||
updated_at: decryptedCreatedMonitor.updated_at,
|
updated_at: decryptedCreatedMonitor.updated_at,
|
||||||
created_at: decryptedCreatedMonitor.created_at,
|
created_at: decryptedCreatedMonitor.created_at,
|
||||||
});
|
});
|
||||||
|
@ -473,6 +475,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
|
||||||
params: '',
|
params: '',
|
||||||
max_attempts: 2,
|
max_attempts: 2,
|
||||||
labels: {},
|
labels: {},
|
||||||
|
maintenance_windows: [],
|
||||||
updated_at: decryptedCreatedMonitor.updated_at,
|
updated_at: decryptedCreatedMonitor.updated_at,
|
||||||
created_at: decryptedCreatedMonitor.created_at,
|
created_at: decryptedCreatedMonitor.created_at,
|
||||||
});
|
});
|
||||||
|
@ -578,6 +581,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
|
||||||
updated_at: decryptedCreatedMonitor.updated_at,
|
updated_at: decryptedCreatedMonitor.updated_at,
|
||||||
created_at: decryptedCreatedMonitor.created_at,
|
created_at: decryptedCreatedMonitor.created_at,
|
||||||
labels: {},
|
labels: {},
|
||||||
|
maintenance_windows: [],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
@ -301,6 +301,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
|
||||||
updated_at: decryptedCreatedMonitor.rawBody.updated_at,
|
updated_at: decryptedCreatedMonitor.rawBody.updated_at,
|
||||||
created_at: decryptedCreatedMonitor.rawBody.created_at,
|
created_at: decryptedCreatedMonitor.rawBody.created_at,
|
||||||
labels: {},
|
labels: {},
|
||||||
|
maintenance_windows: [],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -482,6 +483,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
|
||||||
ipv4: true,
|
ipv4: true,
|
||||||
max_attempts: 2,
|
max_attempts: 2,
|
||||||
labels: {},
|
labels: {},
|
||||||
|
maintenance_windows: [],
|
||||||
updated_at: decryptedCreatedMonitor.updated_at,
|
updated_at: decryptedCreatedMonitor.updated_at,
|
||||||
created_at: decryptedCreatedMonitor.created_at,
|
created_at: decryptedCreatedMonitor.created_at,
|
||||||
});
|
});
|
||||||
|
@ -601,6 +603,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
|
||||||
params: '',
|
params: '',
|
||||||
max_attempts: 2,
|
max_attempts: 2,
|
||||||
labels: {},
|
labels: {},
|
||||||
|
maintenance_windows: [],
|
||||||
updated_at: decryptedCreatedMonitor.updated_at,
|
updated_at: decryptedCreatedMonitor.updated_at,
|
||||||
created_at: decryptedCreatedMonitor.created_at,
|
created_at: decryptedCreatedMonitor.created_at,
|
||||||
});
|
});
|
||||||
|
@ -709,6 +712,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
|
||||||
updated_at: decryptedCreatedMonitor.updated_at,
|
updated_at: decryptedCreatedMonitor.updated_at,
|
||||||
created_at: decryptedCreatedMonitor.created_at,
|
created_at: decryptedCreatedMonitor.created_at,
|
||||||
labels: {},
|
labels: {},
|
||||||
|
maintenance_windows: [],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -56,5 +56,6 @@
|
||||||
"ssl.verification_mode": "full",
|
"ssl.verification_mode": "full",
|
||||||
"revision": 1,
|
"revision": 1,
|
||||||
"max_attempts": 2,
|
"max_attempts": 2,
|
||||||
"labels": {}
|
"labels": {},
|
||||||
|
"maintenance_windows": []
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,5 +79,6 @@
|
||||||
"ipv4": true,
|
"ipv4": true,
|
||||||
"ipv6": true,
|
"ipv6": true,
|
||||||
"params": "",
|
"params": "",
|
||||||
"labels": {}
|
"labels": {},
|
||||||
|
"maintenance_windows": []
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,5 +31,6 @@
|
||||||
"ipv4": true,
|
"ipv4": true,
|
||||||
"ipv6": true,
|
"ipv6": true,
|
||||||
"params": "",
|
"params": "",
|
||||||
"max_attempts": 2
|
"max_attempts": 2,
|
||||||
|
"maintenance_windows": []
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,5 +39,6 @@
|
||||||
"ipv4": true,
|
"ipv4": true,
|
||||||
"ipv6": true,
|
"ipv6": true,
|
||||||
"params": "",
|
"params": "",
|
||||||
"max_attempts": 2
|
"max_attempts": 2,
|
||||||
|
"maintenance_windows": []
|
||||||
}
|
}
|
||||||
|
|
|
@ -322,6 +322,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
|
||||||
journey_id: '',
|
journey_id: '',
|
||||||
max_attempts: 2,
|
max_attempts: 2,
|
||||||
labels: {},
|
labels: {},
|
||||||
|
maintenance_windows: [],
|
||||||
},
|
},
|
||||||
['config_id', 'id', 'form_monitor_type']
|
['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 {
|
return {
|
||||||
|
|
|
@ -16,6 +16,7 @@ export const commonVars = {
|
||||||
},
|
},
|
||||||
maintenance_windows: {
|
maintenance_windows: {
|
||||||
type: 'yaml',
|
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`,
|
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 },
|
ipv4: { type: 'bool', value: true },
|
||||||
ipv6: { type: 'bool', value: true },
|
ipv6: { type: 'bool', value: true },
|
||||||
mode: { type: 'text' },
|
mode: { type: 'text' },
|
||||||
|
maintenance_windows: {
|
||||||
|
type: 'yaml',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
id: `synthetics/http-http-4b6abc6c-118b-4d93-a489-1135500d09f1-${projectId}-default-d70a46e0-22ea-11ed-8c6b-09a2d21dfbc3`,
|
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