From a78bdcc6402504d0be5d20fbbbfbc755a8ce6a37 Mon Sep 17 00:00:00 2001 From: Zacqary Adam Xeper Date: Tue, 30 Jun 2020 15:04:09 -0500 Subject: [PATCH] [7.x] [Metrics UI] Design updates for alert preview results (#69328) (#70347) Co-authored-by: Elastic Machine Co-authored-by: Elastic Machine --- .../infra/common/alerting/metrics/types.ts | 2 +- .../common/components/alert_preview.tsx | 356 ++++++++++++++++++ .../common/components/get_alert_preview.ts | 36 ++ .../alerting/common/get_alert_preview.ts | 51 --- .../infra/public/alerting/common/index.ts | 3 +- .../inventory/components/expression.tsx | 157 +------- .../components/expression.tsx | 220 +---------- .../components/expression_chart.tsx | 2 +- .../public/alerting/metric_threshold/types.ts | 1 - .../preview_metric_threshold_alert.ts | 11 +- .../lib/alerting/metric_threshold/types.ts | 5 +- .../infra/server/routes/alerting/preview.ts | 11 +- 12 files changed, 432 insertions(+), 423 deletions(-) create mode 100644 x-pack/plugins/infra/public/alerting/common/components/alert_preview.tsx create mode 100644 x-pack/plugins/infra/public/alerting/common/components/get_alert_preview.ts delete mode 100644 x-pack/plugins/infra/public/alerting/common/get_alert_preview.ts diff --git a/x-pack/plugins/infra/common/alerting/metrics/types.ts b/x-pack/plugins/infra/common/alerting/metrics/types.ts index 0c1e5090def91..4862b2a7e6a59 100644 --- a/x-pack/plugins/infra/common/alerting/metrics/types.ts +++ b/x-pack/plugins/infra/common/alerting/metrics/types.ts @@ -83,6 +83,7 @@ export const alertPreviewRequestParamsRT = rt.union([ metricThresholdAlertPreviewRequestParamsRT, inventoryAlertPreviewRequestParamsRT, ]); +export type AlertPreviewRequestParams = rt.TypeOf; export const alertPreviewSuccessResponsePayloadRT = rt.type({ numberOfGroups: rt.number, @@ -90,7 +91,6 @@ export const alertPreviewSuccessResponsePayloadRT = rt.type({ fired: rt.number, noData: rt.number, error: rt.number, - tooManyBuckets: rt.number, }), }); export type AlertPreviewSuccessResponsePayload = rt.TypeOf< diff --git a/x-pack/plugins/infra/public/alerting/common/components/alert_preview.tsx b/x-pack/plugins/infra/public/alerting/common/components/alert_preview.tsx new file mode 100644 index 0000000000000..0e0e23ef73a3a --- /dev/null +++ b/x-pack/plugins/infra/public/alerting/common/components/alert_preview.tsx @@ -0,0 +1,356 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { HttpSetup } from 'kibana/public'; +import { omit } from 'lodash'; +import React, { useCallback, useMemo, useState } from 'react'; +import { + EuiSpacer, + EuiFormRow, + EuiButton, + EuiSelect, + EuiFlexGroup, + EuiFlexItem, + EuiCallOut, + EuiOverlayMask, + EuiModal, + EuiModalHeader, + EuiModalHeaderTitle, + EuiModalBody, + EuiCodeBlock, + EuiLink, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; +import { FORMATTERS } from '../../../../common/formatters'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { ValidationResult } from '../../../../../triggers_actions_ui/public/types'; +import { + AlertPreviewSuccessResponsePayload, + AlertPreviewRequestParams, +} from '../../../../common/alerting/metrics'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getIntervalInSeconds } from '../../../../server/utils/get_interval_in_seconds'; +import { getAlertPreview, PreviewableAlertTypes } from './get_alert_preview'; + +interface Props { + alertInterval: string; + alertType: PreviewableAlertTypes; + fetch: HttpSetup['fetch']; + alertParams: { criteria: any[]; sourceId: string } & Record; + validate: (params: any) => ValidationResult; + showNoDataResults?: boolean; + groupByDisplayName?: string; +} + +export const AlertPreview: React.FC = (props) => { + const { + alertParams, + alertInterval, + fetch, + alertType, + validate, + showNoDataResults, + groupByDisplayName, + } = props; + const [previewLookbackInterval, setPreviewLookbackInterval] = useState('h'); + const [isPreviewLoading, setIsPreviewLoading] = useState(false); + const [previewError, setPreviewError] = useState(false); + const [previewResult, setPreviewResult] = useState< + (AlertPreviewSuccessResponsePayload & Record) | null + >(null); + const [isErrorModalVisible, setIsErrorModalVisible] = useState(false); + const onOpenModal = useCallback(() => setIsErrorModalVisible(true), [setIsErrorModalVisible]); + const onCloseModal = useCallback(() => setIsErrorModalVisible(false), [setIsErrorModalVisible]); + + const onSelectPreviewLookbackInterval = useCallback((e) => { + setPreviewLookbackInterval(e.target.value); + }, []); + + const onClickPreview = useCallback(async () => { + setIsPreviewLoading(true); + setPreviewResult(null); + setPreviewError(false); + try { + const result = await getAlertPreview({ + fetch, + params: { + ...alertParams, + lookback: previewLookbackInterval as 'h' | 'd' | 'w' | 'M', + alertInterval, + } as AlertPreviewRequestParams, + alertType, + }); + setPreviewResult({ ...result, groupByDisplayName, previewLookbackInterval }); + } catch (e) { + setPreviewError(e); + } finally { + setIsPreviewLoading(false); + } + }, [alertParams, alertInterval, fetch, alertType, groupByDisplayName, previewLookbackInterval]); + + const previewIntervalError = useMemo(() => { + const intervalInSeconds = getIntervalInSeconds(alertInterval); + const lookbackInSeconds = getIntervalInSeconds(`1${previewLookbackInterval}`); + if (intervalInSeconds >= lookbackInSeconds) { + return true; + } + return false; + }, [previewLookbackInterval, alertInterval]); + + const isPreviewDisabled = useMemo(() => { + const validationResult = validate({ criteria: alertParams.criteria } as any); + const hasValidationErrors = Object.values(validationResult.errors).some((result) => + Object.values(result).some((arr) => Array.isArray(arr) && arr.length) + ); + return hasValidationErrors || previewIntervalError; + }, [alertParams.criteria, previewIntervalError, validate]); + + return ( + + <> + + + + + + + {i18n.translate('xpack.infra.metrics.alertFlyout.testAlertCondition', { + defaultMessage: 'Test alert condition', + })} + + + + + {previewResult && !previewIntervalError && ( + <> + + + + {previewResult.resultTotals.fired}{' '} + {previewResult.resultTotals.fired === 1 + ? firedTimeLabel + : firedTimesLabel} + + ), + }} + />{' '} + {previewResult.groupByDisplayName ? ( + <> + {' '} + + + {' '} + + ) : null} + e.value === previewResult.previewLookbackInterval + )?.shortText, + }} + /> + + } + > + {showNoDataResults && previewResult.resultTotals.noData ? ( + {previewResult.resultTotals.noData}, + plural: previewResult.resultTotals.noData !== 1 ? 's' : '', + }} + /> + ) : null} + {previewResult.resultTotals.error ? ( + + ) : null} + + + )} + {previewIntervalError && ( + <> + + + } + color="warning" + iconType="help" + > + check every, + }} + /> + + + )} + {previewError && ( + <> + + {previewError.body?.statusCode === 508 ? ( + + } + color="warning" + iconType="help" + > + FOR THE LAST, + }} + /> + + ) : ( + + } + color="danger" + iconType="alert" + > + {previewError.body && ( + view the error, + }} + /> + )} + + )} + {isErrorModalVisible && ( + + + + + + + + + {previewError.body.message} + + + + )} + + )} + + + ); +}; + +const previewOptions = [ + { + value: 'h', + text: i18n.translate('xpack.infra.metrics.alertFlyout.lastHourLabel', { + defaultMessage: 'Last hour', + }), + shortText: i18n.translate('xpack.infra.metrics.alertFlyout.hourLabel', { + defaultMessage: 'hour', + }), + }, + { + value: 'd', + text: i18n.translate('xpack.infra.metrics.alertFlyout.lastDayLabel', { + defaultMessage: 'Last day', + }), + shortText: i18n.translate('xpack.infra.metrics.alertFlyout.dayLabel', { + defaultMessage: 'day', + }), + }, + { + value: 'w', + text: i18n.translate('xpack.infra.metrics.alertFlyout.lastWeekLabel', { + defaultMessage: 'Last week', + }), + shortText: i18n.translate('xpack.infra.metrics.alertFlyout.weekLabel', { + defaultMessage: 'week', + }), + }, + { + value: 'M', + text: i18n.translate('xpack.infra.metrics.alertFlyout.lastMonthLabel', { + defaultMessage: 'Last month', + }), + shortText: i18n.translate('xpack.infra.metrics.alertFlyout.monthLabel', { + defaultMessage: 'month', + }), + }, +]; + +const previewDOMOptions: Array<{ text: string; value: string }> = previewOptions.map((o) => + omit(o, 'shortText') +); + +const firedTimeLabel = i18n.translate('xpack.infra.metrics.alertFlyout.firedTime', { + defaultMessage: 'time', +}); +const firedTimesLabel = i18n.translate('xpack.infra.metrics.alertFlyout.firedTimes', { + defaultMessage: 'times', +}); diff --git a/x-pack/plugins/infra/public/alerting/common/components/get_alert_preview.ts b/x-pack/plugins/infra/public/alerting/common/components/get_alert_preview.ts new file mode 100644 index 0000000000000..207d8a722a8c6 --- /dev/null +++ b/x-pack/plugins/infra/public/alerting/common/components/get_alert_preview.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { HttpSetup } from 'src/core/public'; +import { + INFRA_ALERT_PREVIEW_PATH, + METRIC_THRESHOLD_ALERT_TYPE_ID, + METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID, + AlertPreviewRequestParams, + AlertPreviewSuccessResponsePayload, +} from '../../../../common/alerting/metrics'; + +export type PreviewableAlertTypes = + | typeof METRIC_THRESHOLD_ALERT_TYPE_ID + | typeof METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID; + +export async function getAlertPreview({ + fetch, + params, + alertType, +}: { + fetch: HttpSetup['fetch']; + params: AlertPreviewRequestParams; + alertType: PreviewableAlertTypes; +}): Promise { + return await fetch(`${INFRA_ALERT_PREVIEW_PATH}`, { + method: 'POST', + body: JSON.stringify({ + ...params, + alertType, + }), + }); +} diff --git a/x-pack/plugins/infra/public/alerting/common/get_alert_preview.ts b/x-pack/plugins/infra/public/alerting/common/get_alert_preview.ts deleted file mode 100644 index 0db1cd57e093f..0000000000000 --- a/x-pack/plugins/infra/public/alerting/common/get_alert_preview.ts +++ /dev/null @@ -1,51 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import * as rt from 'io-ts'; -import { HttpSetup } from 'src/core/public'; -import { - INFRA_ALERT_PREVIEW_PATH, - METRIC_THRESHOLD_ALERT_TYPE_ID, - METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID, - alertPreviewRequestParamsRT, - alertPreviewSuccessResponsePayloadRT, -} from '../../../common/alerting/metrics'; - -async function getAlertPreview({ - fetch, - params, - alertType, -}: { - fetch: HttpSetup['fetch']; - params: rt.TypeOf; - alertType: - | typeof METRIC_THRESHOLD_ALERT_TYPE_ID - | typeof METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID; -}): Promise> { - return await fetch(`${INFRA_ALERT_PREVIEW_PATH}`, { - method: 'POST', - body: JSON.stringify({ - ...params, - alertType, - }), - }); -} - -export const getMetricThresholdAlertPreview = ({ - fetch, - params, -}: { - fetch: HttpSetup['fetch']; - params: rt.TypeOf; -}) => getAlertPreview({ fetch, params, alertType: METRIC_THRESHOLD_ALERT_TYPE_ID }); - -export const getInventoryAlertPreview = ({ - fetch, - params, -}: { - fetch: HttpSetup['fetch']; - params: rt.TypeOf; -}) => getAlertPreview({ fetch, params, alertType: METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID }); diff --git a/x-pack/plugins/infra/public/alerting/common/index.ts b/x-pack/plugins/infra/public/alerting/common/index.ts index 33f9c856e7166..e1b4a70cfb1fc 100644 --- a/x-pack/plugins/infra/public/alerting/common/index.ts +++ b/x-pack/plugins/infra/public/alerting/common/index.ts @@ -5,8 +5,7 @@ */ import { i18n } from '@kbn/i18n'; - -export * from './get_alert_preview'; +export { AlertPreview } from './components/alert_preview'; export const previewOptions = [ { diff --git a/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx b/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx index ef73d6ff96e41..7df52ad56aef6 100644 --- a/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx +++ b/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx @@ -16,20 +16,11 @@ import { EuiFormRow, EuiButtonEmpty, EuiFieldSearch, - EuiSelect, - EuiButton, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { - previewOptions, - firedTimeLabel, - firedTimesLabel, - getInventoryAlertPreview as getAlertPreview, -} from '../../../alerting/common'; -import { AlertPreviewSuccessResponsePayload } from '../../../../common/alerting/metrics/types'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { getIntervalInSeconds } from '../../../../server/utils/get_interval_in_seconds'; +import { AlertPreview } from '../../common'; +import { METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID } from '../../../../common/alerting/metrics'; import { Comparator, // eslint-disable-next-line @kbn/eslint/no-restricted-paths @@ -108,31 +99,6 @@ export const Expressions: React.FC = (props) => { const [timeSize, setTimeSize] = useState(1); const [timeUnit, setTimeUnit] = useState('m'); - const [previewLookbackInterval, setPreviewLookbackInterval] = useState('h'); - const [isPreviewLoading, setIsPreviewLoading] = useState(false); - const [previewError, setPreviewError] = useState(false); - const [previewResult, setPreviewResult] = useState( - null - ); - - const previewIntervalError = useMemo(() => { - const intervalInSeconds = getIntervalInSeconds(alertInterval); - const lookbackInSeconds = getIntervalInSeconds(`1${previewLookbackInterval}`); - if (intervalInSeconds >= lookbackInSeconds) { - return true; - } - return false; - }, [previewLookbackInterval, alertInterval]); - - const isPreviewDisabled = useMemo(() => { - if (previewIntervalError) return true; - const validationResult = validateMetricThreshold({ criteria: alertParams.criteria } as any); - const hasValidationErrors = Object.values(validationResult.errors).some((result) => - Object.values(result).some((arr) => Array.isArray(arr) && arr.length) - ); - return hasValidationErrors; - }, [alertParams.criteria, previewIntervalError]); - const derivedIndexPattern = useMemo(() => createDerivedIndexPattern('metrics'), [ createDerivedIndexPattern, ]); @@ -253,33 +219,6 @@ export const Expressions: React.FC = (props) => { } }, [alertsContext.metadata, derivedIndexPattern, setAlertParams]); - const onSelectPreviewLookbackInterval = useCallback((e) => { - setPreviewLookbackInterval(e.target.value); - setPreviewResult(null); - }, []); - - const onClickPreview = useCallback(async () => { - setIsPreviewLoading(true); - setPreviewResult(null); - setPreviewError(false); - try { - const result = await getAlertPreview({ - fetch: alertsContext.http.fetch, - params: { - ...pick(alertParams, 'criteria', 'nodeType'), - sourceId: alertParams.sourceId, - lookback: previewLookbackInterval as Unit, - alertInterval, - }, - }); - setPreviewResult(result); - } catch (e) { - setPreviewError(true); - } finally { - setIsPreviewLoading(false); - } - }, [alertParams, alertInterval, alertsContext, previewLookbackInterval]); - useEffect(() => { const md = alertsContext.metadata; if (!alertParams.nodeType) { @@ -396,90 +335,14 @@ export const Expressions: React.FC = (props) => { - - <> - - - - - - - {i18n.translate('xpack.infra.metrics.alertFlyout.testAlertTrigger', { - defaultMessage: 'Test alert trigger', - })} - - - - - {previewResult && ( - <> - - - {previewResult.resultTotals.fired}, - lookback: previewOptions.find((e) => e.value === previewLookbackInterval) - ?.shortText, - }} - />{' '} - {previewResult.numberOfGroups}, - groupName: alertParams.nodeType, - plural: previewResult.numberOfGroups !== 1 ? 's' : '', - }} - /> - - - )} - {previewIntervalError && ( - <> - - - check every, - }} - /> - - - )} - {previewError && ( - <> - - - - - - )} - - + ); diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx index f45474f284484..c5bfbf501bb73 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx @@ -4,38 +4,27 @@ * you may not use this file except in compliance with the Elastic License. */ -import { debounce, pick, omit } from 'lodash'; +import { debounce, pick } from 'lodash'; import { Unit } from '@elastic/datemath'; -import * as rt from 'io-ts'; import React, { ChangeEvent, useCallback, useMemo, useEffect, useState } from 'react'; import { EuiSpacer, EuiText, EuiFormRow, - EuiButton, EuiButtonEmpty, EuiCheckbox, EuiToolTip, EuiIcon, EuiFieldSearch, - EuiSelect, - EuiFlexGroup, - EuiFlexItem, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { - previewOptions, - firedTimeLabel, - firedTimesLabel, - getMetricThresholdAlertPreview as getAlertPreview, -} from '../../common'; +import { AlertPreview } from '../../common'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { getIntervalInSeconds } from '../../../../server/utils/get_interval_in_seconds'; import { Comparator, Aggregators, - alertPreviewSuccessResponsePayloadRT, + METRIC_THRESHOLD_ALERT_TYPE_ID, } from '../../../../common/alerting/metrics'; import { ForLastExpression, @@ -52,7 +41,7 @@ import { useSourceViaHttp } from '../../../containers/source/use_source_via_http import { convertKueryToElasticSearchQuery } from '../../../utils/kuery'; import { ExpressionRow } from './expression_row'; -import { AlertContextMeta, TimeUnit, MetricExpression, AlertParams } from '../types'; +import { AlertContextMeta, MetricExpression, AlertParams } from '../types'; import { ExpressionChart } from './expression_chart'; import { validateMetricThreshold } from './validation'; @@ -85,15 +74,8 @@ export const Expressions: React.FC = (props) => { toastWarning: alertsContext.toastNotifications.addWarning, }); - const [previewLookbackInterval, setPreviewLookbackInterval] = useState('h'); - const [isPreviewLoading, setIsPreviewLoading] = useState(false); - const [previewError, setPreviewError] = useState(false); - const [previewResult, setPreviewResult] = useState | null>(null); - const [timeSize, setTimeSize] = useState(1); - const [timeUnit, setTimeUnit] = useState('m'); + const [timeUnit, setTimeUnit] = useState('m'); const derivedIndexPattern = useMemo(() => createDerivedIndexPattern('metrics'), [ createDerivedIndexPattern, ]); @@ -194,7 +176,7 @@ export const Expressions: React.FC = (props) => { ...c, timeUnit: tu, })) || []; - setTimeUnit(tu as TimeUnit); + setTimeUnit(tu as Unit); setAlertParams('criteria', criteria); }, /* eslint-disable-next-line react-hooks/exhaustive-deps */ @@ -248,33 +230,6 @@ export const Expressions: React.FC = (props) => { } }, [alertsContext.metadata, setAlertParams]); - const onSelectPreviewLookbackInterval = useCallback((e) => { - setPreviewLookbackInterval(e.target.value); - setPreviewResult(null); - }, []); - - const onClickPreview = useCallback(async () => { - setIsPreviewLoading(true); - setPreviewResult(null); - setPreviewError(false); - try { - const result = await getAlertPreview({ - fetch: alertsContext.http.fetch, - params: { - ...pick(alertParams, 'criteria', 'groupBy', 'filterQuery'), - sourceId: alertParams.sourceId, - lookback: previewLookbackInterval as Unit, - alertInterval, - }, - }); - setPreviewResult(result); - } catch (e) { - setPreviewError(true); - } finally { - setIsPreviewLoading(false); - } - }, [alertParams, alertInterval, alertsContext, previewLookbackInterval]); - useEffect(() => { if (alertParams.criteria && alertParams.criteria.length) { setTimeSize(alertParams.criteria[0].timeSize); @@ -301,24 +256,6 @@ export const Expressions: React.FC = (props) => { [onFilterChange] ); - const previewIntervalError = useMemo(() => { - const intervalInSeconds = getIntervalInSeconds(alertInterval); - const lookbackInSeconds = getIntervalInSeconds(`1${previewLookbackInterval}`); - if (intervalInSeconds >= lookbackInSeconds) { - return true; - } - return false; - }, [previewLookbackInterval, alertInterval]); - - const isPreviewDisabled = useMemo(() => { - if (previewIntervalError) return true; - const validationResult = validateMetricThreshold({ criteria: alertParams.criteria } as any); - const hasValidationErrors = Object.values(validationResult.errors).some((result) => - Object.values(result).some((arr) => Array.isArray(arr) && arr.length) - ); - return hasValidationErrors; - }, [alertParams.criteria, previewIntervalError]); - return ( <> @@ -456,147 +393,20 @@ export const Expressions: React.FC = (props) => { - - <> - - - - - - - {i18n.translate('xpack.infra.metrics.alertFlyout.testAlertTrigger', { - defaultMessage: 'Test alert trigger', - })} - - - - - {previewResult && !previewIntervalError && !previewResult.resultTotals.tooManyBuckets && ( - <> - - - {previewResult.resultTotals.fired}, - lookback: previewOptions.find((e) => e.value === previewLookbackInterval) - ?.shortText, - }} - />{' '} - {alertParams.groupBy ? ( - {previewResult.numberOfGroups}, - groupName: alertParams.groupBy, - plural: previewResult.numberOfGroups !== 1 ? 's' : '', - }} - /> - ) : ( - - )} - - {alertParams.alertOnNoData && previewResult.resultTotals.noData ? ( - <> - - - {previewResult.resultTotals.noData}, - plural: previewResult.resultTotals.noData !== 1 ? 's' : '', - }} - /> - - - ) : null} - {previewResult.resultTotals.error ? ( - <> - - - - - - ) : null} - - )} - {previewResult && previewResult.resultTotals.tooManyBuckets ? ( - <> - - - FOR THE LAST, - }} - /> - - - ) : null} - {previewIntervalError && ( - <> - - - check every, - }} - /> - - - )} - {previewError && ( - <> - - - - - - )} - - + ); }; -const previewDOMOptions: Array<{ text: string; value: string }> = previewOptions.map((o) => - omit(o, 'shortText') -); - // required for dynamic import // eslint-disable-next-line import/no-default-export export default Expressions; diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.tsx index 64a9d27961499..2040fe01a9411 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.tsx @@ -151,7 +151,7 @@ export const ExpressionChart: React.FC = ({ const isBelow = [Comparator.LT, Comparator.LT_OR_EQ].includes(expression.comparator); const opacity = 0.3; const { timeSize, timeUnit } = expression; - const timeLabel = TIME_LABELS[timeUnit]; + const timeLabel = TIME_LABELS[timeUnit as keyof typeof TIME_LABELS]; return ( <> diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/types.ts b/x-pack/plugins/infra/public/alerting/metric_threshold/types.ts index 2f8d7ec0ba6f4..58586c1dd8b98 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/types.ts +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/types.ts @@ -16,7 +16,6 @@ export interface AlertContextMeta { series?: MetricsExplorerSeries; } -export type TimeUnit = 's' | 'm' | 'h' | 'd'; export type MetricExpression = Omit & { metric?: string; }; diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/preview_metric_threshold_alert.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/preview_metric_threshold_alert.ts index e58667bfdf3e1..39db24684e8d3 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/preview_metric_threshold_alert.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/preview_metric_threshold_alert.ts @@ -36,7 +36,7 @@ export const previewMetricThresholdAlert: ( params: PreviewMetricThresholdAlertParams, iterations?: number, precalculatedNumberOfGroups?: number -) => Promise> = async ( +) => Promise> = async ( { callCluster, params, @@ -77,13 +77,6 @@ export const previewMetricThresholdAlert: ( const alertResultsPerExecution = alertIntervalInSeconds / bucketIntervalInSeconds; const previewResults = await Promise.all( groups.map(async (group) => { - const tooManyBuckets = alertResults.some((alertResult) => - isTooManyBucketsPreviewException(alertResult[group]) - ); - if (tooManyBuckets) { - return TOO_MANY_BUCKETS_PREVIEW_EXCEPTION; - } - const isNoData = alertResults.some((alertResult) => alertResult[group].isNoData); if (isNoData) { return null; @@ -136,7 +129,7 @@ export const previewMetricThresholdAlert: ( // Bail out if it looks like this is going to take too long if (slicedLookback <= 0 || iterations > MAX_ITERATIONS || slices > MAX_ITERATIONS) { - return [TOO_MANY_BUCKETS_PREVIEW_EXCEPTION]; + throw new Error(`${TOO_MANY_BUCKETS_PREVIEW_EXCEPTION}:${maxBuckets * MAX_ITERATIONS}`); } const slicedRequests = [...Array(slices)].map((_, i) => { diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/types.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/types.ts index 76ddd107bd728..48391925d9910 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/types.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/types.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Unit } from '@elastic/datemath'; export const METRIC_THRESHOLD_ALERT_TYPE_ID = 'metrics.alert.threshold'; export enum Comparator { @@ -34,11 +35,9 @@ export enum AlertStates { ERROR, } -export type TimeUnit = 's' | 'm' | 'h' | 'd'; - interface BaseMetricExpressionParams { timeSize: number; - timeUnit: TimeUnit; + timeUnit: Unit; sourceId?: string; threshold: number[]; comparator: Comparator; diff --git a/x-pack/plugins/infra/server/routes/alerting/preview.ts b/x-pack/plugins/infra/server/routes/alerting/preview.ts index d11425a4f4cb0..8a3e9e4d0bedc 100644 --- a/x-pack/plugins/infra/server/routes/alerting/preview.ts +++ b/x-pack/plugins/infra/server/routes/alerting/preview.ts @@ -56,8 +56,6 @@ export const initAlertPreviewRoute = ({ framework, sources }: InfraBackendLibs) const numberOfGroups = previewResult.length; const resultTotals = previewResult.reduce( (totals, groupResult) => { - if (groupResult === TOO_MANY_BUCKETS_PREVIEW_EXCEPTION) - return { ...totals, tooManyBuckets: totals.tooManyBuckets + 1 }; if (groupResult === null) return { ...totals, noData: totals.noData + 1 }; if (isNaN(groupResult)) return { ...totals, error: totals.error + 1 }; return { ...totals, fired: totals.fired + groupResult }; @@ -66,7 +64,6 @@ export const initAlertPreviewRoute = ({ framework, sources }: InfraBackendLibs) fired: 0, noData: 0, error: 0, - tooManyBuckets: 0, } ); @@ -112,6 +109,14 @@ export const initAlertPreviewRoute = ({ framework, sources }: InfraBackendLibs) throw new Error('Unknown alert type'); } } catch (error) { + if (error.message.includes(TOO_MANY_BUCKETS_PREVIEW_EXCEPTION)) { + return response.customError({ + statusCode: 508, + body: { + message: error.message.split(':')[1], // Extract the max buckets from the error message + }, + }); + } return response.customError({ statusCode: error.statusCode ?? 500, body: {