mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Metrics UI] Add inventory alert preview (#68909)
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
e3d01bf450
commit
6a016d0b57
20 changed files with 563 additions and 208 deletions
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import * as rt from 'io-ts';
|
||||
import { ItemTypeRT } from '../../inventory_models/types';
|
||||
|
||||
// TODO: Have threshold and inventory alerts import these types from this file instead of from their
|
||||
// local directories
|
||||
|
@ -39,7 +40,16 @@ const baseAlertRequestParamsRT = rt.intersection([
|
|||
sourceId: rt.string,
|
||||
}),
|
||||
rt.type({
|
||||
lookback: rt.union([rt.literal('h'), rt.literal('d'), rt.literal('w'), rt.literal('M')]),
|
||||
lookback: rt.union([
|
||||
rt.literal('ms'),
|
||||
rt.literal('s'),
|
||||
rt.literal('m'),
|
||||
rt.literal('h'),
|
||||
rt.literal('d'),
|
||||
rt.literal('w'),
|
||||
rt.literal('M'),
|
||||
rt.literal('y'),
|
||||
]),
|
||||
criteria: rt.array(rt.any),
|
||||
alertInterval: rt.string,
|
||||
}),
|
||||
|
@ -61,10 +71,13 @@ export type MetricThresholdAlertPreviewRequestParams = rt.TypeOf<
|
|||
const inventoryAlertPreviewRequestParamsRT = rt.intersection([
|
||||
baseAlertRequestParamsRT,
|
||||
rt.type({
|
||||
nodeType: rt.string,
|
||||
nodeType: ItemTypeRT,
|
||||
alertType: rt.literal(METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID),
|
||||
}),
|
||||
]);
|
||||
export type InventoryAlertPreviewRequestParams = rt.TypeOf<
|
||||
typeof inventoryAlertPreviewRequestParamsRT
|
||||
>;
|
||||
|
||||
export const alertPreviewRequestParamsRT = rt.union([
|
||||
metricThresholdAlertPreviewRequestParamsRT,
|
||||
|
@ -80,3 +93,6 @@ export const alertPreviewSuccessResponsePayloadRT = rt.type({
|
|||
tooManyBuckets: rt.number,
|
||||
}),
|
||||
});
|
||||
export type AlertPreviewSuccessResponsePayload = rt.TypeOf<
|
||||
typeof alertPreviewSuccessResponsePayloadRT
|
||||
>;
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* 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<typeof alertPreviewRequestParamsRT>;
|
||||
alertType:
|
||||
| typeof METRIC_THRESHOLD_ALERT_TYPE_ID
|
||||
| typeof METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID;
|
||||
}): Promise<rt.TypeOf<typeof alertPreviewSuccessResponsePayloadRT>> {
|
||||
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<typeof alertPreviewRequestParamsRT>;
|
||||
}) => getAlertPreview({ fetch, params, alertType: METRIC_THRESHOLD_ALERT_TYPE_ID });
|
||||
|
||||
export const getInventoryAlertPreview = ({
|
||||
fetch,
|
||||
params,
|
||||
}: {
|
||||
fetch: HttpSetup['fetch'];
|
||||
params: rt.TypeOf<typeof alertPreviewRequestParamsRT>;
|
||||
}) => getAlertPreview({ fetch, params, alertType: METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID });
|
55
x-pack/plugins/infra/public/alerting/common/index.ts
Normal file
55
x-pack/plugins/infra/public/alerting/common/index.ts
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
|
||||
export * from './get_alert_preview';
|
||||
|
||||
export 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',
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
export const firedTimeLabel = i18n.translate('xpack.infra.metrics.alertFlyout.firedTime', {
|
||||
defaultMessage: 'time',
|
||||
});
|
||||
export const firedTimesLabel = i18n.translate('xpack.infra.metrics.alertFlyout.firedTimes', {
|
||||
defaultMessage: 'times',
|
||||
});
|
|
@ -4,7 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { debounce } from 'lodash';
|
||||
import { debounce, pick } from 'lodash';
|
||||
import { Unit } from '@elastic/datemath';
|
||||
import React, { useCallback, useMemo, useEffect, useState, ChangeEvent } from 'react';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
|
@ -15,9 +16,20 @@ 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 {
|
||||
Comparator,
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
|
@ -52,6 +64,8 @@ import { NodeTypeExpression } from './node_type';
|
|||
import { InfraWaffleMapOptions } from '../../../lib/lib';
|
||||
import { convertKueryToElasticSearchQuery } from '../../../utils/kuery';
|
||||
|
||||
import { validateMetricThreshold } from './validation';
|
||||
|
||||
const FILTER_TYPING_DEBOUNCE_MS = 500;
|
||||
|
||||
interface AlertContextMeta {
|
||||
|
@ -65,18 +79,16 @@ interface Props {
|
|||
alertParams: {
|
||||
criteria: InventoryMetricConditions[];
|
||||
nodeType: InventoryItemType;
|
||||
groupBy?: string;
|
||||
filterQuery?: string;
|
||||
filterQueryText?: string;
|
||||
sourceId?: string;
|
||||
};
|
||||
alertInterval: string;
|
||||
alertsContext: AlertsContextValue<AlertContextMeta>;
|
||||
setAlertParams(key: string, value: any): void;
|
||||
setAlertProperty(key: string, value: any): void;
|
||||
}
|
||||
|
||||
type TimeUnit = 's' | 'm' | 'h' | 'd';
|
||||
|
||||
const defaultExpression = {
|
||||
metric: 'cpu' as SnapshotMetricType,
|
||||
comparator: Comparator.GT,
|
||||
|
@ -86,7 +98,7 @@ const defaultExpression = {
|
|||
} as InventoryMetricConditions;
|
||||
|
||||
export const Expressions: React.FC<Props> = (props) => {
|
||||
const { setAlertParams, alertParams, errors, alertsContext } = props;
|
||||
const { setAlertParams, alertParams, errors, alertsContext, alertInterval } = props;
|
||||
const { source, createDerivedIndexPattern } = useSourceViaHttp({
|
||||
sourceId: 'default',
|
||||
type: 'metrics',
|
||||
|
@ -94,7 +106,32 @@ export const Expressions: React.FC<Props> = (props) => {
|
|||
toastWarning: alertsContext.toastNotifications.addWarning,
|
||||
});
|
||||
const [timeSize, setTimeSize] = useState<number | undefined>(1);
|
||||
const [timeUnit, setTimeUnit] = useState<TimeUnit>('m');
|
||||
const [timeUnit, setTimeUnit] = useState<Unit>('m');
|
||||
|
||||
const [previewLookbackInterval, setPreviewLookbackInterval] = useState<string>('h');
|
||||
const [isPreviewLoading, setIsPreviewLoading] = useState<boolean>(false);
|
||||
const [previewError, setPreviewError] = useState<boolean>(false);
|
||||
const [previewResult, setPreviewResult] = useState<AlertPreviewSuccessResponsePayload | null>(
|
||||
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,
|
||||
|
@ -173,7 +210,7 @@ export const Expressions: React.FC<Props> = (props) => {
|
|||
...c,
|
||||
timeUnit: tu,
|
||||
}));
|
||||
setTimeUnit(tu as TimeUnit);
|
||||
setTimeUnit(tu as Unit);
|
||||
setAlertParams('criteria', criteria);
|
||||
},
|
||||
[alertParams.criteria, setAlertParams]
|
||||
|
@ -216,6 +253,33 @@ export const Expressions: React.FC<Props> = (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) {
|
||||
|
@ -332,6 +396,91 @@ export const Expressions: React.FC<Props> = (props) => {
|
|||
</EuiFormRow>
|
||||
|
||||
<EuiSpacer size={'m'} />
|
||||
<EuiFormRow
|
||||
label={i18n.translate('xpack.infra.metrics.alertFlyout.previewLabel', {
|
||||
defaultMessage: 'Preview',
|
||||
})}
|
||||
fullWidth
|
||||
compressed
|
||||
>
|
||||
<>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiSelect
|
||||
id="selectPreviewLookbackInterval"
|
||||
value={previewLookbackInterval}
|
||||
onChange={onSelectPreviewLookbackInterval}
|
||||
options={previewOptions}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
isLoading={isPreviewLoading}
|
||||
isDisabled={isPreviewDisabled}
|
||||
onClick={onClickPreview}
|
||||
>
|
||||
{i18n.translate('xpack.infra.metrics.alertFlyout.testAlertTrigger', {
|
||||
defaultMessage: 'Test alert trigger',
|
||||
})}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiSpacer size={'s'} />
|
||||
</EuiFlexGroup>
|
||||
{previewResult && (
|
||||
<>
|
||||
<EuiSpacer size={'s'} />
|
||||
<EuiText>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.metrics.alertFlyout.alertPreviewResult"
|
||||
defaultMessage="This alert would have fired {fired} {timeOrTimes} in the past {lookback}"
|
||||
values={{
|
||||
timeOrTimes:
|
||||
previewResult.resultTotals.fired === 1 ? firedTimeLabel : firedTimesLabel,
|
||||
fired: <strong>{previewResult.resultTotals.fired}</strong>,
|
||||
lookback: previewOptions.find((e) => e.value === previewLookbackInterval)
|
||||
?.shortText,
|
||||
}}
|
||||
/>{' '}
|
||||
<FormattedMessage
|
||||
id="xpack.infra.metrics.alertFlyout.alertPreviewGroups"
|
||||
defaultMessage="across {numberOfGroups} {groupName}{plural}."
|
||||
values={{
|
||||
numberOfGroups: <strong>{previewResult.numberOfGroups}</strong>,
|
||||
groupName: alertParams.nodeType,
|
||||
plural: previewResult.numberOfGroups !== 1 ? 's' : '',
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
</>
|
||||
)}
|
||||
{previewIntervalError && (
|
||||
<>
|
||||
<EuiSpacer size={'s'} />
|
||||
<EuiText>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.metrics.alertFlyout.previewIntervalTooShort"
|
||||
defaultMessage="Not enough data to preview. Please select a longer preview length, or increase the amount of time in the {checkEvery} field."
|
||||
values={{
|
||||
checkEvery: <strong>check every</strong>,
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
</>
|
||||
)}
|
||||
{previewError && (
|
||||
<>
|
||||
<EuiSpacer size={'s'} />
|
||||
<EuiText>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.metrics.alertFlyout.alertPreviewError"
|
||||
defaultMessage="An error occurred when trying to preview this alert trigger."
|
||||
/>
|
||||
</EuiText>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
</EuiFormRow>
|
||||
<EuiSpacer size={'m'} />
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -6,19 +6,20 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { AlertTypeModel } from '../../../../../triggers_actions_ui/public/types';
|
||||
import { validateMetricThreshold } from './validation';
|
||||
import { METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID } from '../../../server/lib/alerting/inventory_metric_threshold/types';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { AlertTypeModel } from '../../../../triggers_actions_ui/public/types';
|
||||
import { validateMetricThreshold } from './components/validation';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID } from '../../../../server/lib/alerting/inventory_metric_threshold/types';
|
||||
|
||||
export function getInventoryMetricAlertType(): AlertTypeModel {
|
||||
export function createInventoryMetricAlertType(): AlertTypeModel {
|
||||
return {
|
||||
id: METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID,
|
||||
name: i18n.translate('xpack.infra.metrics.inventory.alertFlyout.alertName', {
|
||||
defaultMessage: 'Inventory',
|
||||
}),
|
||||
iconClass: 'bell',
|
||||
alertParamsExpression: React.lazy(() => import('./expression')),
|
||||
alertParamsExpression: React.lazy(() => import('./components/expression')),
|
||||
validate: validateMetricThreshold,
|
||||
defaultActionMessage: i18n.translate(
|
||||
'xpack.infra.metrics.alerting.inventory.threshold.defaultActionMessage',
|
|
@ -5,8 +5,8 @@
|
|||
*/
|
||||
|
||||
import { debounce, pick } from 'lodash';
|
||||
import { Unit } from '@elastic/datemath';
|
||||
import * as rt from 'io-ts';
|
||||
import { HttpSetup } from 'src/core/public';
|
||||
import React, { ChangeEvent, useCallback, useMemo, useEffect, useState } from 'react';
|
||||
import {
|
||||
EuiSpacer,
|
||||
|
@ -24,15 +24,18 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
previewOptions,
|
||||
firedTimeLabel,
|
||||
firedTimesLabel,
|
||||
getMetricThresholdAlertPreview as getAlertPreview,
|
||||
} from '../../common';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { getIntervalInSeconds } from '../../../../server/utils/get_interval_in_seconds';
|
||||
import {
|
||||
Comparator,
|
||||
Aggregators,
|
||||
INFRA_ALERT_PREVIEW_PATH,
|
||||
alertPreviewRequestParamsRT,
|
||||
alertPreviewSuccessResponsePayloadRT,
|
||||
METRIC_THRESHOLD_ALERT_TYPE_ID,
|
||||
} from '../../../../common/alerting/metrics';
|
||||
import {
|
||||
ForLastExpression,
|
||||
|
@ -79,22 +82,6 @@ const defaultExpression = {
|
|||
timeUnit: 'm',
|
||||
} as MetricExpression;
|
||||
|
||||
async function getAlertPreview({
|
||||
fetch,
|
||||
params,
|
||||
}: {
|
||||
fetch: HttpSetup['fetch'];
|
||||
params: rt.TypeOf<typeof alertPreviewRequestParamsRT>;
|
||||
}): Promise<rt.TypeOf<typeof alertPreviewSuccessResponsePayloadRT>> {
|
||||
return await fetch(`${INFRA_ALERT_PREVIEW_PATH}`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
...params,
|
||||
alertType: METRIC_THRESHOLD_ALERT_TYPE_ID,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
export const Expressions: React.FC<Props> = (props) => {
|
||||
const { setAlertParams, alertParams, errors, alertsContext, alertInterval } = props;
|
||||
const { source, createDerivedIndexPattern } = useSourceViaHttp({
|
||||
|
@ -275,7 +262,7 @@ export const Expressions: React.FC<Props> = (props) => {
|
|||
params: {
|
||||
...pick(alertParams, 'criteria', 'groupBy', 'filterQuery'),
|
||||
sourceId: alertParams.sourceId,
|
||||
lookback: previewLookbackInterval as 'h' | 'd' | 'w' | 'M',
|
||||
lookback: previewLookbackInterval as Unit,
|
||||
alertInterval,
|
||||
},
|
||||
});
|
||||
|
@ -319,11 +306,12 @@ export const Expressions: React.FC<Props> = (props) => {
|
|||
}, [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 || previewIntervalError;
|
||||
return hasValidationErrors;
|
||||
}, [alertParams.criteria, previewIntervalError]);
|
||||
|
||||
return (
|
||||
|
@ -600,52 +588,6 @@ export const Expressions: React.FC<Props> = (props) => {
|
|||
);
|
||||
};
|
||||
|
||||
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 firedTimeLabel = i18n.translate('xpack.infra.metrics.alertFlyout.firedTime', {
|
||||
defaultMessage: 'time',
|
||||
});
|
||||
const firedTimesLabel = i18n.translate('xpack.infra.metrics.alertFlyout.firedTimes', {
|
||||
defaultMessage: 'times',
|
||||
});
|
||||
|
||||
// required for dynamic import
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default Expressions;
|
||||
|
|
|
@ -29,7 +29,7 @@ import { WaffleOptionsProvider } from './inventory_view/hooks/use_waffle_options
|
|||
import { WaffleTimeProvider } from './inventory_view/hooks/use_waffle_time';
|
||||
import { WaffleFiltersProvider } from './inventory_view/hooks/use_waffle_filters';
|
||||
|
||||
import { InventoryAlertDropdown } from '../../components/alerting/inventory/alert_dropdown';
|
||||
import { InventoryAlertDropdown } from '../../alerting/inventory/components/alert_dropdown';
|
||||
import { MetricsAlertDropdown } from '../../alerting/metric_threshold/components/alert_dropdown';
|
||||
|
||||
const ADD_DATA_LABEL = i18n.translate('xpack.infra.metricsHeaderAddDataButtonLabel', {
|
||||
|
|
|
@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n';
|
|||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { AlertFlyout } from '../../../../../components/alerting/inventory/alert_flyout';
|
||||
import { AlertFlyout } from '../../../../../alerting/inventory/components/alert_flyout';
|
||||
import { InfraWaffleMapNode, InfraWaffleMapOptions } from '../../../../../lib/lib';
|
||||
import { getNodeDetailUrl, getNodeLogsUrl } from '../../../../link_to';
|
||||
import { createUptimeLink } from '../../lib/create_uptime_link';
|
||||
|
|
|
@ -13,7 +13,7 @@ import {
|
|||
} from 'kibana/public';
|
||||
import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/public';
|
||||
import { createMetricThresholdAlertType } from './alerting/metric_threshold';
|
||||
import { getInventoryMetricAlertType } from './components/alerting/inventory/metric_inventory_threshold_alert_type';
|
||||
import { createInventoryMetricAlertType } from './alerting/inventory';
|
||||
import { getAlertType as getLogsAlertType } from './components/alerting/logs/log_threshold_alert_type';
|
||||
import { registerStartSingleton } from './legacy_singletons';
|
||||
import { registerFeatures } from './register_feature';
|
||||
|
@ -29,7 +29,7 @@ export class Plugin
|
|||
setup(core: CoreSetup<ClientPluginsStart, ClientStart>, pluginsSetup: ClientPluginsSetup) {
|
||||
registerFeatures(pluginsSetup.home);
|
||||
|
||||
pluginsSetup.triggers_actions_ui.alertTypeRegistry.register(getInventoryMetricAlertType());
|
||||
pluginsSetup.triggers_actions_ui.alertTypeRegistry.register(createInventoryMetricAlertType());
|
||||
pluginsSetup.triggers_actions_ui.alertTypeRegistry.register(getLogsAlertType());
|
||||
pluginsSetup.triggers_actions_ui.alertTypeRegistry.register(createMetricThresholdAlertType());
|
||||
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* 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 { mapValues, last } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import {
|
||||
InfraDatabaseSearchResponse,
|
||||
CallWithRequestParams,
|
||||
} from '../../adapters/framework/adapter_types';
|
||||
import { Comparator, InventoryMetricConditions } from './types';
|
||||
import { AlertServices } from '../../../../../alerts/server';
|
||||
import { InfraSnapshot } from '../../snapshot';
|
||||
import { parseFilterQuery } from '../../../utils/serialized_query';
|
||||
import { InventoryItemType, SnapshotMetricType } from '../../../../common/inventory_models/types';
|
||||
import { InfraTimerangeInput } from '../../../../common/http_api/snapshot_api';
|
||||
import { InfraSourceConfiguration } from '../../sources';
|
||||
|
||||
interface ConditionResult {
|
||||
shouldFire: boolean | boolean[];
|
||||
currentValue?: number | null;
|
||||
metric: string;
|
||||
isNoData: boolean;
|
||||
isError: boolean;
|
||||
}
|
||||
|
||||
export const evaluateCondition = async (
|
||||
condition: InventoryMetricConditions,
|
||||
nodeType: InventoryItemType,
|
||||
sourceConfiguration: InfraSourceConfiguration,
|
||||
callCluster: AlertServices['callCluster'],
|
||||
filterQuery?: string,
|
||||
lookbackSize?: number
|
||||
): Promise<Record<string, ConditionResult>> => {
|
||||
const { comparator, metric } = condition;
|
||||
let { threshold } = condition;
|
||||
|
||||
const timerange = {
|
||||
to: Date.now(),
|
||||
from: moment().subtract(condition.timeSize, condition.timeUnit).toDate().getTime(),
|
||||
interval: condition.timeUnit,
|
||||
} as InfraTimerangeInput;
|
||||
if (lookbackSize) {
|
||||
timerange.lookbackSize = lookbackSize;
|
||||
}
|
||||
|
||||
const currentValues = await getData(
|
||||
callCluster,
|
||||
nodeType,
|
||||
metric,
|
||||
timerange,
|
||||
sourceConfiguration,
|
||||
filterQuery
|
||||
);
|
||||
|
||||
threshold = threshold.map((n) => convertMetricValue(metric, n));
|
||||
|
||||
const comparisonFunction = comparatorMap[comparator];
|
||||
|
||||
return mapValues(currentValues, (value) => ({
|
||||
shouldFire:
|
||||
value !== undefined &&
|
||||
value !== null &&
|
||||
(Array.isArray(value)
|
||||
? value.map((v) => comparisonFunction(Number(v), threshold))
|
||||
: comparisonFunction(value, threshold)),
|
||||
metric,
|
||||
isNoData: value === null,
|
||||
isError: value === undefined,
|
||||
...(!Array.isArray(value) ? { currentValue: value } : {}),
|
||||
}));
|
||||
};
|
||||
|
||||
const getData = async (
|
||||
callCluster: AlertServices['callCluster'],
|
||||
nodeType: InventoryItemType,
|
||||
metric: SnapshotMetricType,
|
||||
timerange: InfraTimerangeInput,
|
||||
sourceConfiguration: InfraSourceConfiguration,
|
||||
filterQuery?: string
|
||||
) => {
|
||||
const snapshot = new InfraSnapshot();
|
||||
const esClient = <Hit = {}, Aggregation = undefined>(
|
||||
options: CallWithRequestParams
|
||||
): Promise<InfraDatabaseSearchResponse<Hit, Aggregation>> => callCluster('search', options);
|
||||
|
||||
const options = {
|
||||
filterQuery: parseFilterQuery(filterQuery),
|
||||
nodeType,
|
||||
groupBy: [],
|
||||
sourceConfiguration,
|
||||
metric: { type: metric },
|
||||
timerange,
|
||||
includeTimeseries: Boolean(timerange.lookbackSize),
|
||||
};
|
||||
|
||||
const { nodes } = await snapshot.getNodes(esClient, options);
|
||||
|
||||
return nodes.reduce((acc, n) => {
|
||||
const nodePathItem = last(n.path);
|
||||
if (n.metric?.value && n.metric?.timeseries) {
|
||||
const { timeseries } = n.metric;
|
||||
const values = timeseries.rows.map((row) => row.metric_0) as Array<number | null>;
|
||||
acc[nodePathItem.label] = values;
|
||||
} else {
|
||||
acc[nodePathItem.label] = n.metric && n.metric.value;
|
||||
}
|
||||
return acc;
|
||||
}, {} as Record<string, number | Array<number | string | null | undefined> | undefined | null>);
|
||||
};
|
||||
|
||||
const comparatorMap = {
|
||||
[Comparator.BETWEEN]: (value: number, [a, b]: number[]) =>
|
||||
value >= Math.min(a, b) && value <= Math.max(a, b),
|
||||
// `threshold` is always an array of numbers in case the BETWEEN comparator is
|
||||
// used; all other compartors will just destructure the first value in the array
|
||||
[Comparator.GT]: (a: number, [b]: number[]) => a > b,
|
||||
[Comparator.LT]: (a: number, [b]: number[]) => a < b,
|
||||
[Comparator.OUTSIDE_RANGE]: (value: number, [a, b]: number[]) => value < a || value > b,
|
||||
[Comparator.GT_OR_EQ]: (a: number, [b]: number[]) => a >= b,
|
||||
[Comparator.LT_OR_EQ]: (a: number, [b]: number[]) => a <= b,
|
||||
};
|
||||
|
||||
// Some metrics in the UI are in a different unit that what we store in ES.
|
||||
const convertMetricValue = (metric: SnapshotMetricType, value: number) => {
|
||||
if (converters[metric]) {
|
||||
return converters[metric](value);
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
};
|
||||
const converters: Record<string, (n: number) => number> = {
|
||||
cpu: (n) => Number(n) / 100,
|
||||
memory: (n) => Number(n) / 100,
|
||||
};
|
|
@ -3,27 +3,18 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { mapValues, last, get } from 'lodash';
|
||||
import { first, get } from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import moment from 'moment';
|
||||
import {
|
||||
InfraDatabaseSearchResponse,
|
||||
CallWithRequestParams,
|
||||
} from '../../adapters/framework/adapter_types';
|
||||
import { Comparator, AlertStates, InventoryMetricConditions } from './types';
|
||||
import { AlertServices, AlertExecutorOptions } from '../../../../../alerts/server';
|
||||
import { InfraSnapshot } from '../../snapshot';
|
||||
import { parseFilterQuery } from '../../../utils/serialized_query';
|
||||
import { AlertStates, InventoryMetricConditions } from './types';
|
||||
import { AlertExecutorOptions } from '../../../../../alerts/server';
|
||||
import { InventoryItemType, SnapshotMetricType } from '../../../../common/inventory_models/types';
|
||||
import { InfraTimerangeInput } from '../../../../common/http_api/snapshot_api';
|
||||
import { InfraSourceConfiguration } from '../../sources';
|
||||
import { InfraBackendLibs } from '../../infra_types';
|
||||
import { METRIC_FORMATTERS } from '../../../../common/formatters/snapshot_metric_formats';
|
||||
import { createFormatter } from '../../../../common/formatters';
|
||||
import { evaluateCondition } from './evaluate_condition';
|
||||
|
||||
interface InventoryMetricThresholdParams {
|
||||
criteria: InventoryMetricConditions[];
|
||||
groupBy: string | undefined;
|
||||
filterQuery: string | undefined;
|
||||
nodeType: InventoryItemType;
|
||||
sourceId?: string;
|
||||
|
@ -41,11 +32,13 @@ export const createInventoryMetricThresholdExecutor = (
|
|||
);
|
||||
|
||||
const results = await Promise.all(
|
||||
criteria.map((c) => evaluateCondtion(c, nodeType, source.configuration, services, filterQuery))
|
||||
criteria.map((c) =>
|
||||
evaluateCondition(c, nodeType, source.configuration, services.callCluster, filterQuery)
|
||||
)
|
||||
);
|
||||
|
||||
const invenotryItems = Object.keys(results[0]);
|
||||
for (const item of invenotryItems) {
|
||||
const inventoryItems = Object.keys(first(results));
|
||||
for (const item of inventoryItems) {
|
||||
const alertInstance = services.alertInstanceFactory(`${alertId}-${item}`);
|
||||
// AND logic; all criteria must be across the threshold
|
||||
const shouldAlertFire = results.every((result) => result[item].shouldFire);
|
||||
|
@ -79,93 +72,6 @@ export const createInventoryMetricThresholdExecutor = (
|
|||
}
|
||||
};
|
||||
|
||||
interface ConditionResult {
|
||||
shouldFire: boolean;
|
||||
currentValue?: number | null;
|
||||
isNoData: boolean;
|
||||
isError: boolean;
|
||||
}
|
||||
|
||||
const evaluateCondtion = async (
|
||||
condition: InventoryMetricConditions,
|
||||
nodeType: InventoryItemType,
|
||||
sourceConfiguration: InfraSourceConfiguration,
|
||||
services: AlertServices,
|
||||
filterQuery?: string
|
||||
): Promise<Record<string, ConditionResult>> => {
|
||||
const { comparator, metric } = condition;
|
||||
let { threshold } = condition;
|
||||
|
||||
const currentValues = await getData(
|
||||
services,
|
||||
nodeType,
|
||||
metric,
|
||||
{
|
||||
to: Date.now(),
|
||||
from: moment().subtract(condition.timeSize, condition.timeUnit).toDate().getTime(),
|
||||
interval: condition.timeUnit,
|
||||
},
|
||||
sourceConfiguration,
|
||||
filterQuery
|
||||
);
|
||||
|
||||
threshold = threshold.map((n) => convertMetricValue(metric, n));
|
||||
|
||||
const comparisonFunction = comparatorMap[comparator];
|
||||
|
||||
return mapValues(currentValues, (value) => ({
|
||||
shouldFire: value !== undefined && value !== null && comparisonFunction(value, threshold),
|
||||
metric,
|
||||
currentValue: value,
|
||||
isNoData: value === null,
|
||||
isError: value === undefined,
|
||||
}));
|
||||
};
|
||||
|
||||
const getData = async (
|
||||
services: AlertServices,
|
||||
nodeType: InventoryItemType,
|
||||
metric: SnapshotMetricType,
|
||||
timerange: InfraTimerangeInput,
|
||||
sourceConfiguration: InfraSourceConfiguration,
|
||||
filterQuery?: string
|
||||
) => {
|
||||
const snapshot = new InfraSnapshot();
|
||||
const esClient = <Hit = {}, Aggregation = undefined>(
|
||||
options: CallWithRequestParams
|
||||
): Promise<InfraDatabaseSearchResponse<Hit, Aggregation>> =>
|
||||
services.callCluster('search', options);
|
||||
|
||||
const options = {
|
||||
filterQuery: parseFilterQuery(filterQuery),
|
||||
nodeType,
|
||||
groupBy: [],
|
||||
sourceConfiguration,
|
||||
metric: { type: metric },
|
||||
timerange,
|
||||
};
|
||||
|
||||
const { nodes } = await snapshot.getNodes(esClient, options);
|
||||
|
||||
return nodes.reduce((acc, n) => {
|
||||
const nodePathItem = last(n.path);
|
||||
acc[nodePathItem.label] = n.metric && n.metric.value;
|
||||
return acc;
|
||||
}, {} as Record<string, number | undefined | null>);
|
||||
};
|
||||
|
||||
const comparatorMap = {
|
||||
[Comparator.BETWEEN]: (value: number, [a, b]: number[]) =>
|
||||
value >= Math.min(a, b) && value <= Math.max(a, b),
|
||||
// `threshold` is always an array of numbers in case the BETWEEN comparator is
|
||||
// used; all other compartors will just destructure the first value in the array
|
||||
[Comparator.GT]: (a: number, [b]: number[]) => a > b,
|
||||
[Comparator.LT]: (a: number, [b]: number[]) => a < b,
|
||||
[Comparator.OUTSIDE_RANGE]: (value: number, [a, b]: number[]) => value < a || value > b,
|
||||
[Comparator.GT_OR_EQ]: (a: number, [b]: number[]) => a >= b,
|
||||
[Comparator.LT_OR_EQ]: (a: number, [b]: number[]) => a <= b,
|
||||
};
|
||||
|
||||
const mapToConditionsLookup = (
|
||||
list: any[],
|
||||
mapFn: (value: any, index: number, array: any[]) => unknown
|
||||
|
@ -184,19 +90,6 @@ export const FIRED_ACTIONS = {
|
|||
}),
|
||||
};
|
||||
|
||||
// Some metrics in the UI are in a different unit that what we store in ES.
|
||||
const convertMetricValue = (metric: SnapshotMetricType, value: number) => {
|
||||
if (converters[metric]) {
|
||||
return converters[metric](value);
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
};
|
||||
const converters: Record<string, (n: number) => number> = {
|
||||
cpu: (n) => Number(n) / 100,
|
||||
memory: (n) => Number(n) / 100,
|
||||
};
|
||||
|
||||
const formatMetric = (metric: SnapshotMetricType, value: number) => {
|
||||
// if (SnapshotCustomMetricInputRT.is(metric)) {
|
||||
// const formatter = createFormatterForMetric(metric);
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* 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 { Unit } from '@elastic/datemath';
|
||||
import { first } from 'lodash';
|
||||
import { InventoryMetricConditions } from './types';
|
||||
import { IScopedClusterClient } from '../../../../../../../src/core/server';
|
||||
import { InfraSource } from '../../../../common/http_api/source_api';
|
||||
import { getIntervalInSeconds } from '../../../utils/get_interval_in_seconds';
|
||||
import { InventoryItemType } from '../../../../common/inventory_models/types';
|
||||
import { evaluateCondition } from './evaluate_condition';
|
||||
|
||||
interface InventoryMetricThresholdParams {
|
||||
criteria: InventoryMetricConditions[];
|
||||
filterQuery: string | undefined;
|
||||
nodeType: InventoryItemType;
|
||||
sourceId?: string;
|
||||
}
|
||||
|
||||
interface PreviewInventoryMetricThresholdAlertParams {
|
||||
callCluster: IScopedClusterClient['callAsCurrentUser'];
|
||||
params: InventoryMetricThresholdParams;
|
||||
config: InfraSource['configuration'];
|
||||
lookback: Unit;
|
||||
alertInterval: string;
|
||||
}
|
||||
|
||||
export const previewInventoryMetricThresholdAlert = async ({
|
||||
callCluster,
|
||||
params,
|
||||
config,
|
||||
lookback,
|
||||
alertInterval,
|
||||
}: PreviewInventoryMetricThresholdAlertParams) => {
|
||||
const { criteria, filterQuery, nodeType } = params as InventoryMetricThresholdParams;
|
||||
|
||||
const { timeSize, timeUnit } = criteria[0];
|
||||
const bucketInterval = `${timeSize}${timeUnit}`;
|
||||
const bucketIntervalInSeconds = getIntervalInSeconds(bucketInterval);
|
||||
|
||||
const lookbackInterval = `1${lookback}`;
|
||||
const lookbackIntervalInSeconds = getIntervalInSeconds(lookbackInterval);
|
||||
const lookbackSize = Math.ceil(lookbackIntervalInSeconds / bucketIntervalInSeconds);
|
||||
|
||||
const alertIntervalInSeconds = getIntervalInSeconds(alertInterval);
|
||||
const alertResultsPerExecution = alertIntervalInSeconds / bucketIntervalInSeconds;
|
||||
|
||||
const results = await Promise.all(
|
||||
criteria.map((c) =>
|
||||
evaluateCondition(c, nodeType, config, callCluster, filterQuery, lookbackSize)
|
||||
)
|
||||
);
|
||||
|
||||
const inventoryItems = Object.keys(first(results));
|
||||
const previewResults = inventoryItems.map((item) => {
|
||||
const isNoData = results.some((result) => result[item].isNoData);
|
||||
if (isNoData) {
|
||||
return null;
|
||||
}
|
||||
const isError = results.some((result) => result[item].isError);
|
||||
if (isError) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const numberOfResultBuckets = lookbackSize;
|
||||
const numberOfExecutionBuckets = Math.floor(numberOfResultBuckets / alertResultsPerExecution);
|
||||
return [...Array(numberOfExecutionBuckets)].reduce(
|
||||
(totalFired, _, i) =>
|
||||
totalFired +
|
||||
(results.every((result) => {
|
||||
const shouldFire = result[item].shouldFire as boolean[];
|
||||
return shouldFire[Math.floor(i * alertResultsPerExecution)];
|
||||
})
|
||||
? 1
|
||||
: 0),
|
||||
0
|
||||
);
|
||||
});
|
||||
|
||||
return previewResults;
|
||||
};
|
|
@ -3,6 +3,7 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { Unit } from '@elastic/datemath';
|
||||
import { SnapshotMetricType } from '../../../../common/inventory_models/types';
|
||||
|
||||
export const METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID = 'metrics.alert.inventory.threshold';
|
||||
|
@ -23,12 +24,10 @@ export enum AlertStates {
|
|||
ERROR,
|
||||
}
|
||||
|
||||
export type TimeUnit = 's' | 'm' | 'h' | 'd';
|
||||
|
||||
export interface InventoryMetricConditions {
|
||||
metric: SnapshotMetricType;
|
||||
timeSize: number;
|
||||
timeUnit: TimeUnit;
|
||||
timeUnit: Unit;
|
||||
sourceId?: string;
|
||||
threshold: number[];
|
||||
comparator: Comparator;
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import { first, zip } from 'lodash';
|
||||
import { Unit } from '@elastic/datemath';
|
||||
import {
|
||||
TOO_MANY_BUCKETS_PREVIEW_EXCEPTION,
|
||||
isTooManyBucketsPreviewException,
|
||||
|
@ -25,7 +26,7 @@ interface PreviewMetricThresholdAlertParams {
|
|||
filterQuery: string | undefined;
|
||||
};
|
||||
config: InfraSource['configuration'];
|
||||
lookback: 'h' | 'd' | 'w' | 'M';
|
||||
lookback: Unit;
|
||||
alertInterval: string;
|
||||
end?: number;
|
||||
overrideLookbackIntervalInSeconds?: number;
|
||||
|
|
|
@ -12,8 +12,10 @@ import {
|
|||
alertPreviewRequestParamsRT,
|
||||
alertPreviewSuccessResponsePayloadRT,
|
||||
MetricThresholdAlertPreviewRequestParams,
|
||||
InventoryAlertPreviewRequestParams,
|
||||
} from '../../../common/alerting/metrics';
|
||||
import { createValidationFunction } from '../../../common/runtime_types';
|
||||
import { previewInventoryMetricThresholdAlert } from '../../lib/alerting/inventory_metric_threshold/preview_inventory_metric_threshold_alert';
|
||||
import { previewMetricThresholdAlert } from '../../lib/alerting/metric_threshold/preview_metric_threshold_alert';
|
||||
import { InfraBackendLibs } from '../../lib/infra_types';
|
||||
|
||||
|
@ -76,8 +78,35 @@ export const initAlertPreviewRoute = ({ framework, sources }: InfraBackendLibs)
|
|||
});
|
||||
}
|
||||
case METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID: {
|
||||
// TODO: Add inventory preview functionality
|
||||
return response.ok({});
|
||||
const { nodeType } = request.body as InventoryAlertPreviewRequestParams;
|
||||
const previewResult = await previewInventoryMetricThresholdAlert({
|
||||
callCluster,
|
||||
params: { criteria, filterQuery, nodeType },
|
||||
lookback,
|
||||
config: source.configuration,
|
||||
alertInterval,
|
||||
});
|
||||
|
||||
const numberOfGroups = previewResult.length;
|
||||
const resultTotals = previewResult.reduce(
|
||||
(totals, groupResult) => {
|
||||
if (groupResult === null) return { ...totals, noData: totals.noData + 1 };
|
||||
if (isNaN(groupResult)) return { ...totals, error: totals.error + 1 };
|
||||
return { ...totals, fired: totals.fired + groupResult };
|
||||
},
|
||||
{
|
||||
fired: 0,
|
||||
noData: 0,
|
||||
error: 0,
|
||||
}
|
||||
);
|
||||
|
||||
return response.ok({
|
||||
body: alertPreviewSuccessResponsePayloadRT.encode({
|
||||
numberOfGroups,
|
||||
resultTotals,
|
||||
}),
|
||||
});
|
||||
}
|
||||
default:
|
||||
throw new Error('Unknown alert type');
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue