From ff37e75b914eaeb4b882fe8c70ef75f78866a3fb Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Wed, 13 Nov 2024 16:01:44 -0500 Subject: [PATCH] Remove default alerts notion and update warning callouts for status and tls rules. --- .../common/constants/synthetics_alerts.ts | 2 + .../alerting_callout.test.tsx | 211 +++++----- .../alerting_callout/alerting_callout.tsx | 393 +++++++++++------- .../overview/alert_missing_callout.tsx | 40 -- .../overview/overview/use_active_rules.ts | 50 --- .../monitors_page/overview/overview_page.tsx | 1 - .../synthetics/state/alert_rules/actions.ts | 11 +- .../apps/synthetics/state/alert_rules/api.ts | 32 +- .../synthetics/state/alert_rules/effects.ts | 26 +- .../synthetics/state/alert_rules/index.ts | 12 + .../synthetics/state/alert_rules/selectors.ts | 1 + .../apps/synthetics/state/root_effect.ts | 4 + .../apps/synthetics/state/ui/actions.ts | 7 +- .../default_alert_service.test.ts | 181 ++------ .../default_alerts/default_alert_service.ts | 66 +-- .../default_alerts/enable_default_alert.ts | 20 +- .../default_alerts/update_default_alert.ts | 7 +- .../add_monitor/add_monitor_api.ts | 19 - .../routes/settings/dynamic_settings.ts | 2 - .../server/runtime_types/settings.ts | 2 - 20 files changed, 533 insertions(+), 554 deletions(-) delete mode 100644 x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/alert_missing_callout.tsx delete mode 100644 x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/use_active_rules.ts diff --git a/x-pack/plugins/observability_solution/synthetics/common/constants/synthetics_alerts.ts b/x-pack/plugins/observability_solution/synthetics/common/constants/synthetics_alerts.ts index 4f525bda1756..be458f8387db 100644 --- a/x-pack/plugins/observability_solution/synthetics/common/constants/synthetics_alerts.ts +++ b/x-pack/plugins/observability_solution/synthetics/common/constants/synthetics_alerts.ts @@ -37,6 +37,8 @@ export const ACTION_GROUP_DEFINITIONS: { export const SYNTHETICS_STATUS_RULE = 'xpack.synthetics.alerts.monitorStatus'; export const SYNTHETICS_TLS_RULE = 'xpack.synthetics.alerts.tls'; +export type FlyoutIdArgument = typeof SYNTHETICS_STATUS_RULE | typeof SYNTHETICS_TLS_RULE | null; + export const SYNTHETICS_ALERT_RULE_TYPES = { MONITOR_STATUS: SYNTHETICS_STATUS_RULE, TLS: SYNTHETICS_TLS_RULE, diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/alerting_callout/alerting_callout.test.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/alerting_callout/alerting_callout.test.tsx index f6cbde251a92..690148781531 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/alerting_callout/alerting_callout.test.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/alerting_callout/alerting_callout.test.tsx @@ -6,12 +6,29 @@ */ import React from 'react'; +import * as reactRedux from 'react-redux'; import { waitFor } from '@testing-library/react'; import { render } from '../../../utils/testing/rtl_helpers'; -import { AlertingCallout, MISSING_RULES_PRIVILEGES_LABEL } from './alerting_callout'; +import { + AlertingCallout, + MISSING_DEFAULT_CONNECTOR_LABEL, + MISSING_MONITOR_STATUS_CONTENT, + MISSING_MONITOR_STATUS_HEADER, + MISSING_TLS_RULE_CONTENT, + MISSING_TLS_RULE_HEADER, +} from './alerting_callout'; +import { selectSyntheticsRules } from '../../../state/alert_rules/selectors'; +import { selectDynamicSettings } from '../../../state/settings'; +import { selectMonitorListState } from '../../../state'; +import { ConfigKey } from '../../../../../../common/runtime_types'; +import { SYNTHETICS_STATUS_RULE, SYNTHETICS_TLS_RULE } from '@kbn/rule-data-utils'; +import * as contextHelpers from '../../../contexts'; jest.mock('../../../contexts', () => ({ ...jest.requireActual('../../../contexts'), + useSyntheticsSettingsContext: jest.fn().mockReturnValue({ + canSave: true, + }), useSyntheticsStartPlugins: jest.fn().mockReturnValue({ share: { url: { @@ -25,116 +42,118 @@ jest.mock('../../../contexts', () => ({ }), })); +function injectReduxState(syntheticsRules?: any, dynamicSettings?: any, monitorList?: any) { + jest.spyOn(reactRedux, 'useSelector').mockImplementation((selector) => { + if (selector === selectSyntheticsRules) { + return syntheticsRules; + } else if (selector === selectDynamicSettings) { + return dynamicSettings; + } else if (selector === selectMonitorListState) { + return monitorList; + } + }); +} + describe('AlertingCallout', () => { + afterEach(() => jest.clearAllMocks()); + it('renders null when `activeRules` is undefined', () => { + injectReduxState(undefined, { settings: {} }, { data: { monitors: [] }, loaded: true }); + const { container } = render(); + expect(container).toBeEmptyDOMElement(); + }); it.each([ - [false, false, false], - [false, true, true], - [true, false, false], - [true, true, false], - ])('renders correctly', async (hasConnectors, statusAlertEnabled, shouldShowCallout) => { - const { getByText, queryByText } = render(, { - state: { - dynamicSettings: { - ...(shouldShowCallout - ? { - settings: { - defaultTLSRuleEnabled: true, - }, - } - : {}), - }, - defaultAlerting: { - data: { - statusRule: {}, - tlsRule: {}, - }, - loading: false, - success: true, + [ + // rules + [{ rule_type_id: SYNTHETICS_STATUS_RULE, enabled: true }], + // monitors + [ + { + [ConfigKey.ALERT_CONFIG]: { status: { enabled: true } }, + [ConfigKey.MONITOR_TYPE]: 'http', + [ConfigKey.ENABLED]: true, }, - monitorList: { - loaded: true, - data: { - total: 1, - monitors: [ - { - alert: { - status: { - enabled: statusAlertEnabled, - }, - }, - }, - ], - }, + ], + // expected messages + [MISSING_DEFAULT_CONNECTOR_LABEL, MISSING_TLS_RULE_CONTENT, MISSING_TLS_RULE_HEADER], + ], + [ + // rules + [{ rule_type_id: SYNTHETICS_TLS_RULE, enabled: true }], + // monitors + [ + { + [ConfigKey.ALERT_CONFIG]: { status: { enabled: true } }, + [ConfigKey.MONITOR_TYPE]: 'http', + [ConfigKey.ENABLED]: true, }, - }, - }); + ], + // expected messages + [ + MISSING_DEFAULT_CONNECTOR_LABEL, + MISSING_MONITOR_STATUS_CONTENT, + MISSING_MONITOR_STATUS_HEADER, + ], + ], + ])('renders correctly', async (rules, monitors, expectedContent) => { + jest + .spyOn(contextHelpers, 'useSyntheticsSettingsContext') + // casting to any because we aren't testing the rest of these fields + .mockReturnValue({ canSave: true } as any); + injectReduxState( + rules, + { settings: {} }, + { + data: { monitors }, + loaded: true, + } + ); + const { getByText } = render(); await waitFor(() => { - if (shouldShowCallout) { - expect(getByText(/Alerts are not being sent/)).toBeInTheDocument(); - } else { - expect(queryByText(/Alerts are not being sent/)).not.toBeInTheDocument(); - } + expectedContent.forEach((content) => { + expect(getByText(content)).toBeInTheDocument(); + }); }); }); - it.each([ - [false, false, false], - [false, true, true], - [true, false, false], - [true, true, false], - ])( - 'overwrites rendering with isAlertingEnabled prop', - async (hasConnectors, statusAlertEnabled, shouldShowCallout) => { - const { getByText, queryByText } = render( - , + it.each([true, false])( + '`isAlertingEnabled` impacts whether callouts are rendered', + async (isAlertingEnabled) => { + injectReduxState( + [], + { settings: {} }, { - state: { - dynamicSettings: { - ...(shouldShowCallout - ? { - settings: { - defaultTLSRuleEnabled: true, - }, - } - : {}), - }, - defaultAlerting: { - data: { - statusRule: {}, - tlsRule: {}, + data: { + monitors: [ + { + [ConfigKey.ALERT_CONFIG]: { status: { enabled: true } }, + [ConfigKey.MONITOR_TYPE]: 'http', + [ConfigKey.ENABLED]: true, }, - loading: false, - success: true, - }, + ], }, } ); + const { container, getByText } = render( + + ); - await waitFor(() => { - if (shouldShowCallout) { - expect(getByText(/Alerts are not being sent/)).toBeInTheDocument(); - } else { - expect(queryByText(/Alerts are not being sent/)).not.toBeInTheDocument(); - } - }); + const calloutContent = [ + MISSING_MONITOR_STATUS_CONTENT, + MISSING_MONITOR_STATUS_HEADER, + MISSING_TLS_RULE_CONTENT, + MISSING_TLS_RULE_HEADER, + ]; + + if (!isAlertingEnabled) { + expect(container.firstChild).toBeNull(); + } else { + await waitFor(() => { + if (isAlertingEnabled) { + calloutContent.forEach((content) => expect(getByText(content)).toBeInTheDocument()); + } + }); + } } ); - - it('show call out for missing privileges rules', async () => { - const { getByText } = render(, { - state: { - defaultAlerting: { - data: {}, - loading: false, - success: true, - }, - }, - }); - - await waitFor(() => { - expect(getByText(/Alerts are not being sent/)).toBeInTheDocument(); - expect(getByText(MISSING_RULES_PRIVILEGES_LABEL)).toBeInTheDocument(); - }); - }); }); diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/alerting_callout/alerting_callout.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/alerting_callout/alerting_callout.tsx index f88691a1f028..d9188e63e94a 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/alerting_callout/alerting_callout.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/alerting_callout/alerting_callout.tsx @@ -5,41 +5,50 @@ * 2.0. */ -import React, { useEffect } from 'react'; -import { FormattedMessage } from '@kbn/i18n-react'; +import React, { useCallback, useEffect, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { EuiButton, EuiButtonEmpty, EuiCallOut, EuiMarkdownFormat, EuiSpacer } from '@elastic/eui'; +import { + EuiButton, + EuiButtonEmpty, + EuiCallOut, + EuiFlexGroup, + EuiFlexItem, + EuiIconTip, + EuiSpacer, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; import { syntheticsSettingsLocatorID } from '@kbn/observability-plugin/common'; import { useFetcher } from '@kbn/observability-shared-plugin/public'; -import useSessionStorage from 'react-use/lib/useSessionStorage'; import { i18n } from '@kbn/i18n'; import { isEmpty } from 'lodash'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { ClientPluginsStart } from '../../../../../plugin'; import { selectDynamicSettings } from '../../../state/settings'; -import { - selectSyntheticsAlerts, - selectSyntheticsAlertsLoaded, -} from '../../../state/alert_rules/selectors'; -import { selectMonitorListState } from '../../../state'; +import { selectMonitorListState, setAlertFlyoutVisible } from '../../../state'; import { getDynamicSettingsAction } from '../../../state/settings/actions'; import { useSyntheticsSettingsContext } from '../../../contexts'; import { ConfigKey } from '../../../../../../common/runtime_types'; -import { useActiveRules } from '../../monitors_page/overview/overview/use_active_rules'; import { + FlyoutIdArgument, SYNTHETICS_STATUS_RULE, SYNTHETICS_TLS_RULE, } from '../../../../../../common/constants/synthetics_alerts'; +import { enableDefaultAlertingAction, getSyntheticsRules } from '../../../state/alert_rules'; +import { selectSyntheticsRules } from '../../../state/alert_rules/selectors'; export const AlertingCallout = ({ isAlertingEnabled }: { isAlertingEnabled?: boolean }) => { const dispatch = useDispatch(); - const defaultRules = useSelector(selectSyntheticsAlerts); - const rulesLoaded = useSelector(selectSyntheticsAlertsLoaded); + const activeRules = useSelector(selectSyntheticsRules); + useEffect(() => { + if (!activeRules) { + dispatch(getSyntheticsRules()); + } + }, [dispatch, activeRules]); + const { settings } = useSelector(selectDynamicSettings); const hasDefaultConnector = !settings || !isEmpty(settings?.defaultConnectors); - const defaultRuleEnabled = settings?.defaultTLSRuleEnabled || settings?.defaultStatusRuleEnabled; const { canSave } = useSyntheticsSettingsContext(); @@ -56,176 +65,276 @@ export const AlertingCallout = ({ isAlertingEnabled }: { isAlertingEnabled?: boo return locator?.getUrl({}); }, [locator]); - const { activeRules, activeRuleLoading } = useActiveRules(); - console.log('active rules', activeRules, activeRuleLoading); - - // if (!activeRules) { - // return null; - // } - console.log('active rules', activeRules); - const hasStatusRule = activeRules.some( - (rule) => rule.rule_type_id === SYNTHETICS_STATUS_RULE && rule.enabled - ); - const hasTls = activeRules.some( - (rule) => rule.rule_type_id === SYNTHETICS_TLS_RULE && rule.enabled - ); - console.log('has status rule', hasStatusRule); - console.log('has tls rule', hasTls); - const hasMonitorsRunningWithAlertingConfigured = isAlertingEnabled ?? (monitorsLoaded && monitors.some((monitor) => monitor[ConfigKey.ALERT_CONFIG]?.status?.enabled)); const hasHttpMonitorsRunningWithAlertingEnabled = - monitorsLoaded && - monitors.some( - (monitor) => - monitor[ConfigKey.MONITOR_TYPE] === 'http' && - monitor[ConfigKey.ALERT_CONFIG]?.status?.enabled && - monitor[ConfigKey.ENABLED] - ); - const showMonitorStatusCallout = !hasStatusRule && hasMonitorsRunningWithAlertingConfigured; - const showTlsCallout = !hasTls && hasHttpMonitorsRunningWithAlertingEnabled; - console.log('monitors', monitors); - console.log('has http monitors', hasHttpMonitorsRunningWithAlertingEnabled); - - console.log('url', url); - const showCallout = - !hasDefaultConnector && hasMonitorsRunningWithAlertingConfigured && defaultRuleEnabled; - const hasDefaultRules = - !rulesLoaded || Boolean(defaultRules?.statusRule && defaultRules?.tlsRule); - const missingRules = !hasDefaultRules && !canSave; - + isAlertingEnabled ?? + (monitorsLoaded && + monitors.some( + (monitor) => + monitor[ConfigKey.MONITOR_TYPE] === 'http' && + monitor[ConfigKey.ALERT_CONFIG]?.status?.enabled && + monitor[ConfigKey.ENABLED] + )); + // display a callout to the user if there are monitors with alerting enabled but no default connector configured useEffect(() => { dispatch(getDynamicSettingsAction.get()); }, [dispatch]); - if (!showMonitorStatusCallout && !showTlsCallout) return null; + const [monitorStatusRuleFlyoutArgs] = useState({ + id: SYNTHETICS_STATUS_RULE, + isNewRuleFlyout: true, + }); + const [tlsRuleFlyoutArgs] = useState({ + id: SYNTHETICS_TLS_RULE, + isNewRuleFlyout: true, + }); + + if (!activeRules) return null; + const hasStatusRule = activeRules.some( + (rule) => rule.rule_type_id === SYNTHETICS_STATUS_RULE && rule.enabled + ); + const hasTlsRules = activeRules.some( + (rule) => rule.rule_type_id === SYNTHETICS_TLS_RULE && rule.enabled + ); + const showMonitorStatusCallout = + canSave && !hasStatusRule && hasMonitorsRunningWithAlertingConfigured; + const showTlsCallout = canSave && !hasTlsRules && hasHttpMonitorsRunningWithAlertingEnabled; + const showConnectorCallout = + canSave && !hasDefaultConnector && hasMonitorsRunningWithAlertingConfigured; + if (!showMonitorStatusCallout && !showTlsCallout && !showConnectorCallout) return null; return ( <> - - + + + ); - // return ( - // <> - // - // - // - // ); }; -const MissingMonitorStatusRuleCallout = ({ showCallout }: { showCallout?: boolean }) => { - if (!showCallout) { - return null; - } +function toRulesProps(testId: string, url?: string) { + if (!url) return undefined; + return { + url, + 'data-test-subj': testId, + }; +} + +interface ManageRulesProps { + url: string; + 'data-test-subj': string; +} + +interface RuleFlyoutArgs { + id: FlyoutIdArgument; + isNewRuleFlyout: boolean; +} + +interface MissingRuleCalloutProps { + showCallout: boolean; + setFlyoutVisibleArgs: RuleFlyoutArgs; + title: string; + buttonLabel: string; + content: string; + defaultRuleType: FlyoutIdArgument; + createRuleTestId: string; + enableDefaultRuleTestId: string; + manageRulesProps?: ManageRulesProps; +} + +const MissingConnectorCallout = ({ + showCallout, + url, + 'data-test-subj': testId, +}: { + showCallout: boolean; + url?: string; + 'data-test-subj': string; +}) => { + if (!showCallout) return null; return ( <> - - You have Monitors enabled that are not covered by alerts + +

{MISSING_DEFAULT_CONNECTOR_LABEL}

+ + + + + + + +
); }; -const MissingTlsCallout = ({ showCallout }: { showCallout?: boolean }) => { + +const MissingRuleCallout = ({ + showCallout, + setFlyoutVisibleArgs, + title, + buttonLabel, + content, + manageRulesProps, + createRuleTestId, + enableDefaultRuleTestId, + defaultRuleType, +}: MissingRuleCalloutProps) => { + const dispatch = useDispatch(); + const showFlyout = useCallback( + () => dispatch(setAlertFlyoutVisible(setFlyoutVisibleArgs)), + [dispatch, setFlyoutVisibleArgs] + ); + const dispatchEnableDefaultRule = useCallback(() => { + dispatch( + enableDefaultAlertingAction.get({ + enableTls: defaultRuleType === SYNTHETICS_TLS_RULE, + enableMonitorStatus: defaultRuleType === SYNTHETICS_STATUS_RULE, + }) + ); + }, [dispatch, defaultRuleType]); if (!showCallout) { return null; } return ( <> - - You have HTTP Monitors enabled that are not covered by TLS alerts + + {content} + + + + + + {buttonLabel} + + + + + + + + + + {ENABLE_DEFAULT_RULE_LABEL} + + + + + {!!manageRulesProps && ( + + + {MANAGE_RULES_LABEL} + + + )} + ); }; -const MissingRulesCallout = ({ - url, - missingRules, - missingConfig, -}: { - url?: string; - missingConfig?: boolean; - missingRules?: boolean; -}) => { - const [isHidden, setIsHidden] = useSessionStorage('MissingRulesCalloutHidden', false); - - if ((!missingConfig && !missingRules) || isHidden) { - return null; +export const MISSING_CONNECTOR_CALLOUT_TITLE = i18n.translate( + 'xpack.synthetics.alerting.noConnectorsCallout.title', + { + defaultMessage: 'Default connector is not defined', } - const point = missingRules === missingConfig ? '* ' : ''; - - const configCallout = missingConfig ? ( - {`${point}${MISSING_CONFIG_LABEL}`} - ) : null; - - const rulesCallout = missingRules ? ( - {`${point}${MISSING_RULES_PRIVILEGES_LABEL}`} - ) : null; - - return ( - - } - color="warning" - iconType="warning" - > - {configCallout} - {rulesCallout} - {missingConfig && ( - <> - - - - - - )} - { - setIsHidden(true); - }} - > - - - - ); -}; - -const MISSING_CONFIG_LABEL = i18n.translate( +); +export const MISSING_DEFAULT_CONNECTOR_LABEL = i18n.translate( 'xpack.synthetics.alerting.noConnectorsCallout.content', { defaultMessage: 'You have monitors with alerting enabled, but there is no default connector configured to send those alerts.', } ); - -export const MISSING_RULES_PRIVILEGES_LABEL = i18n.translate( - 'xpack.synthetics.alerting.missingRules.content', +export const MISSING_MONITOR_STATUS_HEADER = i18n.translate( + 'xpack.synthetics.alerting.noMonitorStatus.header', { - defaultMessage: - 'You have monitors with alerting enabled, but there is no rules configured to send those alerts. Default rules are automatically created when user with write privileges opens Synthetics app.', + defaultMessage: 'Monitor Status Alerts will not be sent', + } +); +export const MISSING_MONITOR_STATUS_CONTENT = i18n.translate( + 'xpack.synthetics.alerting.noMonitorStatus.content', + { + defaultMessage: 'You have Monitors enabled that are not covered by alerts', + } +); +export const MISSING_TLS_RULE_CONTENT = i18n.translate('xpack.synthetics.alerting.noTls.header', { + defaultMessage: 'You have HTTP Monitors enabled that are not covered by TLS alerts', +}); +export const MISSING_TLS_RULE_HEADER = i18n.translate('xpack.synthetics.alerting.noTls.content', { + defaultMessage: 'TLS Alerts will not be sent', +}); +export const CONFIGURE_CUSTOM_RULE_LABEL = i18n.translate( + 'xpack.synthetics.alerting.customRule.label', + { + defaultMessage: 'Configure custom rule', + } +); +export const ENABLE_DEFAULT_RULE_LABEL = i18n.translate( + 'xpack.synthetics.alerting.enableDefaultRule.label', + { + defaultMessage: 'Enable default rule', } ); +export const MANAGE_RULES_LABEL = i18n.translate('xpack.synthetics.alerting.manageRules.label', { + defaultMessage: 'Manage rules', +}); diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/alert_missing_callout.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/alert_missing_callout.tsx deleted file mode 100644 index dde755280230..000000000000 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/alert_missing_callout.tsx +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { EuiCallOut } from '@elastic/eui'; -import React from 'react'; -import { useActiveRules } from './use_active_rules'; -import { - SYNTHETICS_STATUS_RULE, - SYNTHETICS_TLS_RULE, -} from '../../../../../../../common/constants/synthetics_alerts'; - -export function AlertMissingCallout() { - const { activeRules, activeRuleLoading } = useActiveRules(); - console.log('active rules', activeRules, activeRuleLoading); - - if (!activeRules) { - return null; - } - console.log('active rules', activeRules); - const hasStatusRule = activeRules.some( - (rule) => rule.rule_type_id === SYNTHETICS_STATUS_RULE && rule.enabled - ); - const hasTls = activeRules.some( - (rule) => rule.rule_type_id === SYNTHETICS_TLS_RULE && rule.enabled - ); - console.log('has status rule', hasStatusRule); - console.log('has tls rule', hasTls); - return ( - <> - {!hasStatusRule && ( - - )} - {!hasTls && } - - ); -} diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/use_active_rules.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/use_active_rules.ts deleted file mode 100644 index a461d1f96f37..000000000000 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/use_active_rules.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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 { useFetcher } from '@kbn/observability-shared-plugin/public'; -import { ClientPluginsStart } from '../../../../../../plugin'; -import { - SYNTHETICS_STATUS_RULE, - SYNTHETICS_TLS_RULE, -} from '../../../../../../../common/constants/synthetics_alerts'; - -export function generateFilter(types: string[]) { - if (types.length === 0) { - return ''; - } - - return types.reduce((acc, type) => { - if (acc === '') { - return `alert.attributes.alertTypeId: "${type}"`; - } - return `${acc} OR alert.attributes.alertTypeId: "${type}"`; - }, ''); -} - -interface RuleInfo { - id: string; - enabled: boolean; - name: string; - rule_type_id: string; -} - -export function useActiveRules() { - const { http } = useKibana().services; - - const { data, loading } = useFetcher( - () => - http.get(`/internal/alerting/rules/_find`, { - query: { - filter: generateFilter([SYNTHETICS_TLS_RULE, SYNTHETICS_STATUS_RULE]), - }, - }), - [http] - ); - - return { activeRuleLoading: loading, activeRules: (data as { data?: RuleInfo[] })?.data ?? [] }; -} diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview_page.tsx b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview_page.tsx index 6a68f5cfcab7..d1a872455503 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview_page.tsx +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/monitors_page/overview/overview_page.tsx @@ -28,7 +28,6 @@ import { SearchField } from '../common/search_field'; import { NoMonitorsFound } from '../common/no_monitors_found'; import { OverviewErrors } from './overview/overview_errors/overview_errors'; import { AlertingCallout } from '../../common/alerting_callout/alerting_callout'; -import { AlertMissingCallout } from './overview/alert_missing_callout'; export const OverviewPage: React.FC = () => { useTrackPageview({ app: 'synthetics', path: 'overview' }); diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/alert_rules/actions.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/alert_rules/actions.ts index e004b5a34396..965782468de6 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/alert_rules/actions.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/alert_rules/actions.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { createAction } from '@reduxjs/toolkit'; import { DEFAULT_ALERT_RESPONSE } from '../../../../../common/types/default_alerts'; import { createAsyncAction } from '../utils/actions'; @@ -12,9 +13,10 @@ export const getDefaultAlertingAction = createAsyncAction( - 'enableDefaultAlertingAction' -); +export const enableDefaultAlertingAction = createAsyncAction< + { enableTls: boolean; enableMonitorStatus: boolean }, + DEFAULT_ALERT_RESPONSE +>('enableDefaultAlertingAction'); export const enableDefaultAlertingSilentlyAction = createAsyncAction( 'enableDefaultAlertingSilentlyAction' @@ -23,3 +25,6 @@ export const enableDefaultAlertingSilentlyAction = createAsyncAction( 'updateDefaultAlertingAction' ); + +export const getSyntheticsRules = createAction('getSyntheticsRules'); +export const putSyntheticsRules = createAction('putSyntheticsRules'); diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/alert_rules/api.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/alert_rules/api.ts index 909976e1f284..8ca715943476 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/alert_rules/api.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/alert_rules/api.ts @@ -5,18 +5,46 @@ * 2.0. */ +import { + SYNTHETICS_STATUS_RULE, + SYNTHETICS_TLS_RULE, +} from '../../../../../common/constants/synthetics_alerts'; import { SYNTHETICS_API_URLS } from '../../../../../common/constants'; import { DEFAULT_ALERT_RESPONSE } from '../../../../../common/types/default_alerts'; import { apiService } from '../../../../utils/api_service'; +interface Payload { + enableTls: boolean; + enableMonitorStatus: boolean; +} + export async function getDefaultAlertingAPI(): Promise { return apiService.get(SYNTHETICS_API_URLS.ENABLE_DEFAULT_ALERTING); } -export async function enableDefaultAlertingAPI(): Promise { - return apiService.post(SYNTHETICS_API_URLS.ENABLE_DEFAULT_ALERTING); +export async function enableDefaultAlertingAPI(payload?: Payload): Promise { + return apiService.post(SYNTHETICS_API_URLS.ENABLE_DEFAULT_ALERTING, payload); } export async function updateDefaultAlertingAPI(): Promise { return apiService.put(SYNTHETICS_API_URLS.ENABLE_DEFAULT_ALERTING); } + +export function generateFilter(types: string[]) { + if (types.length === 0) { + return ''; + } + + return types.reduce((acc, type) => { + if (acc === '') { + return `alert.attributes.alertTypeId: "${type}"`; + } + return `${acc} OR alert.attributes.alertTypeId: "${type}"`; + }, ''); +} +export async function getActiveRulesAPI(): Promise { + return apiService.post('/internal/alerting/rules/_find', { + filter: generateFilter([SYNTHETICS_TLS_RULE, SYNTHETICS_STATUS_RULE]), + per_page: 10000, + }); +} diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/alert_rules/effects.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/alert_rules/effects.ts index e4d15339065a..bb68d2401177 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/alert_rules/effects.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/alert_rules/effects.ts @@ -5,19 +5,27 @@ * 2.0. */ -import { takeLeading } from 'redux-saga/effects'; +import { call, put, takeEvery, takeLeading } from 'redux-saga/effects'; import { i18n } from '@kbn/i18n'; import { enableDefaultAlertingAction, enableDefaultAlertingSilentlyAction, getDefaultAlertingAction, + getSyntheticsRules, + putSyntheticsRules, updateDefaultAlertingAction, } from './actions'; import { fetchEffectFactory } from '../utils/fetch_effect'; -import { enableDefaultAlertingAPI, getDefaultAlertingAPI, updateDefaultAlertingAPI } from './api'; +import { + enableDefaultAlertingAPI, + getActiveRulesAPI, + getDefaultAlertingAPI, + updateDefaultAlertingAPI, +} from './api'; export function* getDefaultAlertingEffect() { yield takeLeading( + // @ts-expect-error TODO: unsure why this is failing getDefaultAlertingAction.get, fetchEffectFactory( getDefaultAlertingAPI, @@ -42,8 +50,22 @@ export function* enableDefaultAlertingEffect() { ); } +function* fetchLatestSyntheticsRulesData(): any { + const activeAlerts = yield call(getActiveRulesAPI) as any; + yield put(putSyntheticsRules(activeAlerts.data)); +} + +export function* fetchSyntheticsRules() { + yield takeEvery(getSyntheticsRules, fetchLatestSyntheticsRulesData); +} + +export function* updateActiveRulesEffect(): Generator { + yield takeEvery(enableDefaultAlertingAction.success, fetchLatestSyntheticsRulesData); +} + export function* enableDefaultAlertingSilentlyEffect() { yield takeLeading( + // @ts-expect-error TODO: unsure why this is failing enableDefaultAlertingSilentlyAction.get, fetchEffectFactory( enableDefaultAlertingAPI, diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/alert_rules/index.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/alert_rules/index.ts index 7dc44fa1dbf0..25e6f6f4bcc7 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/alert_rules/index.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/alert_rules/index.ts @@ -12,10 +12,19 @@ import { enableDefaultAlertingAction, enableDefaultAlertingSilentlyAction, getDefaultAlertingAction, + putSyntheticsRules, updateDefaultAlertingAction, } from './actions'; +export interface RuleInfo { + id: string; + enabled: boolean; + name: string; + rule_type_id: string; +} + export interface DefaultAlertingState { + syntheticsRules?: RuleInfo[]; data?: DEFAULT_ALERT_RESPONSE; success: boolean | null; loading: boolean; @@ -56,6 +65,9 @@ export const defaultAlertingReducer = createReducer(initialSettingState, (builde state.success = Boolean(action.payload); state.loading = false; }) + .addCase(putSyntheticsRules, (state, action) => { + state.syntheticsRules = action.payload; + }) .addCase(updateDefaultAlertingAction.fail, (state, action) => { state.error = action.payload; state.loading = false; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/alert_rules/selectors.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/alert_rules/selectors.ts index 53ade8ab44ab..a4fa29d3d790 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/alert_rules/selectors.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/alert_rules/selectors.ts @@ -12,3 +12,4 @@ const getState = (appState: SyntheticsAppState) => appState.defaultAlerting; export const selectSyntheticsAlerts = createSelector(getState, (state) => state.data); export const selectSyntheticsAlertsLoading = createSelector(getState, (state) => state.loading); export const selectSyntheticsAlertsLoaded = createSelector(getState, (state) => state.success); +export const selectSyntheticsRules = createSelector(getState, (state) => state.syntheticsRules); diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/root_effect.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/root_effect.ts index e38a1b5ad918..df52963a3ab5 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/root_effect.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/root_effect.ts @@ -17,7 +17,9 @@ import { fetchManualTestRunsEffect } from './manual_test_runs/effects'; import { enableDefaultAlertingEffect, enableDefaultAlertingSilentlyEffect, + fetchSyntheticsRules, getDefaultAlertingEffect, + updateActiveRulesEffect, updateDefaultAlertingEffect, } from './alert_rules/effects'; import { executeEsQueryEffect } from './elasticsearch'; @@ -80,5 +82,7 @@ export const rootEffect = function* root(): Generator { fork(quietFetchMonitorStatusHeatmap), fork(fetchOverviewTrendStats), fork(refreshOverviewTrendStats), + fork(fetchSyntheticsRules), + fork(updateActiveRulesEffect), ]); }; diff --git a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/ui/actions.ts b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/ui/actions.ts index 7a9e3e2884b9..4d42f5af43cd 100644 --- a/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/ui/actions.ts +++ b/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/state/ui/actions.ts @@ -6,10 +6,7 @@ */ import { createAction } from '@reduxjs/toolkit'; -import { - SYNTHETICS_STATUS_RULE, - SYNTHETICS_TLS_RULE, -} from '../../../../../common/constants/synthetics_alerts'; +import type { FlyoutIdArgument } from '../../../../../common/constants/synthetics_alerts'; export interface PopoverState { id: string; @@ -17,7 +14,7 @@ export interface PopoverState { } export const setAlertFlyoutVisible = createAction<{ - id: typeof SYNTHETICS_STATUS_RULE | typeof SYNTHETICS_TLS_RULE | null; + id: FlyoutIdArgument; isNewRuleFlyout: boolean; } | null>('[UI] TOGGLE ALERT FLYOUT'); diff --git a/x-pack/plugins/observability_solution/synthetics/server/routes/default_alerts/default_alert_service.test.ts b/x-pack/plugins/observability_solution/synthetics/server/routes/default_alerts/default_alert_service.test.ts index 887d7c71564c..44b66a6c4eab 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/routes/default_alerts/default_alert_service.test.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/routes/default_alerts/default_alert_service.test.ts @@ -39,68 +39,11 @@ describe('DefaultAlertService', () => { expect(settings).toEqual({ ...expectedSettings, defaultEmail: undefined, - defaultStatusRuleEnabled: true, - defaultTLSRuleEnabled: true, }); expect(soClient.get).toHaveBeenCalledTimes(1); }); }); - describe('setupDefaultAlerts', () => { - afterEach(() => jest.resetAllMocks()); - - it('sets up status and tls rules', async () => { - const soClient = { get: jest.fn() } as any; - const service = new DefaultAlertService({} as any, {} as any, soClient); - service.getSettings = jest.fn().mockResolvedValue({ - certAgeThreshold: 50, - certExpirationThreshold: 10, - defaultConnectors: ['slack', 'email'], - defaultEmail: undefined, - defaultStatusRuleEnabled: true, - defaultTLSRuleEnabled: true, - }); - const setupStatusRule = jest.fn(); - const setupTlsRule = jest.fn(); - service.setupStatusRule = setupStatusRule; - service.setupTlsRule = setupTlsRule; - setupStatusRule.mockResolvedValueOnce({ status: 'fulfilled', value: {} }); - setupTlsRule.mockResolvedValueOnce({ status: 'fulfilled', value: {} }); - const result = await service.setupDefaultAlerts(); - expect(setupStatusRule).toHaveBeenCalledTimes(1); - expect(setupTlsRule).toHaveBeenCalledTimes(1); - expect(result).toEqual({ - statusRule: { status: 'fulfilled', value: {} }, - tlsRule: { status: 'fulfilled', value: {} }, - }); - }); - it('returns null rules if value is falsy', async () => { - const soClient = { get: jest.fn() } as any; - const service = new DefaultAlertService({} as any, {} as any, soClient); - service.getSettings = jest.fn().mockResolvedValue({ - certAgeThreshold: 50, - certExpirationThreshold: 10, - defaultConnectors: ['slack', 'email'], - defaultEmail: undefined, - defaultStatusRuleEnabled: true, - defaultTLSRuleEnabled: true, - }); - const setupStatusRule = jest.fn(); - const setupTlsRule = jest.fn(); - service.setupStatusRule = setupStatusRule; - service.setupTlsRule = setupTlsRule; - setupStatusRule.mockResolvedValueOnce(undefined); - setupTlsRule.mockResolvedValueOnce(undefined); - const result = await service.setupDefaultAlerts(); - expect(setupStatusRule).toHaveBeenCalledTimes(1); - expect(setupTlsRule).toHaveBeenCalledTimes(1); - expect(result).toEqual({ - statusRule: null, - tlsRule: null, - }); - }); - }); - describe('getMinimumRuleInterval', () => { it('returns 1m if minimum interval is less than 1m', () => { const server = { @@ -124,10 +67,8 @@ describe('DefaultAlertService', () => { const service = new DefaultAlertService({} as any, {} as any, {} as any); service.getMinimumRuleInterval = jest.fn().mockReturnValue('1m'); service.createDefaultRuleIfNotExist = jest.fn(); - service.settings = { defaultStatusRuleEnabled: true } as any; - service.getSettings = jest.fn().mockResolvedValue({ - defaultStatusRuleEnabled: true, - }); + service.settings = {} as any; + service.getSettings = jest.fn().mockResolvedValue({}); await service.setupStatusRule(); expect(service.createDefaultRuleIfNotExist).toHaveBeenCalledWith( SYNTHETICS_STATUS_RULE, @@ -135,16 +76,6 @@ describe('DefaultAlertService', () => { '1m' ); }); - - it('does not create status rule if disabled', async () => { - const service = new DefaultAlertService({} as any, {} as any, {} as any); - service.getMinimumRuleInterval = jest.fn().mockReturnValue('1m'); - service.createDefaultRuleIfNotExist = jest.fn(); - service.settings = { defaultStatusRuleEnabled: false } as any; - const result = await service.setupStatusRule(); - expect(service.createDefaultRuleIfNotExist).not.toHaveBeenCalled(); - expect(result).toBeUndefined(); - }); }); describe('setupTlsRule', () => { @@ -152,10 +83,8 @@ describe('DefaultAlertService', () => { const service = new DefaultAlertService({} as any, {} as any, {} as any); service.getMinimumRuleInterval = jest.fn().mockReturnValue('1m'); service.createDefaultRuleIfNotExist = jest.fn(); - service.settings = { defaultTlsRuleEnabled: true } as any; - service.getSettings = jest.fn().mockResolvedValue({ - defaultTlsRuleEnabled: true, - }); + service.settings = {} as any; + service.getSettings = jest.fn().mockResolvedValue({}); await service.setupTlsRule(); expect(service.createDefaultRuleIfNotExist).toHaveBeenCalledWith( SYNTHETICS_TLS_RULE, @@ -163,23 +92,18 @@ describe('DefaultAlertService', () => { '1m' ); }); - - it('does not create tls rule if disabled', async () => { - const service = new DefaultAlertService({} as any, {} as any, {} as any); - service.getMinimumRuleInterval = jest.fn().mockReturnValue('1m'); - service.createDefaultRuleIfNotExist = jest.fn(); - service.settings = { defaultTLSRuleEnabled: false } as any; - const result = await service.setupTlsRule(); - expect(service.createDefaultRuleIfNotExist).not.toHaveBeenCalled(); - expect(result).toBeUndefined(); - }); }); describe('existing alerts', () => { function setUpExistingRules>( ruleOverride?: Partial>, - getRulesClientMocks = {} + getRulesClientMocks = {}, + savedObjectsClientMocks = {} ) { + const savedObjectsClient = { + get: jest.fn().mockReturnValue({ attributes: {} }), + ...savedObjectsClientMocks, + }; const getRulesClient = jest.fn(); const mockRule: any = ruleOverride ?? { actions: [{ alertsFilter: { query: { kql: 'some kql', filters: [] } } }], @@ -192,9 +116,10 @@ describe('DefaultAlertService', () => { find.mockResolvedValue({ data: [mockRule], }); - getRulesClient.mockReturnValue({ find, ...getRulesClientMocks }); + const bulkDeleteRules = jest.fn(); + getRulesClient.mockReturnValue({ find, bulkDeleteRules, ...getRulesClientMocks }); - return { getRulesClient, mockRule }; + return { getRulesClient, mockRule, savedObjectsClient }; } function formatMockRuleResult(mockRule: any) { @@ -233,11 +158,35 @@ describe('DefaultAlertService', () => { }); describe('createDefaultAlertIfNotExist', () => { it('returns rule if exists', async () => { - const { getRulesClient, mockRule } = setUpExistingRules(); + const sampleAction = { alertsFilter: { query: { kql: 'some kql', filters: [] } } }; + const { getRulesClient, mockRule, savedObjectsClient } = setUpExistingRules( + undefined, + { + create: jest.fn().mockResolvedValue({ + actions: [sampleAction], + systemActions: [ + { + actionTypeId: 'actionTypeId', + id: 'some system action', + params: {}, + }, + ], + id: '123', + alertTypeId: 'xpack.synthetics.alerts.monitorStatus', + }), + }, + { attributes: { defaultEmail: '' } } + ); + const getActionsClient = jest.fn(); + getActionsClient.mockReturnValue({ + getAll: jest + .fn() + .mockResolvedValue([{ id: 'id', actionTypeId: 'actionTypeId', name: 'action name' }]), + }); const service = new DefaultAlertService( - { alerting: { getRulesClient } } as any, + { actions: { getActionsClient }, alerting: { getRulesClient } } as any, {} as any, - {} as any + savedObjectsClient as any ); const alert = await service.createDefaultRuleIfNotExist( 'xpack.synthetics.alerts.monitorStatus', @@ -332,7 +281,7 @@ describe('DefaultAlertService', () => { }); const service = new DefaultAlertService(context as any, server as any, {} as any); service.settings = { defaultConnectors: ['slack', 'email'] } as any; - const result = await service.updateStatusRule(true); + const result = await service.updateStatusRule(); expect(result).toEqual({ actions: [ { actionTypeId: 'actionTypeId', id: 'id', name: 'action name' }, @@ -355,27 +304,6 @@ describe('DefaultAlertService', () => { }); expect(getAll).toHaveBeenCalled(); }); - - it('deletes the rule if it is disabled', async () => { - const server = { - alerting: { - getConfig: jest.fn().mockReturnValue({ minimumScheduleInterval: { value: '3m' } }), - }, - } as any; - const bulkDeleteRules = jest.fn(); - const { getRulesClient } = setUpExistingRules(undefined, { bulkDeleteRules }); - const service = new DefaultAlertService( - { alerting: { getRulesClient } } as any, - server as any, - {} as any - ); - await service.updateStatusRule(false); - expect(bulkDeleteRules).toHaveBeenCalled(); - expect(bulkDeleteRules.mock.calls[0][0]).toEqual({ - filter: - 'alert.attributes.alertTypeId:"xpack.synthetics.alerts.monitorStatus" AND alert.attributes.tags:"SYNTHETICS_DEFAULT_ALERT"', - }); - }); }); describe('updateTlsRule', () => { @@ -383,7 +311,7 @@ describe('DefaultAlertService', () => { const { context, server } = setUpUpdateTest(); const service = new DefaultAlertService(context as any, server as any, {} as any); service.settings = { defaultConnectors: ['slack', 'email'] } as any; - const result = await service.updateTlsRule(true); + const result = await service.updateTlsRule(); expect(result).toEqual({ actions: [ { actionTypeId: 'actionTypeId', id: 'id', name: 'action name' }, @@ -403,7 +331,7 @@ describe('DefaultAlertService', () => { service.getExistingAlert = getExistingAlertMock; const createDefaultAlertIfNotExistMock = jest.fn(); service.createDefaultRuleIfNotExist = createDefaultAlertIfNotExistMock; - const result = await service.updateTlsRule(true); + const result = await service.updateTlsRule(); expect(result).toBeUndefined(); expect(service.getExistingAlert).toHaveBeenCalled(); expect(service.createDefaultRuleIfNotExist).toHaveBeenCalled(); @@ -414,27 +342,6 @@ describe('DefaultAlertService', () => { '3m', ]); }); - - it('deletes the rule if it is disabled', async () => { - const server = { - alerting: { - getConfig: jest.fn().mockReturnValue({ minimumScheduleInterval: { value: '3m' } }), - }, - } as any; - const bulkDeleteRules = jest.fn(); - const { getRulesClient } = setUpExistingRules(undefined, { bulkDeleteRules }); - const service = new DefaultAlertService( - { alerting: { getRulesClient } } as any, - server as any, - {} as any - ); - await service.updateTlsRule(false); - expect(bulkDeleteRules).toHaveBeenCalled(); - expect(bulkDeleteRules.mock.calls[0][0]).toEqual({ - filter: - 'alert.attributes.alertTypeId:"xpack.synthetics.alerts.tls" AND alert.attributes.tags:"SYNTHETICS_DEFAULT_ALERT"', - }); - }); }); }); @@ -455,8 +362,6 @@ describe('DefaultAlertService', () => { actionConnectors: [{ id: 'id', actionTypeId: 'actionTypeId' }], settings: { ...DYNAMIC_SETTINGS_DEFAULTS, - defaultStatusRuleEnabled: true, - defaultTLSRuleEnabled: true, }, }); expect(getAll).toHaveBeenCalled(); diff --git a/x-pack/plugins/observability_solution/synthetics/server/routes/default_alerts/default_alert_service.ts b/x-pack/plugins/observability_solution/synthetics/server/routes/default_alerts/default_alert_service.ts index 56d70794dc37..eff32fd69608 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/routes/default_alerts/default_alert_service.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/routes/default_alerts/default_alert_service.ts @@ -45,12 +45,18 @@ export class DefaultAlertService { return this.settings; } - async setupDefaultAlerts() { + async setupDefaultAlerts({ + enableTls = true, + enableMonitorStatus = true, + }: Partial<{ + enableTls: boolean; + enableMonitorStatus: boolean; + }>) { this.settings = await this.getSettings(); const [statusRule, tlsRule] = await Promise.allSettled([ - this.setupStatusRule(), - this.setupTlsRule(), + enableMonitorStatus ? this.setupStatusRule() : undefined, + enableTls ? this.setupTlsRule() : undefined, ]); if (statusRule.status === 'rejected') { @@ -73,12 +79,9 @@ export class DefaultAlertService { const interval = minimumIntervalInMs < defaultIntervalInMs ? '1m' : minimumInterval.value; return interval; } - + setupStatusRule() { const minimumRuleInterval = this.getMinimumRuleInterval(); - if (this.settings?.defaultStatusRuleEnabled === false) { - return; - } return this.createDefaultRuleIfNotExist( SYNTHETICS_STATUS_RULE, `Synthetics status internal rule`, @@ -88,16 +91,12 @@ export class DefaultAlertService { setupTlsRule() { const minimumRuleInterval = this.getMinimumRuleInterval(); - if (this.settings?.defaultTLSRuleEnabled === false) { - return; - } return this.createDefaultRuleIfNotExist( SYNTHETICS_TLS_RULE, `Synthetics internal TLS rule`, minimumRuleInterval ); } - async getExistingAlert(ruleType: DefaultRuleType) { const rulesClient = (await this.context.alerting)?.getRulesClient(); @@ -115,13 +114,7 @@ export class DefaultAlertService { const { actions = [], systemActions = [], ...alert } = data[0]; return { ...alert, actions: [...actions, ...systemActions], ruleTypeId: alert.alertTypeId }; } - async createDefaultRuleIfNotExist(ruleType: DefaultRuleType, name: string, interval: string) { - const alert = await this.getExistingAlert(ruleType); - if (alert) { - return alert; - } - const actions = await this.getAlertActions(ruleType); const rulesClient = (await this.context.alerting)?.getRulesClient(); const { @@ -149,37 +142,22 @@ export class DefaultAlertService { }; } - async updateStatusRule(enabled?: boolean) { + async updateStatusRule() { const minimumRuleInterval = this.getMinimumRuleInterval(); - if (enabled) { - return this.upsertDefaultAlert( - SYNTHETICS_STATUS_RULE, - `Synthetics status internal rule`, - minimumRuleInterval - ); - } else { - const rulesClient = (await this.context.alerting)?.getRulesClient(); - await rulesClient.bulkDeleteRules({ - filter: `alert.attributes.alertTypeId:"${SYNTHETICS_STATUS_RULE}" AND alert.attributes.tags:"SYNTHETICS_DEFAULT_ALERT"`, - }); - } + return this.upsertDefaultAlert( + SYNTHETICS_STATUS_RULE, + `Synthetics status internal rule`, + minimumRuleInterval + ); } - async updateTlsRule(enabled?: boolean) { + async updateTlsRule() { const minimumRuleInterval = this.getMinimumRuleInterval(); - if (enabled) { - return this.upsertDefaultAlert( - SYNTHETICS_TLS_RULE, - `Synthetics internal TLS rule`, - minimumRuleInterval - ); - } else { - const rulesClient = (await this.context.alerting)?.getRulesClient(); - await rulesClient.bulkDeleteRules({ - filter: `alert.attributes.alertTypeId:"${SYNTHETICS_TLS_RULE}" AND alert.attributes.tags:"SYNTHETICS_DEFAULT_ALERT"`, - /// use this to create a get command to find all of the rules we care about andf bring up a relevant callout - }); - } + return this.upsertDefaultAlert( + SYNTHETICS_TLS_RULE, + `Synthetics internal TLS rule`, + minimumRuleInterval + ); } async upsertDefaultAlert(ruleType: DefaultRuleType, name: string, interval: string) { diff --git a/x-pack/plugins/observability_solution/synthetics/server/routes/default_alerts/enable_default_alert.ts b/x-pack/plugins/observability_solution/synthetics/server/routes/default_alerts/enable_default_alert.ts index 4a78ee67ddd1..f997775e5c0b 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/routes/default_alerts/enable_default_alert.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/routes/default_alerts/enable_default_alert.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { schema } from '@kbn/config-schema'; import { DefaultAlertService } from './default_alert_service'; import { SyntheticsRestApiRouteFactory } from '../types'; import { SYNTHETICS_API_URLS } from '../../../common/constants'; @@ -13,10 +14,23 @@ import { DEFAULT_ALERT_RESPONSE } from '../../../common/types/default_alerts'; export const enableDefaultAlertingRoute: SyntheticsRestApiRouteFactory = () => ({ method: 'POST', path: SYNTHETICS_API_URLS.ENABLE_DEFAULT_ALERTING, - validate: {}, - handler: async ({ context, server, savedObjectsClient }): Promise => { + validate: { + request: { + body: schema.object({ + enableTls: schema.boolean(), + enableMonitorStatus: schema.boolean(), + }), + }, + }, + handler: async ({ + context, + request, + server, + savedObjectsClient, + }): Promise => { + const { enableTls, enableMonitorStatus } = request.body; const defaultAlertService = new DefaultAlertService(context, server, savedObjectsClient); - return defaultAlertService.setupDefaultAlerts(); + return defaultAlertService.setupDefaultAlerts({ enableTls, enableMonitorStatus }); }, }); diff --git a/x-pack/plugins/observability_solution/synthetics/server/routes/default_alerts/update_default_alert.ts b/x-pack/plugins/observability_solution/synthetics/server/routes/default_alerts/update_default_alert.ts index 406eaef0aad1..28643bc84129 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/routes/default_alerts/update_default_alert.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/routes/default_alerts/update_default_alert.ts @@ -8,7 +8,6 @@ import { DefaultAlertService } from './default_alert_service'; import { SyntheticsRestApiRouteFactory } from '../types'; import { SYNTHETICS_API_URLS } from '../../../common/constants'; -import { savedObjectsAdapter } from '../../saved_objects'; import { DEFAULT_ALERT_RESPONSE } from '../../../common/types/default_alerts'; export const updateDefaultAlertingRoute: SyntheticsRestApiRouteFactory = () => ({ @@ -22,11 +21,9 @@ export const updateDefaultAlertingRoute: SyntheticsRestApiRouteFactory = () => ( savedObjectsClient, }): Promise => { const defaultAlertService = new DefaultAlertService(context, server, savedObjectsClient); - const { defaultTLSRuleEnabled, defaultStatusRuleEnabled } = - await savedObjectsAdapter.getSyntheticsDynamicSettings(savedObjectsClient); - const updateStatusRulePromise = defaultAlertService.updateStatusRule(defaultStatusRuleEnabled); - const updateTLSRulePromise = defaultAlertService.updateTlsRule(defaultTLSRuleEnabled); + const updateStatusRulePromise = defaultAlertService.updateStatusRule(); + const updateTLSRulePromise = defaultAlertService.updateTlsRule(); try { const [statusRule, tlsRule] = await Promise.all([ diff --git a/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/add_monitor/add_monitor_api.ts b/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/add_monitor/add_monitor_api.ts index f8c7fa9ed9b2..6ce425bd36a8 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/add_monitor/add_monitor_api.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/routes/monitor_cruds/add_monitor/add_monitor_api.ts @@ -34,7 +34,6 @@ import { DEFAULT_NAMESPACE_STRING, } from '../../../../common/constants/monitor_defaults'; import { triggerTestNow } from '../../synthetics_service/test_now_monitor'; -import { DefaultAlertService } from '../../default_alerts/default_alert_service'; import { RouteContext } from '../../types'; import { formatTelemetryEvent, sendTelemetryEvents } from '../../telemetry/monitor_upgrade_sender'; import { formatSecrets } from '../../../synthetics_service/utils'; @@ -253,24 +252,6 @@ export class AddEditMonitorAPI { } } - initDefaultAlerts(name: string) { - const { server, savedObjectsClient, context } = this.routeContext; - try { - // we do this async, so we don't block the user, error handling will be done on the UI via separate api - const defaultAlertService = new DefaultAlertService(context, server, savedObjectsClient); - defaultAlertService - .setupDefaultAlerts() - .then(() => { - server.logger.debug(`Successfully created default alert for monitor: ${name}`); - }) - .catch((error) => { - server.logger.error(`Error creating default alert: ${error} for monitor: ${name}`); - }); - } catch (e) { - server.logger.error(`Error creating default alert: ${e} for monitor: ${name}`); - } - } - setupGettingStarted = (configId: string) => { const { server, request } = this.routeContext; diff --git a/x-pack/plugins/observability_solution/synthetics/server/routes/settings/dynamic_settings.ts b/x-pack/plugins/observability_solution/synthetics/server/routes/settings/dynamic_settings.ts index e9b9bb6da931..95259b9f608f 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/routes/settings/dynamic_settings.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/routes/settings/dynamic_settings.ts @@ -54,8 +54,6 @@ export const fromSettingsAttribute = ( certAgeThreshold: attr.certAgeThreshold, defaultConnectors: attr.defaultConnectors, defaultEmail: attr.defaultEmail, - defaultStatusRuleEnabled: attr.defaultStatusRuleEnabled ?? true, - defaultTLSRuleEnabled: attr.defaultTLSRuleEnabled ?? true, }; }; diff --git a/x-pack/plugins/observability_solution/synthetics/server/runtime_types/settings.ts b/x-pack/plugins/observability_solution/synthetics/server/runtime_types/settings.ts index def512e2f73a..4aff41008868 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/runtime_types/settings.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/runtime_types/settings.ts @@ -25,8 +25,6 @@ export const DynamicSettingsAttributesCodec = t.intersection([ }), t.partial({ defaultEmail: DefaultEmailCodec, - defaultStatusRuleEnabled: t.boolean, - defaultTLSRuleEnabled: t.boolean, }), ]);